ip2function.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. #!/usr/bin/env python3
  2. #
  3. # Copyright (C) 2024 Amazon Inc. All rights reserved.
  4. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  5. #
  6. """
  7. This tool corrects function names in call stacks based on the
  8. instruction pointers.
  9. When the AOT file is generated with excluded func-idx in the
  10. `--call-stack-features` parameter, the function indexes are
  11. incorrect (likely they're zero). This script uses instruction
  12. pointers and the original WASM file to generate a call stack
  13. file with the correct function indexes (or function names,
  14. when available).
  15. Example input (call_stack.txt) - note that `__imported_wasi_snapshot_preview1_fd_close`
  16. had index 0, therefore it appears as a name in every line:
  17. ```
  18. #00: 0x0505 - __imported_wasi_snapshot_preview1_fd_close
  19. #01: 0x0309 - __imported_wasi_snapshot_preview1_fd_close
  20. #02: 0x037c - __imported_wasi_snapshot_preview1_fd_close
  21. #03: 0x03b2 - __imported_wasi_snapshot_preview1_fd_close
  22. #04: 0x03e4 - __imported_wasi_snapshot_preview1_fd_close
  23. #05: 0x02e6 - __imported_wasi_snapshot_preview1_fd_close
  24. ```
  25. Conversion command:
  26. ```
  27. python3 test-tools/ip2function/ip2function.py \
  28. --wasm-file opt-samp/tiny.wasm \
  29. call_stack.txt
  30. ```
  31. Output:
  32. ```
  33. #0: 0x0505 - abort
  34. #1: 0x0309 - baz
  35. #2: 0x037c - bar
  36. #3: 0x03b2 - foo
  37. #4: 0x03e4 - __original_main
  38. #5: 0x02e6 - _start
  39. ```
  40. """
  41. import argparse
  42. import bisect
  43. import os
  44. import re
  45. import subprocess
  46. import sys
  47. from typing import NamedTuple, Optional
  48. from typing import TextIO
  49. from pathlib import Path
  50. import shutil
  51. class FunctionInfo(NamedTuple):
  52. start_address: int
  53. idx: int
  54. name: Optional[str]
  55. def __str__(self) -> str:
  56. return self.name if self.name else f"$f{self.idx}"
  57. def load_functions(wasm_objdump: Path, wasm_file: Path) -> list[FunctionInfo]:
  58. objdump_function_pattern = re.compile(
  59. r"^([0-9a-f]+)\sfunc\[(\d+)\](?:\s\<(.+)\>)?\:$"
  60. )
  61. def parse_objdump_function_line(
  62. line: str,
  63. ) -> Optional[FunctionInfo]:
  64. match = objdump_function_pattern.match(line.strip())
  65. return (
  66. FunctionInfo(int(match[1], 16), int(match[2]), match[3]) if match else None
  67. )
  68. p = subprocess.run(
  69. [wasm_objdump, "--disassemble", wasm_file],
  70. check=True,
  71. capture_output=True,
  72. text=True,
  73. universal_newlines=True,
  74. )
  75. return list(
  76. filter(
  77. None,
  78. (
  79. parse_objdump_function_line(line.strip())
  80. for line in p.stdout.split(os.linesep)
  81. ),
  82. )
  83. )
  84. def parse_call_stack_file(
  85. functions: list[FunctionInfo], call_stack_file: TextIO, output_file: TextIO
  86. ) -> None:
  87. call_stack_line_pattern = re.compile(r"^(#\d+): (0x[0-9a-f]+) \- (\S+)$")
  88. for line in call_stack_file:
  89. match = call_stack_line_pattern.match(line.strip())
  90. if not match:
  91. output_file.write(line)
  92. continue
  93. index = match[1]
  94. address = match[2]
  95. func_pos = bisect.bisect_right(
  96. functions, int(address, 16), key=lambda x: x.start_address
  97. )
  98. if func_pos <= 0:
  99. raise ValueError(f"Cannot find function for address {address}")
  100. output_file.write(f"{index}: {address} - {functions[func_pos -1]}\n")
  101. def main() -> int:
  102. parser = argparse.ArgumentParser(description="addr2line for wasm")
  103. parser.add_argument(
  104. "--wasm-objdump", type=Path, default="wasm-objdump", help="path to wasm objdump"
  105. )
  106. parser.add_argument(
  107. "--wasm-file", required=True, type=Path, help="path to wasm file"
  108. )
  109. parser.add_argument(
  110. "call_stack_file", type=argparse.FileType("r"), help="path to a call stack file"
  111. )
  112. parser.add_argument(
  113. "-o",
  114. "--output",
  115. type=argparse.FileType("w"),
  116. default=sys.stdout,
  117. help="Output file path (default is stdout)",
  118. )
  119. args = parser.parse_args()
  120. wasm_objdump: Path = shutil.which(args.wasm_objdump)
  121. assert wasm_objdump is not None
  122. wasm_file: Path = args.wasm_file
  123. assert wasm_file.exists()
  124. parse_call_stack_file(
  125. load_functions(wasm_objdump, wasm_file), args.call_stack_file, args.output
  126. )
  127. return 0
  128. if __name__ == "__main__":
  129. sys.exit(main())