Skip to content

Commit 9c2083a

Browse files
authored
Implement option for skipping function index in the callstack (#3785)
Also add a script that converts instruction pointers to function indexes (or function names). #3758
1 parent b882017 commit 9c2083a

File tree

8 files changed

+229
-34
lines changed

8 files changed

+229
-34
lines changed

core/iwasm/aot/aot_runtime.c

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,15 @@ is_frame_per_function(WASMExecEnv *exec_env)
134134
return module->feature_flags & WASM_FEATURE_FRAME_PER_FUNCTION;
135135
}
136136

137+
static bool
138+
is_frame_func_idx_disabled(WASMExecEnv *exec_env)
139+
{
140+
AOTModule *module =
141+
(AOTModule *)((AOTModuleInstance *)exec_env->module_inst)->module;
142+
143+
return module->feature_flags & WASM_FEATURE_FRAME_NO_FUNC_IDX;
144+
}
145+
137146
static void *
138147
get_top_frame(WASMExecEnv *exec_env)
139148
{
@@ -3952,7 +3961,7 @@ aot_create_call_stack(struct WASMExecEnv *exec_env)
39523961
#endif
39533962
}
39543963
WASMCApiFrame frame = { 0 };
3955-
uint32 max_local_cell_num, max_stack_cell_num;
3964+
uint32 max_local_cell_num = 0, max_stack_cell_num = 0;
39563965
uint32 all_cell_num, lp_size;
39573966

39583967
frame.instance = module_inst;
@@ -3961,16 +3970,20 @@ aot_create_call_stack(struct WASMExecEnv *exec_env)
39613970
frame.func_offset = ip_offset;
39623971
frame.func_name_wp = get_func_name_from_index(module_inst, func_index);
39633972

