Implement option for skipping function index in the callstack (#3785)
Also add a script that converts instruction pointers to function indexes (or function names). https://github.com/bytecodealliance/wasm-micro-runtime/issues/3758
This commit is contained in:
@ -134,6 +134,15 @@ is_frame_per_function(WASMExecEnv *exec_env)
|
|||||||
return module->feature_flags & WASM_FEATURE_FRAME_PER_FUNCTION;
|
return module->feature_flags & WASM_FEATURE_FRAME_PER_FUNCTION;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
is_frame_func_idx_disabled(WASMExecEnv *exec_env)
|
||||||
|
{
|
||||||
|
AOTModule *module =
|
||||||
|
(AOTModule *)((AOTModuleInstance *)exec_env->module_inst)->module;
|
||||||
|
|
||||||
|
return module->feature_flags & WASM_FEATURE_FRAME_NO_FUNC_IDX;
|
||||||
|
}
|
||||||
|
|
||||||
static void *
|
static void *
|
||||||
get_top_frame(WASMExecEnv *exec_env)
|
get_top_frame(WASMExecEnv *exec_env)
|
||||||
{
|
{
|
||||||
@ -3952,7 +3961,7 @@ aot_create_call_stack(struct WASMExecEnv *exec_env)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
WASMCApiFrame frame = { 0 };
|
WASMCApiFrame frame = { 0 };
|
||||||
uint32 max_local_cell_num, max_stack_cell_num;
|
uint32 max_local_cell_num = 0, max_stack_cell_num = 0;
|
||||||
uint32 all_cell_num, lp_size;
|
uint32 all_cell_num, lp_size;
|
||||||
|
|
||||||
frame.instance = module_inst;
|
frame.instance = module_inst;
|
||||||
@ -3961,16 +3970,20 @@ aot_create_call_stack(struct WASMExecEnv *exec_env)
|
|||||||
frame.func_offset = ip_offset;
|
frame.func_offset = ip_offset;
|
||||||
frame.func_name_wp = get_func_name_from_index(module_inst, func_index);
|
frame.func_name_wp = get_func_name_from_index(module_inst, func_index);
|
||||||
|
|
||||||
if (func_index >= module->import_func_count) {
|
if (!is_frame_func_idx_disabled(exec_env)) {
|
||||||
uint32 aot_func_idx = func_index - module->import_func_count;
|
if (func_index >= module->import_func_count) {
|
||||||
max_local_cell_num = module->max_local_cell_nums[aot_func_idx];
|
uint32 aot_func_idx = func_index - module->import_func_count;
|
||||||
max_stack_cell_num = module->max_stack_cell_nums[aot_func_idx];
|
max_local_cell_num = module->max_local_cell_nums[aot_func_idx];
|
||||||
}
|
max_stack_cell_num = module->max_stack_cell_nums[aot_func_idx];
|
||||||
else {
|
}
|
||||||
AOTFuncType *func_type = module->import_funcs[func_index].func_type;
|
else {
|
||||||
max_local_cell_num =
|
AOTFuncType *func_type =
|
||||||
func_type->param_cell_num > 2 ? func_type->param_cell_num : 2;
|
module->import_funcs[func_index].func_type;
|
||||||
max_stack_cell_num = 0;
|
max_local_cell_num = func_type->param_cell_num > 2
|
||||||
|
? func_type->param_cell_num
|
||||||
|
: 2;
|
||||||
|
max_stack_cell_num = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
all_cell_num = max_local_cell_num + max_stack_cell_num;
|
all_cell_num = max_local_cell_num + max_stack_cell_num;
|
||||||
|
|||||||
@ -34,6 +34,7 @@ extern "C" {
|
|||||||
/* Stack frame is created at the beginning of the function,
|
/* Stack frame is created at the beginning of the function,
|
||||||
* and not at the beginning of each function call */
|
* and not at the beginning of each function call */
|
||||||
#define WASM_FEATURE_FRAME_PER_FUNCTION (1 << 12)
|
#define WASM_FEATURE_FRAME_PER_FUNCTION (1 << 12)
|
||||||
|
#define WASM_FEATURE_FRAME_NO_FUNC_IDX (1 << 13)
|
||||||
|
|
||||||
typedef enum AOTSectionType {
|
typedef enum AOTSectionType {
|
||||||
AOT_SECTION_TYPE_TARGET_INFO = 0,
|
AOT_SECTION_TYPE_TARGET_INFO = 0,
|
||||||
|
|||||||
@ -4439,6 +4439,9 @@ aot_obj_data_create(AOTCompContext *comp_ctx)
|
|||||||
if (comp_ctx->call_stack_features.frame_per_function) {
|
if (comp_ctx->call_stack_features.frame_per_function) {
|
||||||
obj_data->target_info.feature_flags |= WASM_FEATURE_FRAME_PER_FUNCTION;
|
obj_data->target_info.feature_flags |= WASM_FEATURE_FRAME_PER_FUNCTION;
|
||||||
}
|
}
|
||||||
|
if (!comp_ctx->call_stack_features.func_idx) {
|
||||||
|
obj_data->target_info.feature_flags |= WASM_FEATURE_FRAME_NO_FUNC_IDX;
|
||||||
|
}
|
||||||
|
|
||||||
bh_print_time("Begin to resolve object file info");
|
bh_print_time("Begin to resolve object file info");
|
||||||
|
|
||||||
|
|||||||
@ -885,25 +885,28 @@ alloc_frame_for_aot_func(AOTCompContext *comp_ctx, AOTFuncContext *func_ctx,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!comp_ctx->is_jit_mode) {
|
if (!comp_ctx->is_jit_mode) {
|
||||||
/* aot mode: new_frame->func_idx = func_idx */
|
if (comp_ctx->call_stack_features.func_idx) {
|
||||||
func_idx_val = comp_ctx->pointer_size == sizeof(uint64)
|
/* aot mode: new_frame->func_idx = func_idx */
|
||||||
? I64_CONST(func_idx)
|
func_idx_val = comp_ctx->pointer_size == sizeof(uint64)
|
||||||
: I32_CONST(func_idx);
|
? I64_CONST(func_idx)
|
||||||
offset = I32_CONST(comp_ctx->pointer_size);
|
: I32_CONST(func_idx);
|
||||||
CHECK_LLVM_CONST(func_idx_val);
|
offset = I32_CONST(comp_ctx->pointer_size);
|
||||||
CHECK_LLVM_CONST(offset);
|
CHECK_LLVM_CONST(func_idx_val);
|
||||||
if (!(func_idx_ptr =
|
CHECK_LLVM_CONST(offset);
|
||||||
LLVMBuildInBoundsGEP2(comp_ctx->builder, INT8_TYPE, new_frame,
|
if (!(func_idx_ptr = LLVMBuildInBoundsGEP2(
|
||||||
&offset, 1, "func_idx_addr"))
|
comp_ctx->builder, INT8_TYPE, new_frame, &offset, 1,
|
||||||
|| !(func_idx_ptr =
|
"func_idx_addr"))
|
||||||
LLVMBuildBitCast(comp_ctx->builder, func_idx_ptr,
|
|| !(func_idx_ptr =
|
||||||
INTPTR_T_PTR_TYPE, "func_idx_ptr"))) {
|
LLVMBuildBitCast(comp_ctx->builder, func_idx_ptr,
|
||||||
aot_set_last_error("llvm get func_idx_ptr failed");
|
INTPTR_T_PTR_TYPE, "func_idx_ptr"))) {
|
||||||
return false;
|
aot_set_last_error("llvm get func_idx_ptr failed");
|
||||||
}
|
return false;
|
||||||
if (!LLVMBuildStore(comp_ctx->builder, func_idx_val, func_idx_ptr)) {
|
}
|
||||||
aot_set_last_error("llvm build store failed");
|
if (!LLVMBuildStore(comp_ctx->builder, func_idx_val,
|
||||||
return false;
|
func_idx_ptr)) {
|
||||||
|
aot_set_last_error("llvm build store failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
@ -70,7 +70,9 @@ aot_alloc_tiny_frame_for_aot_func(AOTCompContext *comp_ctx,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Save the func_idx on the top of the stack */
|
/* Save the func_idx on the top of the stack */
|
||||||
ADD_STORE(func_index, wasm_stack_top);
|
if (comp_ctx->call_stack_features.func_idx) {
|
||||||
|
ADD_STORE(func_index, wasm_stack_top);
|
||||||
|
}
|
||||||
|
|
||||||
/* increment the stack pointer */
|
/* increment the stack pointer */
|
||||||
INT_CONST(offset, sizeof(AOTTinyFrame), I32_TYPE, true);
|
INT_CONST(offset, sizeof(AOTTinyFrame), I32_TYPE, true);
|
||||||
|
|||||||
@ -12,11 +12,19 @@ typedef struct {
|
|||||||
* bounds of the current stack frame (and if not, traps). */
|
* bounds of the current stack frame (and if not, traps). */
|
||||||
bool bounds_checks;
|
bool bounds_checks;
|
||||||
|
|
||||||
/* Enables or disables instruction pointer (IP) tracking.*/
|
/* Enables or disables instruction pointer (IP) tracking. */
|
||||||
bool ip;
|
bool ip;
|
||||||
|
|
||||||
|
/* Enables or disables function index in the stack trace. Please note that
|
||||||
|
* function index can be recovered from the instruction pointer using
|
||||||
|
* ip2function.py script, so enabling this feature along with `ip` might
|
||||||
|
* often be redundant.
|
||||||
|
* This option will automatically be enabled for GC and Perf Profiling mode.
|
||||||
|
*/
|
||||||
|
bool func_idx;
|
||||||
|
|
||||||
/* Enables or disables tracking instruction pointer of a trap. Only takes
|
/* Enables or disables tracking instruction pointer of a trap. Only takes
|
||||||
* effect when `ip` is enabled.*/
|
* effect when `ip` is enabled. */
|
||||||
bool trap_ip;
|
bool trap_ip;
|
||||||
|
|
||||||
/* Enables or disables parameters, locals and stack operands. */
|
/* Enables or disables parameters, locals and stack operands. */
|
||||||
|
|||||||
156
test-tools/ip2function/ip2function.py
Normal file
156
test-tools/ip2function/ip2function.py
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
#
|
||||||
|
# Copyright (C) 2024 Amazon Inc. All rights reserved.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
This tool corrects function names in call stacks based on the
|
||||||
|
instruction pointers.
|
||||||
|
|
||||||
|
When the AOT file is generated with excluded func-idx in the
|
||||||
|
`--call-stack-features` parameter, the function indexes are
|
||||||
|
incorrect (likely they're zero). This script uses instruction
|
||||||
|
pointers and the original WASM file to generate a call stack
|
||||||
|
file with the correct function indexes (or function names,
|
||||||
|
when available).
|
||||||
|
|
||||||
|
Example input (call_stack.txt) - note that `__imported_wasi_snapshot_preview1_fd_close`
|
||||||
|
had index 0, therefore it appears as a name in every line:
|
||||||
|
```
|
||||||
|
#00: 0x0505 - __imported_wasi_snapshot_preview1_fd_close
|
||||||
|
#01: 0x0309 - __imported_wasi_snapshot_preview1_fd_close
|
||||||
|
#02: 0x037c - __imported_wasi_snapshot_preview1_fd_close
|
||||||
|
#03: 0x03b2 - __imported_wasi_snapshot_preview1_fd_close
|
||||||
|
#04: 0x03e4 - __imported_wasi_snapshot_preview1_fd_close
|
||||||
|
#05: 0x02e6 - __imported_wasi_snapshot_preview1_fd_close
|
||||||
|
```
|
||||||
|
|
||||||
|
Conversion command:
|
||||||
|
```
|
||||||
|
python3 test-tools/ip2function/ip2function.py \
|
||||||
|
--wasm-file opt-samp/tiny.wasm \
|
||||||
|
call_stack.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
```
|
||||||
|
#0: 0x0505 - abort
|
||||||
|
#1: 0x0309 - baz
|
||||||
|
#2: 0x037c - bar
|
||||||
|
#3: 0x03b2 - foo
|
||||||
|
#4: 0x03e4 - __original_main
|
||||||
|
#5: 0x02e6 - _start
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import bisect
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from typing import NamedTuple, Optional
|
||||||
|
from typing import TextIO
|
||||||
|
from pathlib import Path
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
|
||||||
|
class FunctionInfo(NamedTuple):
|
||||||
|
start_address: int
|
||||||
|
idx: int
|
||||||
|
name: Optional[str]
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.name if self.name else f"$f{self.idx}"
|
||||||
|
|
||||||
|
|
||||||
|
def load_functions(wasm_objdump: Path, wasm_file: Path) -> list[FunctionInfo]:
|
||||||
|
objdump_function_pattern = re.compile(
|
||||||
|
r"^([0-9a-f]+)\sfunc\[(\d+)\](?:\s\<(.+)\>)?\:$"
|
||||||
|
)
|
||||||
|
|
||||||
|
def parse_objdump_function_line(
|
||||||
|
line: str,
|
||||||
|
) -> Optional[FunctionInfo]:
|
||||||
|
match = objdump_function_pattern.match(line.strip())
|
||||||
|
return (
|
||||||
|
FunctionInfo(int(match[1], 16), int(match[2]), match[3]) if match else None
|
||||||
|
)
|
||||||
|
|
||||||
|
p = subprocess.run(
|
||||||
|
[wasm_objdump, "--disassemble", wasm_file],
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
universal_newlines=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
return list(
|
||||||
|
filter(
|
||||||
|
None,
|
||||||
|
(
|
||||||
|
parse_objdump_function_line(line.strip())
|
||||||
|
for line in p.stdout.split(os.linesep)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_call_stack_file(
|
||||||
|
functions: list[FunctionInfo], call_stack_file: TextIO, output_file: TextIO
|
||||||
|
) -> None:
|
||||||
|
call_stack_line_pattern = re.compile(r"^(#\d+): (0x[0-9a-f]+) \- (\S+)$")
|
||||||
|
for line in call_stack_file:
|
||||||
|
match = call_stack_line_pattern.match(line.strip())
|
||||||
|
if not match:
|
||||||
|
output_file.write(line)
|
||||||
|
continue
|
||||||
|
index = match[1]
|
||||||
|
address = match[2]
|
||||||
|
|
||||||
|
func_pos = bisect.bisect_right(
|
||||||
|
functions, int(address, 16), key=lambda x: x.start_address
|
||||||
|
)
|
||||||
|
if func_pos <= 0:
|
||||||
|
raise ValueError(f"Cannot find function for address {address}")
|
||||||
|
output_file.write(f"{index}: {address} - {functions[func_pos -1]}\n")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(description="addr2line for wasm")
|
||||||
|
parser.add_argument(
|
||||||
|
"--wasm-objdump", type=Path, default="wasm-objdump", help="path to wasm objdump"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--wasm-file", required=True, type=Path, help="path to wasm file"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"call_stack_file", type=argparse.FileType("r"), help="path to a call stack file"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-o",
|
||||||
|
"--output",
|
||||||
|
type=argparse.FileType("w"),
|
||||||
|
default=sys.stdout,
|
||||||
|
help="Output file path (default is stdout)",
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
wasm_objdump: Path = shutil.which(args.wasm_objdump)
|
||||||
|
assert wasm_objdump is not None
|
||||||
|
|
||||||
|
wasm_file: Path = args.wasm_file
|
||||||
|
assert wasm_file.exists()
|
||||||
|
|
||||||
|
parse_call_stack_file(
|
||||||
|
load_functions(wasm_objdump, wasm_file), args.call_stack_file, args.output
|
||||||
|
)
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@ -167,7 +167,7 @@ print_help()
|
|||||||
printf(" By default, all features are enabled. To disable all features,\n");
|
printf(" By default, all features are enabled. To disable all features,\n");
|
||||||
printf(" provide an empty list (i.e. --call-stack-features=). This flag\n");
|
printf(" provide an empty list (i.e. --call-stack-features=). This flag\n");
|
||||||
printf(" only only takes effect when --enable-dump-call-stack is set.\n");
|
printf(" only only takes effect when --enable-dump-call-stack is set.\n");
|
||||||
printf(" Available features: bounds-checks, ip, trap-ip, values.\n");
|
printf(" Available features: bounds-checks, ip, func-idx, trap-ip, values.\n");
|
||||||
printf(" --enable-perf-profiling Enable function performance profiling\n");
|
printf(" --enable-perf-profiling Enable function performance profiling\n");
|
||||||
printf(" --enable-memory-profiling Enable memory usage profiling\n");
|
printf(" --enable-memory-profiling Enable memory usage profiling\n");
|
||||||
printf(" --xip A shorthand of --enable-indirect-mode --disable-llvm-intrinsics\n");
|
printf(" --xip A shorthand of --enable-indirect-mode --disable-llvm-intrinsics\n");
|
||||||
@ -295,6 +295,9 @@ parse_call_stack_features(char *features_str,
|
|||||||
else if (!strcmp(features[size], "values")) {
|
else if (!strcmp(features[size], "values")) {
|
||||||
out_features->values = true;
|
out_features->values = true;
|
||||||
}
|
}
|
||||||
|
else if (!strcmp(features[size], "func-idx")) {
|
||||||
|
out_features->func_idx = true;
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
ret = false;
|
ret = false;
|
||||||
printf("Unsupported feature %s\n", features[size]);
|
printf("Unsupported feature %s\n", features[size]);
|
||||||
@ -664,6 +667,12 @@ main(int argc, char *argv[])
|
|||||||
/* for now we only enable frame per function for a TINY frame mode */
|
/* for now we only enable frame per function for a TINY frame mode */
|
||||||
option.call_stack_features.frame_per_function = true;
|
option.call_stack_features.frame_per_function = true;
|
||||||
}
|
}
|
||||||
|
if (!option.call_stack_features.func_idx
|
||||||
|
&& (option.enable_gc || option.enable_perf_profiling)) {
|
||||||
|
LOG_WARNING("'func-idx' call stack feature will be automatically "
|
||||||
|
"enabled for GC and perf profiling mode");
|
||||||
|
option.call_stack_features.func_idx = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!size_level_set) {
|
if (!size_level_set) {
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user