2024-02-06 12:47:11 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
#
|
|
|
|
# Copyright (C) 2019 Intel Corporation. All rights reserved.
|
|
|
|
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
|
|
#
|
|
|
|
import argparse
|
|
|
|
import os
|
|
|
|
from pathlib import Path
|
|
|
|
import re
|
|
|
|
import shlex
|
|
|
|
import subprocess
|
|
|
|
import sys
|
|
|
|
|
|
|
|
"""
|
|
|
|
it is a tool to transfer the address, which is from a call-stack dump generated by iwasm, to line info for a wasm file.
|
|
|
|
|
|
|
|
> in order to generate the call-stack dump, you can use the following command: `$ cmake -DWAMR_BUILD_DUMP_CALL_STACK=1 ...`
|
|
|
|
|
|
|
|
When a wasm file is compiled with debug info, it is possible to transfer the address to line info.
|
|
|
|
|
|
|
|
For example, there is a call-stack dump:
|
|
|
|
|
|
|
|
```
|
|
|
|
#00: 0x0a04 - $f18
|
|
|
|
#01: 0x08e4 - $f11
|
|
|
|
#02: 0x096f - $f12
|
|
|
|
#03: 0x01aa - _start
|
|
|
|
```
|
|
|
|
|
|
|
|
- store the call-stack dump into a file, e.g. call_stack.txt
|
|
|
|
- run the following command to transfer the address to line info:
|
|
|
|
```
|
|
|
|
$ cd test-tools/addr2line
|
2024-03-01 02:56:36 +00:00
|
|
|
$ python3 addr2line.py --wasi-sdk <wasi-sdk installation> --wasm-file <wasm file path> call_stack.txt
|
2024-02-06 12:47:11 +00:00
|
|
|
```
|
2024-03-01 02:56:36 +00:00
|
|
|
- the script will use *llvm-dwarfdump* to lookup the line info for each address in the call-stack dump.
|
2024-02-06 12:47:11 +00:00
|
|
|
- the output will be:
|
|
|
|
```
|
|
|
|
#00: 0x0a04 - $f18
|
|
|
|
#01: 0x08e4 - $f11 (FILE:quicksort.c LINE: 176 COLUMN: 11 FUNC:Quick)
|
|
|
|
#02: 0x096f - $f12 (FILE:quicksort.c LINE: 182 COLUMN: 3 FUNC:main)
|
|
|
|
#03: 0x01aa - _start
|
|
|
|
```
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
def get_line_info(dwarf_dump: Path, wasm_file: Path, offset: int) -> str:
|
|
|
|
"""
|
|
|
|
Find the location info of a given offset in a wasm file.
|
|
|
|
"""
|
|
|
|
cmd = f"{dwarf_dump} --lookup={offset} {wasm_file}"
|
|
|
|
p = subprocess.run(
|
|
|
|
shlex.split(cmd),
|
|
|
|
check=False,
|
|
|
|
capture_output=True,
|
|
|
|
text=True,
|
|
|
|
universal_newlines=True,
|
|
|
|
)
|
|
|
|
outputs = p.stdout.split(os.linesep)
|
|
|
|
|
|
|
|
capture_name = False
|
|
|
|
for line in outputs:
|
|
|
|
line = line.strip()
|
|
|
|
|
|
|
|
if "DW_TAG_subprogram" in line:
|
|
|
|
capture_name = True
|
|
|
|
continue
|
|
|
|
|
|
|
|
if "DW_AT_name" in line and capture_name:
|
|
|
|
PATTERN = r"DW_AT_name\s+\(\"(\S+)\"\)"
|
|
|
|
m = re.match(PATTERN, line)
|
|
|
|
assert m is not None
|
|
|
|
|
|
|
|
function_name = m.groups()[0]
|
|
|
|
|
|
|
|
if line.startswith("Line info"):
|
|
|
|
location = line
|
|
|
|
return (function_name, location)
|
|
|
|
|
|
|
|
return ()
|
|
|
|
|
|
|
|
|
|
|
|
def parse_line_info(line_info: str) -> ():
|
|
|
|
"""
|
|
|
|
line_info -> [file, line, column]
|
|
|
|
"""
|
|
|
|
PATTERN = r"Line info: file \'(.+)\', line ([0-9]+), column ([0-9]+)"
|
|
|
|
m = re.search(PATTERN, line_info)
|
|
|
|
assert m is not None
|
|
|
|
|
|
|
|
file, line, column = m.groups()
|
|
|
|
return (file, int(line), int(column))
|
|
|
|
|
|
|
|
|
|
|
|
def parse_call_stack_line(line: str) -> ():
|
|
|
|
"""
|
|
|
|
#00: 0x0a04 - $f18 => (00, 0x0a04, $f18)
|
|
|
|
"""
|
|
|
|
PATTERN = r"#([0-9]+): 0x([0-9a-f]+) - (\S+)"
|
|
|
|
m = re.match(PATTERN, line)
|
|
|
|
return m.groups() if m else None
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
parser = argparse.ArgumentParser(description="addr2line for wasm")
|
|
|
|
parser.add_argument("--wasi-sdk", type=Path, help="path to wasi-sdk")
|
|
|
|
parser.add_argument("--wasm-file", type=Path, help="path to wasm file")
|
|
|
|
parser.add_argument("call_stack_file", type=Path, help="path to a call stack file")
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
|
llvm_dwarf_dump = args.wasi_sdk.joinpath("bin/llvm-dwarfdump")
|
|
|
|
assert llvm_dwarf_dump.exists()
|
|
|
|
|
|
|
|
assert args.call_stack_file.exists()
|
|
|
|
with open(args.call_stack_file, "rt", encoding="ascii") as f:
|
|
|
|
for line in f:
|
|
|
|
line = line.strip()
|
|
|
|
|
|
|
|
if not line:
|
|
|
|
continue
|
|
|
|
|
|
|
|
splitted = parse_call_stack_line(line)
|
|
|
|
if splitted is None:
|
|
|
|
print(line)
|
|
|
|
continue
|
|
|
|
|
|
|
|
_, offset, _ = splitted
|
|
|
|
|
|
|
|
offset = int(offset, 16)
|
|
|
|
line_info = get_line_info(llvm_dwarf_dump, args.wasm_file, offset)
|
|
|
|
if not line_info:
|
|
|
|
print(line)
|
|
|
|
continue
|
|
|
|
|
|
|
|
function_name, line_info = line_info
|
|
|
|
src_file, src_line, src_column = parse_line_info(line_info)
|
|
|
|
print(
|
|
|
|
f"{line} (FILE:{src_file} LINE:{src_line:5} COLUMN:{src_column:3} FUNC:{function_name})"
|
|
|
|
)
|
|
|
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
sys.exit(main())
|