Skip to content

Instantly share code, notes, and snippets.

@fitzgen
Last active January 10, 2018 20:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fitzgen/4556873f3af59bd7f01a67f2c1f9f28a to your computer and use it in GitHub Desktop.
Save fitzgen/4556873f3af59bd7f01a67f2c1f9f28a to your computer and use it in GitHub Desktop.
List the callers of some function in the given `.wasm` file.
#!/usr/bin/env python3
import argparse
import re
import subprocess
DESC = """
List the callers of some function in the given `.wasm` file.
Constructs the call graph of functions in the `.wasm` file and then queries
edges in that call graph.
Requires that the `wasm-objdump` tool from the WABT[0] is installed and on the
`$PATH`.
[0]: https://github.com/WebAssembly/wabt
## Example Usage
Print every caller of `std::panicking::begin_panic`:
$ ./who-calls.py path/to/something.wasm \\
--function "std::panicking::begin_panic::h59c9bbae5c8cc295"
Print the top 5 largest functions and their callers:
$ ./who-calls.py path/to/something.wasm --top
"""
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description=DESC)
parser.add_argument(
"wasm_file",
type=str,
help="The input `.wasm` file.")
parser.add_argument(
"--function",
type=str,
help="The function whose callers should be listed.")
parser.add_argument(
"-t",
"--top",
type=int,
default=None,
help="Display the largest N functions and their callers")
parser.add_argument(
"-d",
"--max-depth",
type=int,
default=None,
help="The maximum call stack depth to display")
def decode(f):
return f.decode(encoding="utf-8", errors="ignore")
def run(cmd, **kwargs):
kwargs["stdout"] = subprocess.PIPE
child = subprocess.run(cmd, **kwargs)
if child.returncode != 0:
raise Exception("{} did not exit OK".format(str(cmd)))
return decode(child.stdout)
def disassemble(args):
return run(["wasm-objdump", "-d", args.wasm_file])
START_FUNCTION = re.compile(r"^(\w+) <([\w<>:\s]+)>:$")
CALL_FUNCTION = re.compile(r"^ \w+: [\w ]*\|\s*call \w+ <([\w<>:\s]+)>$")
def parse_call_graph(disassembly, args):
call_graph = {}
current_function = None
for line in disassembly.splitlines():
match = re.match(START_FUNCTION, line)
if match:
current_function = match.groups()[1]
call_graph[current_function] = set()
continue
match = re.match(CALL_FUNCTION, line)
if match and current_function:
call_graph[current_function].add(match.groups()[0])
return call_graph
def parse_top_functions(disassembly, args):
functions = []
last_function = None
for line in disassembly.splitlines():
match = re.match(START_FUNCTION, line)
if match:
(start, function) = match.groups()
start = int(start, 16)
if last_function:
(f, last_start) = last_function
functions.append((f, start - last_start))
last_function = (function, start)
top_functions = sorted(functions, key=lambda a: a[1], reverse=True)
return top_functions[:args.top]
def reverse_call_graph(call_graph, args):
reversed_call_graph = {}
for function, calls in call_graph.items():
if function not in reversed_call_graph:
reversed_call_graph[function] = set()
for call in calls:
if call not in reversed_call_graph:
reversed_call_graph[call] = set()
reversed_call_graph[call].add(function)
return reversed_call_graph
def print_callers(reversed_call_graph, args, function=None, depth=0, seen=set()):
if not function:
function = args.function
if depth == 0:
depth += 1
print("{}".format(function))
if function not in reversed_call_graph:
print(" <function not defined>")
return
for caller in reversed_call_graph[function]:
indent = ""
for _ in range(0, depth):
indent += " "
print("{}⬑ {}".format(indent, caller))
if caller not in seen and (args.max_depth is None or depth < args.max_depth):
seen.add(caller)
print_callers(reversed_call_graph, args, function=caller, depth=depth+1, seen=seen)
def main():
args = parser.parse_args()
disassembly = disassemble(args)
call_graph = parse_call_graph(disassembly, args)
reversed_call_graph = reverse_call_graph(call_graph, args)
if args.function:
print_callers(reversed_call_graph, args)
elif args.top:
top_functions = parse_top_functions(disassembly, args)
for f, size in top_functions:
print(size, "bytes:")
print_callers(reversed_call_graph, args, function=f)
print()
else:
raise Exception("Must use one of --function or --top")
if __name__ == "__main__":
main()
$ ./who-calls.py target/wasm32-unknown-unknown/release/source_map_mappings_wasm_api.opt.wasm \\
--function "std::panicking::begin_panic_fmt::h6b6a5045bf7a8311"
std::panicking::begin_panic_fmt::h6b6a5045bf7a8311
⬑ generated_location_for
⬑ allocate_mappings
⬑ parse_mappings
⬑ original_location_for
⬑ core::panicking::panic_fmt::he95e6f0e21885985
⬑ core::slice::slice_index_len_fail::h0c62925fe2252838
⬑ std::panicking::rust_panic_with_hook::hfadcb90e20ba407f
⬑ std::panicking::begin_panic::hb089ba7cfbb22318
⬑ rand::Rng::gen_range::h8ed2a1384a09f4b7
⬑ source_map_mappings::sort::do_quick_sort::hde52290b1b9201cc
⬑ source_map_mappings::sort::do_quick_sort::hde52290b1b9201cc
⬑ <source_map_mappings::Mappings<O>>::by_original_location::h48efa2a342d4e146
⬑ all_generated_locations_for
⬑ by_original_location
⬑ generated_location_for
⬑ source_map_mappings::sort::do_quick_sort::h8a0fabcee7f70118
⬑ source_map_mappings::sort::do_quick_sort::h8a0fabcee7f70118
⬑ parse_mappings
⬑ parse_mappings
⬑ std::panicking::begin_panic::h59c9bbae5c8cc295
⬑ std::panicking::begin_panic_fmt::h6b6a5045bf7a8311
⬑ generated_location_for
⬑ allocate_mappings
⬑ parse_mappings
⬑ original_location_for
⬑ core::panicking::panic_fmt::he95e6f0e21885985
⬑ std::panicking::begin_panic::h63e35c2db9daa46f
⬑ <std::thread::local::LocalKey<T>>::try_with::h282e06b1430b7131
⬑ std::panicking::rust_panic_with_hook::hfadcb90e20ba407f
⬑ std::panicking::rust_panic_with_hook::hfadcb90e20ba407f
⬑ core::char_private::check::hbec220ddcd84fbe8
⬑ core::char_private::is_printable::hef17caa7cedaa45e
⬑ core::fmt::Write::write_fmt::hfe66dc2c11f06023
⬑ std::panicking::begin_panic_fmt::h6b6a5045bf7a8311
⬑ <char as core::fmt::Debug>::fmt::h2746778dae637484
⬑ core::result::unwrap_failed::h303cf7c77329cf85
⬑ get_last_error
⬑ parse_mappings
⬑ core::option::expect_failed::h841b03527805d3d3
⬑ <alloc::vec::Vec<T>>::extend_from_slice::h5afd22416635e8d7
⬑ core::ptr::drop_in_place::h3f9d4ba6261882a6
⬑ <alloc::vec::Vec<T>>::extend_from_slice::h82a1bcb67712e202
⬑ std::io::error::Error::new::h14956c84e93b0007
⬑ std::io::Write::write_all::h4a2e1d1753837a69
⬑ std::panicking::rust_panic_with_hook::hfadcb90e20ba407f
⬑ core::ptr::drop_in_place::h01c992b2a38446bc
⬑ core::fmt::Write::write_char::hd0c82db72b3c6fd5
⬑ core::ptr::drop_in_place::h3f9d4ba6261882a6
⬑ std::panicking::rust_panic_with_hook::hfadcb90e20ba407f
⬑ std::io::Write::write_fmt::h6f93bad64abded4d
⬑ std::sys_common::util::dumb_print::h08caa4e4a69ce437
⬑ std::panicking::rust_panic_with_hook::hfadcb90e20ba407f
⬑ std::panicking::rust_panic_with_hook::hfadcb90e20ba407f
⬑ parse_mappings
⬑ <source_map_mappings::Mappings<O>>::by_original_location::h48efa2a342d4e146
⬑ allocate_mappings
⬑ core::result::unwrap_failed::ha4f67c374d262ff6
⬑ <std::thread::local::LocalKey<T>>::try_with::h282e06b1430b7131
⬑ core::result::unwrap_failed::h0d23a36b00c86a70
⬑ <std::thread::local::LocalKey<T>>::try_with::h282e06b1430b7131
⬑ std::panicking::rust_panic_with_hook::hfadcb90e20ba407f
⬑ core::slice::slice_index_order_fail::h5047276ce7ab5559
⬑ core::char_private::check::hbec220ddcd84fbe8
⬑ core::panicking::panic_bounds_check::hf426de951c6773a2
⬑ core::fmt::write::h3fdcfa41538f8361
⬑ core::fmt::Write::write_fmt::hcf209b9ab583bfd9
⬑ core::ptr::drop_in_place::h3f9d4ba6261882a6
⬑ <core::ops::range::Range<Idx> as core::fmt::Debug>::fmt::h963c041bf5a11a92
⬑ core::fmt::num::<impl core::fmt::Display for usize>::fmt::h190ae91f4e6e797c
⬑ rand::Rng::gen_range::h8ed2a1384a09f4b7
⬑ core::fmt::num::<impl core::fmt::Debug for usize>::fmt::hac7e5e02de6d35bc
⬑ core::fmt::builders::DebugTuple::field::h29d9f36ce3e7b021
⬑ std::error::Error::type_id::h9d0f8f179effe427
⬑ core::fmt::Write::write_fmt::h8135df65faa514f9
⬑ core::fmt::Write::write_fmt::hfe66dc2c11f06023
⬑ std::io::Write::write_fmt::h6f93bad64abded4d
⬑ all_generated_locations_for
⬑ original_location_for
⬑ generated_location_for
⬑ core::str::slice_error_fail::h2de2c9b902e4c2f4
⬑ core::str::slice_error_fail::h2de2c9b902e4c2f4
⬑ <core::cell::BorrowMutError as core::fmt::Debug>::fmt::h75621c8fb5cc826a
⬑ core::fmt::Formatter::pad::h33d7155441061e69
⬑ rand::Rng::gen_range::h8ed2a1384a09f4b7
⬑ std::error::Error::type_id::h9d0f8f179effe427
⬑ core::fmt::Write::write_fmt::hfe66dc2c11f06023
⬑ core::result::unwrap_failed::h2ef4fcdd006ccc3e
⬑ std::panicking::rust_panic_with_hook::hfadcb90e20ba407f
⬑ <std::thread::local::LocalKey<T>>::with::h6962dc74436e3e5a
⬑ std::panicking::rust_panic_with_hook::hfadcb90e20ba407f
⬑ core::panicking::panic::h0c8240d335aa06f1
⬑ core::fmt::write::h3fdcfa41538f8361
⬑ <alloc::vec::Vec<T>>::extend_from_slice::h5afd22416635e8d7
⬑ parse_mappings
⬑ generated_location_for
⬑ <alloc::vec::Vec<T>>::extend_from_slice::h82a1bcb67712e202
⬑ source_map_mappings::sort::do_quick_sort::h8a0fabcee7f70118
⬑ by_original_location
⬑ <source_map_mappings::Mappings<O>>::by_original_location::h48efa2a342d4e146
⬑ std::panicking::rust_panic_with_hook::hfadcb90e20ba407f
⬑ <source_map_mappings::Mappings<O>>::compute_column_spans::h304b76a7be460676
⬑ compute_column_spans
⬑ <source_map_mappings::Mappings<O>>::by_original_location::h48efa2a342d4e146
⬑ by_generated_location
⬑ source_map_mappings::sort::do_quick_sort::hde52290b1b9201cc
⬑ all_generated_locations_for
⬑ core::str::slice_error_fail::h2de2c9b902e4c2f4
⬑ rand::Rng::gen_range::h8ed2a1384a09f4b7
⬑ original_location_for
⬑ <T as core::any::Any>::get_type_id::h855349fbd4c430f4
⬑ compute_column_spans
⬑ allocate_mappings
⬑ core::char_private::check::hbec220ddcd84fbe8
⬑ alloc::allocator::Layout::array::hdbe1bc1ebbc17fd3
⬑ <source_map_mappings::Mappings<O>>::by_original_location::h48efa2a342d4e146
⬑ <source_map_mappings::Mappings<O>>::compute_column_spans::h304b76a7be460676
$ ./who-calls.py --top 5 --max-depth 2 target/wasm32-unknown-unknown/release/source_map_mappings_wasm_api.opt.wasm
3409 bytes:
dlmalloc::dlmalloc::Dlmalloc::malloc::hb7db7cffe7eae9d8
⬑ __rust_realloc
⬑ <alloc::vec::Vec<T>>::extend_from_slice::hbe130cc89ef92abc
⬑ <source_map_mappings::Mappings<O>>::by_original_location::hce8ace2a0e2a4bb3
⬑ <T as core::any::Any>::get_type_id::h8a116c174de7ff8c
⬑ <alloc::vec::Vec<T>>::extend_from_slice::ha11979e3f44fb95a
⬑ alloc::allocator::Layout::array::h9e9ad5f5ad4ae5c6
⬑ dlmalloc::dlmalloc::Dlmalloc::memalign::h441b1c2355f124c0
⬑ __rust_realloc
⬑ __rust_alloc
⬑ __rust_alloc
⬑ std::sys::wasm::unsupported::ha10f63ee1611f134
⬑ parse_mappings
⬑ std::sys::wasm::unsupported::h54aa47085aab4d9d
⬑ std::sys_common::thread_info::THREAD_INFO::__getit::ha84da844d11d72c3
⬑ std::panicking::begin_panic::he18ca0199c0c50e0
⬑ std::panicking::LOCAL_STDERR::__getit::hb68975c206e1a4f4
⬑ <T as core::any::Any>::get_type_id::h8a116c174de7ff8c
⬑ <std::thread::local::LocalKey<T>>::try_with::h04afe1b0f0a0454a
⬑ <alloc::vec::Vec<T>>::extend_from_slice::ha11979e3f44fb95a
⬑ alloc::allocator::Layout::array::h9e9ad5f5ad4ae5c6
⬑ std::panicking::begin_panic::haa8adef7fad5642e
⬑ <alloc::vec::Vec<T>>::extend_from_slice::hbe130cc89ef92abc
⬑ source_map_mappings_wasm_api::LAST_ERROR::__getit::h63c6eaba6115430a
⬑ <T as core::convert::Into<U>>::into::haa168c9fcbe3c9a5
⬑ std::panicking::begin_panic::h24d55e330bfd6ad4
⬑ std::panicking::update_panic_count::PANIC_COUNT::__getit::h7ed37a609a2cc76c
⬑ std::sys_common::thread_local::StaticKey::lazy_init::h55c1ea984aefb78e
⬑ allocate_mappings
⬑ <source_map_mappings::Mappings<O>>::by_original_location::hce8ace2a0e2a4bb3
⬑ std::sys::wasm::unsupported::h8a0c9d428e59c8b5
⬑ std::io::error::Error::new::h2062df6b85d29f4e
3008 bytes:
std::panicking::rust_panic_with_hook::h02b37a5538e89bc2
⬑ std::panicking::begin_panic::he18ca0199c0c50e0
⬑ std::panicking::begin_panic_fmt::h951592fd1be3d1f2
⬑ std::panicking::begin_panic::haa8adef7fad5642e
⬑ rand::Rng::gen_range::h0f7e11b952ead81c
⬑ std::panicking::begin_panic::h24d55e330bfd6ad4
⬑ std::panicking::rust_panic_with_hook::h02b37a5538e89bc2
⬑ <std::thread::local::LocalKey<T>>::try_with::h04afe1b0f0a0454a
1833 bytes:
core::str::slice_error_fail::h105f13cd96633aba
⬑ core::ptr::drop_in_place::hdb9b0daa1583986f
⬑ core::fmt::Formatter::pad::hb35b2f03e1c6174a
⬑ <T as core::any::Any>::get_type_id::hd6007c4034ea0caa
⬑ std::error::Error::type_id::h0e778952f812afba
⬑ core::str::slice_error_fail::h105f13cd96633aba
⬑ core::ptr::drop_in_place::hdb9b0daa1583986f
⬑ core::fmt::Formatter::pad::hb35b2f03e1c6174a
⬑ core::str::slice_error_fail::h105f13cd96633aba
1751 bytes:
parse_mappings
1315 bytes:
core::fmt::Formatter::pad_integral::ha821d9da14fd6ac2
⬑ core::fmt::num::<impl core::fmt::Display for usize>::fmt::h3ef074a904b5a6c9
⬑ core::fmt::num::<impl core::fmt::Debug for usize>::fmt::h064ef8bd351ca3c5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment