Last active
January 10, 2018 20:40
-
-
Save fitzgen/4556873f3af59bd7f01a67f2c1f9f28a to your computer and use it in GitHub Desktop.
List the callers of some function in the given `.wasm` file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$ ./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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$ ./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