Ver código fonte

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
Marcin Kolny 1 ano atrás
pai
commit
9c2083a27f

+ 24 - 11
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;
     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) {
-            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;
         all_cell_num = max_local_cell_num + max_stack_cell_num;

+ 1 - 0
core/iwasm/aot/aot_runtime.h

@@ -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,

+ 3 - 0
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) {
     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");
 
 

+ 22 - 19
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) {
     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 {
     else {

+ 3 - 1
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 */
     /* 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);

+ 10 - 2
core/iwasm/include/aot_comp_option.h

@@ -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 - 0
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())

+ 10 - 1
wamr-compiler/main.c

@@ -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) {
         /**
         /**