Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

it is a tool to transfer the address, in a call-stack dump, to line info for a wasm file #3128

Merged
merged 2 commits into from
Feb 5, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 192 additions & 0 deletions test-tools/addr2line/addr2line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
#!/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
$ python3 addr2line.py --wasi-sdk <wasi-sdk installation> --wabt <wabt installation> --wasm-file <wasm file path> call_stack.txt
```
- the script will use *wasm-objdump* in wabt to transform address, then use *llvm-dwarfdump* to lookup the line info for each address
in the call-stack dump.
- 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_code_section_start(wasm_objdump: Path, wasm_file: Path) -> int:
"""
Find the start offset of Code section in a wasm file.

if the code section header likes:
Code start=0x0000017c end=0x00004382 (size=0x00004206) count: 47

the start offset is 0x0000017c
"""
cmd = f"{wasm_objdump} -h {wasm_file}"
p = subprocess.run(
shlex.split(cmd),
check=True,
capture_output=True,
text=True,
universal_newlines=True,
)
outputs = p.stdout.split(os.linesep)

# if there is no .debug section, return -1
for line in outputs:
line = line.strip()
if ".debug_info" in line:
break
else:
print(f"No .debug_info section found {wasm_file}")
return -1

for line in outputs:
line = line.strip()
if "Code" in line:
return int(line.split()[1].split("=")[1], 16)

return -1


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("--wabt", type=Path, help="path to wabt")
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()

wasm_objdump = args.wabt.joinpath("bin/wasm-objdump")
assert wasm_objdump.exists()

llvm_dwarf_dump = args.wasi_sdk.joinpath("bin/llvm-dwarfdump")
assert llvm_dwarf_dump.exists()

code_section_start = get_code_section_start(wasm_objdump, args.wasm_file)
if code_section_start == -1:
return -1

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)
offset = offset - code_section_start
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())