3964-
if (func_index >= module->import_func_count) {
3965-
uint32 aot_func_idx = func_index - module->import_func_count;
3966-
max_local_cell_num = module->max_local_cell_nums[aot_func_idx];
3967-
max_stack_cell_num = module->max_stack_cell_nums[aot_func_idx];
3968-
}
3969-
else {
3970-
AOTFuncType *func_type = module->import_funcs[func_index].func_type;
3971-
max_local_cell_num =
3972-
func_type->param_cell_num > 2 ? func_type->param_cell_num : 2;
3973-
max_stack_cell_num = 0;
3973+
if (!is_frame_func_idx_disabled(exec_env)) {
3974+
if (func_index >= module->import_func_count) {
3975+
uint32 aot_func_idx = func_index - module->import_func_count;
3976+
max_local_cell_num = module->max_local_cell_nums[aot_func_idx];
3977+
max_stack_cell_num = module->max_stack_cell_nums[aot_func_idx];
3978+
}
3979+
else {
3980+
AOTFuncType *func_type =
3981+
module->import_funcs[func_index].func_type;
3982+
max_local_cell_num = func_type->param_cell_num > 2
3983+
? func_type->param_cell_num
3984+
: 2;
3985+
max_stack_cell_num = 0;
3986+
}
39743987
}
39753988

39763989
all_cell_num = max_local_cell_num + max_stack_cell_num;

core/iwasm/aot/aot_runtime.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ extern "C" {
3434
/* Stack frame is created at the beginning of the function,
3535
* and not at the beginning of each function call */
3636
#define WASM_FEATURE_FRAME_PER_FUNCTION (1 << 12)
37+
#define WASM_FEATURE_FRAME_NO_FUNC_IDX (1 << 13)
3738

3839
typedef enum AOTSectionType {
3940
AOT_SECTION_TYPE_TARGET_INFO = 0,

core/iwasm/compilation/aot_emit_aot_file.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4439,6 +4439,9 @@ aot_obj_data_create(AOTCompContext *comp_ctx)
44394439
if (comp_ctx->call_stack_features.frame_per_function) {
44404440
obj_data->target_info.feature_flags |= WASM_FEATURE_FRAME_PER_FUNCTION;
44414441
}
4442+
if (!comp_ctx->call_stack_features.func_idx) {
4443+
obj_data->target_info.feature_flags |= WASM_FEATURE_FRAME_NO_FUNC_IDX;
4444+
}
44424445

44434446
bh_print_time("Begin to resolve object file info");
44444447

core/iwasm/compilation/aot_emit_function.c

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -885,25 +885,28 @@ alloc_frame_for_aot_func(AOTCompContext *comp_ctx, AOTFuncContext *func_ctx,
885885
}
886886

887887
if (!comp_ctx->is_jit_mode) {
888-
/* aot mode: new_frame->func_idx = func_idx */
889-
func_idx_val = comp_ctx->pointer_size == sizeof(uint64)
890-
? I64_CONST(func_idx)
891-
: I32_CONST(func_idx);
892-
offset = I32_CONST(comp_ctx->pointer_size);
893-
CHECK_LLVM_CONST(func_idx_val);
894-
CHECK_LLVM_CONST(offset);
895-
if (!(func_idx_ptr =
896-
LLVMBuildInBoundsGEP2(comp_ctx->builder, INT8_TYPE, new_frame,
897-
&offset, 1, "func_idx_addr"))
898-
|| !(func_idx_ptr =
899-
LLVMBuildBitCast(comp_ctx->builder, func_idx_ptr,
900-
INTPTR_T_PTR_TYPE, "func_idx_ptr"))) {
901-
aot_set_last_error("llvm get func_idx_ptr failed");
902-
return false;
903-
}
904-
if (!LLVMBuildStore(comp_ctx->builder, func_idx_val, func_idx_ptr)) {
905-
aot_set_last_error("llvm build store failed");
906-
return false;
888+
if (comp_ctx->call_stack_features.func_idx) {
889+
/* aot mode: new_frame->func_idx = func_idx */
890+
func_idx_val = comp_ctx->pointer_size == sizeof(uint64)
891+
? I64_CONST(func_idx)
892+
: I32_CONST(func_idx);
893+
offset = I32_CONST(comp_ctx->pointer_size);
894+
CHECK_LLVM_CONST(func_idx_val);
895+
CHECK_LLVM_CONST(offset);
896+
if (!(func_idx_ptr = LLVMBuildInBoundsGEP2(
897+
comp_ctx->builder, INT8_TYPE, new_frame, &offset, 1,
898+
"func_idx_addr"))
899+
|| !(func_idx_ptr =
900+
LLVMBuildBitCast(comp_ctx->builder, func_idx_ptr,
901+
INTPTR_T_PTR_TYPE, "func_idx_ptr"))) {
902+
aot_set_last_error("llvm get func_idx_ptr failed");
903+
return false;
904+
}
905+
if (!LLVMBuildStore(comp_ctx->builder, func_idx_val,
906+
func_idx_ptr)) {
907+
aot_set_last_error("llvm build store failed");
908+
return false;
909+
}
907910
}
908911
}
909912
else {

core/iwasm/compilation/aot_stack_frame_comp.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,9 @@ aot_alloc_tiny_frame_for_aot_func(AOTCompContext *comp_ctx,
7070
}
7171

7272
/* Save the func_idx on the top of the stack */
73-
ADD_STORE(func_index, wasm_stack_top);
73+
if (comp_ctx->call_stack_features.func_idx) {
74+
ADD_STORE(func_index, wasm_stack_top);
75+
}
7476

7577
/* increment the stack pointer */
7678
INT_CONST(offset, sizeof(AOTTinyFrame), I32_TYPE, true);

core/iwasm/include/aot_comp_option.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,19 @@ typedef struct {
1212
* bounds of the current stack frame (and if not, traps). */
1313
bool bounds_checks;
1414

15-
/* Enables or disables instruction pointer (IP) tracking.*/
15+
/* Enables or disables instruction pointer (IP) tracking. */
1616
bool ip;
1717

18+
/* Enables or disables function index in the stack trace. Please note that
19+
* function index can be recovered from the instruction pointer using
20+
* ip2function.py script, so enabling this feature along with `ip` might
21+
* often be redundant.
22+
* This option will automatically be enabled for GC and Perf Profiling mode.
23+
*/
24+
bool func_idx;
25+
1826
/* Enables or disables tracking instruction pointer of a trap. Only takes
19-
* effect when `ip` is enabled.*/
27+
* effect when `ip` is enabled. */
2028
bool trap_ip;
2129

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

wamr-compiler/main.c

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ print_help()
167167
printf(" By default, all features are enabled. To disable all features,\n");
168168
printf(" provide an empty list (i.e. --call-stack-features=). This flag\n");
169169
printf(" only only takes effect when --enable-dump-call-stack is set.\n");
170-
printf(" Available features: bounds-checks, ip, trap-ip, values.\n");
170+
printf(" Available features: bounds-checks, ip, func-idx, trap-ip, values.\n");
171171
printf(" --enable-perf-profiling Enable function performance profiling\n");
172172
printf(" --enable-memory-profiling Enable memory usage profiling\n");
173173
printf(" --xip A shorthand of --enable-indirect-mode --disable-llvm-intrinsics\n");
@@ -295,6 +295,9 @@ parse_call_stack_features(char *features_str,
295295
else if (!strcmp(features[size], "values")) {
296296
out_features->values = true;
297297
}
298+
else if (!strcmp(features[size], "func-idx")) {
299+
out_features->func_idx = true;
300+
}
298301
else {
299302
ret = false;
300303
printf("Unsupported feature %s\n", features[size]);
@@ -664,6 +667,12 @@ main(int argc, char *argv[])
664667
/* for now we only enable frame per function for a TINY frame mode */
665668
option.call_stack_features.frame_per_function = true;
666669
}
670+
if (!option.call_stack_features.func_idx
671+
&& (option.enable_gc || option.enable_perf_profiling)) {
672+
LOG_WARNING("'func-idx' call stack feature will be automatically "
673+
"enabled for GC and perf profiling mode");
674+
option.call_stack_features.func_idx = true;
675+
}
667676

668677
if (!size_level_set) {
669678
/**

0 commit comments

Comments
 (0)