diff --git a/core/iwasm/aot/aot_runtime.c b/core/iwasm/aot/aot_runtime.c index 013c761a0..4b6d25b83 100644 --- a/core/iwasm/aot/aot_runtime.c +++ b/core/iwasm/aot/aot_runtime.c @@ -134,6 +134,15 @@ is_frame_per_function(WASMExecEnv *exec_env) 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 * get_top_frame(WASMExecEnv *exec_env) { @@ -3952,7 +3961,7 @@ aot_create_call_stack(struct WASMExecEnv *exec_env) #endif } 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; frame.instance = module_inst; @@ -3961,16 +3970,20 @@ aot_create_call_stack(struct WASMExecEnv *exec_env) frame.func_offset = ip_offset; frame.func_name_wp = get_func_name_from_index(module_inst, func_index); - if (func_index >= module->import_func_count) { - uint32 aot_func_idx = func_index - module->import_func_count; - 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; - max_local_cell_num = - func_type->param_cell_num > 2 ? func_type->param_cell_num : 2; - max_stack_cell_num = 0; + if (!is_frame_func_idx_disabled(exec_env)) { + if (func_index >= module->import_func_count) { + uint32 aot_func_idx = func_index - module->import_func_count; + 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; + 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; diff --git a/core/iwasm/aot/aot_runtime.h b/core/iwasm/aot/aot_runtime.h index 3ff0e0e3c..56d11a22d 100644 --- a/core/iwasm/aot/aot_runtime.h +++ b/core/iwasm/aot/aot_runtime.h @@ -34,6 +34,7 @@ extern "C" { /* Stack frame is created at the beginning of the function, * and not at the beginning of each function call */ #define WASM_FEATURE_FRAME_PER_FUNCTION (1 << 12) +#define WASM_FEATURE_FRAME_NO_FUNC_IDX (1 << 13) typedef enum AOTSectionType { AOT_SECTION_TYPE_TARGET_INFO = 0, diff --git a/core/iwasm/compilation/aot_emit_aot_file.c b/core/iwasm/compilation/aot_emit_aot_file.c index 20f29057c..8fa205308 100644 --- a/core/iwasm/compilation/aot_emit_aot_file.c +++ b/core/iwasm/compilation/aot_emit_aot_file.c @@ -4439,6 +4439,9 @@ aot_obj_data_create(AOTCompContext *comp_ctx) if (comp_ctx->call_stack_features.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"); diff --git a/core/iwasm/compilation/aot_emit_function.c b/core/iwasm/compilation/aot_emit_function.c index fbef02e20..11129ac9c 100644 --- a/core/iwasm/compilation/aot_emit_function.c +++ b/core/iwasm/compilation/aot_emit_function.c @@ -885,25 +885,28 @@ alloc_frame_for_aot_func(AOTCompContext *comp_ctx, AOTFuncContext *func_ctx, } if (!comp_ctx->is_jit_mode) { - /* aot mode: new_frame->func_idx = func_idx */ - func_idx_val = comp_ctx->pointer_size == sizeof(uint64) - ? I64_CONST(func_idx) - : I32_CONST(func_idx); - offset = I32_CONST(comp_ctx->pointer_size); - CHECK_LLVM_CONST(func_idx_val); - CHECK_LLVM_CONST(offset); - if (!(func_idx_ptr = - LLVMBuildInBoundsGEP2(comp_ctx->builder, INT8_TYPE, new_frame, - &offset, 1, "func_idx_addr")) - || !(func_idx_ptr = - LLVMBuildBitCast(comp_ctx->builder, func_idx_ptr, - INTPTR_T_PTR_TYPE, "func_idx_ptr"))) { - 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"); - return false; + if (comp_ctx->call_stack_features.func_idx) { + /* aot mode: new_frame->func_idx = func_idx */ + func_idx_val = comp_ctx->pointer_size == sizeof(uint64) + ? I64_CONST(func_idx) + : I32_CONST(func_idx); + offset = I32_CONST(comp_ctx->pointer_size); + CHECK_LLVM_CONST(func_idx_val); + CHECK_LLVM_CONST(offset); + if (!(func_idx_ptr = LLVMBuildInBoundsGEP2( + comp_ctx->builder, INT8_TYPE, new_frame, &offset, 1, + "func_idx_addr")) + || !(func_idx_ptr = + LLVMBuildBitCast(comp_ctx->builder, func_idx_ptr, + INTPTR_T_PTR_TYPE, "func_idx_ptr"))) { + 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"); + return false; + } } } else { diff --git a/core/iwasm/compilation/aot_stack_frame_comp.c b/core/iwasm/compilation/aot_stack_frame_comp.c index 342dfe806..fb540e643 100644 --- a/core/iwasm/compilation/aot_stack_frame_comp.c +++ b/core/iwasm/compilation/aot_stack_frame_comp.c @@ -70,7 +70,9 @@ aot_alloc_tiny_frame_for_aot_func(AOTCompContext *comp_ctx, } /* 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 */ INT_CONST(offset, sizeof(AOTTinyFrame), I32_TYPE, true); diff --git a/core/iwasm/include/aot_comp_option.h b/core/iwasm/include/aot_comp_option.h index 67ec81cd3..98f33a160 100644 --- a/core/iwasm/include/aot_comp_option.h +++ b/core/iwasm/include/aot_comp_option.h @@ -12,11 +12,19 @@ typedef struct { * bounds of the current stack frame (and if not, traps). */ bool bounds_checks; - /* Enables or disables instruction pointer (IP) tracking.*/ + /* Enables or disables instruction pointer (IP) tracking. */ 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 - * effect when `ip` is enabled.*/ + * effect when `ip` is enabled. */ bool trap_ip; /* Enables or disables parameters, locals and stack operands. */ diff --git a/test-tools/ip2function/ip2function.py b/test-tools/ip2function/ip2function.py new file mode 100644 index 000000000..fb8ecd17d --- /dev/null +++ b/test-tools/ip2function/ip2function.py @@ -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()) diff --git a/wamr-compiler/main.c b/wamr-compiler/main.c index 53c75c84e..8dca712cd 100644 --- a/wamr-compiler/main.c +++ b/wamr-compiler/main.c @@ -167,7 +167,7 @@ print_help() 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(" 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-memory-profiling Enable memory usage profiling\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")) { out_features->values = true; } + else if (!strcmp(features[size], "func-idx")) { + out_features->func_idx = true; + } else { ret = false; 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 */ 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) { /**