Quellcode durchsuchen

Merge pull request #4033 from g0djan/godjan/iterate_callstack

Copy callstack API
liang.he vor 10 Monaten
Ursprung
Commit
766f378590

+ 5 - 0
CMakeLists.txt

@@ -99,6 +99,11 @@ if (NOT DEFINED WAMR_BUILD_LIB_WASI_THREADS)
   set (WAMR_BUILD_LIB_WASI_THREADS 0)
 endif ()
 
+if (NOT DEFINED WAMR_ENABLE_COPY_CALLSTACK)
+  # Disable copy callstack by default
+  set (WAMR_ENABLE_COPY_CALLSTACK 0)
+endif()
+
 if (NOT DEFINED WAMR_BUILD_MINI_LOADER)
   # Disable wasm mini loader by default
   set (WAMR_BUILD_MINI_LOADER 0)

+ 8 - 0
build-scripts/config_common.cmake

@@ -324,6 +324,14 @@ if (WAMR_BUILD_SHARED_HEAP EQUAL 1)
   message ("     Shared heap enabled")
 endif()
 
+if (WAMR_ENABLE_COPY_CALLSTACK EQUAL 1)
+  add_definitions (-DWAMR_ENABLE_COPY_CALLSTACK=1)
+  message("     Copy callstack enabled")
+else ()
+  add_definitions (-DWAMR_ENABLE_COPY_CALLSTACK=0)
+  message("     Copy callstack disabled")
+endif()
+
 if (WAMR_BUILD_MEMORY64 EQUAL 1)
   # if native is 32-bit or cross-compiled to 32-bit
   if (NOT WAMR_BUILD_TARGET MATCHES ".*64.*")

+ 4 - 0
core/config.h

@@ -193,6 +193,10 @@
 #error "Heap aux stack allocation must be enabled for WASI threads"
 #endif
 
+#ifndef WAMR_ENABLE_COPY_CALLSTACK
+#define WAMR_ENABLE_COPY_CALLSTACK 0
+#endif
+
 #ifndef WASM_ENABLE_BASE_LIB
 #define WASM_ENABLE_BASE_LIB 0
 #endif

+ 130 - 0
core/iwasm/aot/aot_runtime.c

@@ -4103,6 +4103,136 @@ aot_frame_update_profile_info(WASMExecEnv *exec_env, bool alloc_frame)
 }
 #endif /* end of WASM_ENABLE_AOT_STACK_FRAME != 0 */
 
+#if WAMR_ENABLE_COPY_CALLSTACK != 0
+uint32
+aot_copy_callstack_tiny_frame(WASMExecEnv *exec_env, wasm_frame_t *buffer,
+                              const uint32 length, const uint32 skip_n,
+                              char *error_buf, uint32 error_buf_size)
+{
+    /*
+     * Note for devs: please refrain from such modifications inside of
+     * aot_copy_callstack_tiny_frame
+     * - any allocations/freeing memory
+     * - dereferencing any pointers other than: exec_env, exec_env->module_inst,
+     * exec_env->module_inst->module, pointers between stack's bottom and
+     * top_boundary For more details check wasm_copy_callstack in
+     * wasm_export.h
+     */
+    uint8 *top_boundary = exec_env->wasm_stack.top_boundary;
+    uint8 *top = exec_env->wasm_stack.top;
+    uint8 *bottom = exec_env->wasm_stack.bottom;
+    uint32 count = 0;
+
+    bool is_top_index_in_range =
+        top_boundary >= top && top >= (bottom + sizeof(AOTTinyFrame));
+    if (!is_top_index_in_range) {
+        char *err_msg =
+            "Top of the stack pointer is outside of the stack boundaries";
+        strncpy(error_buf, err_msg, error_buf_size);
+        return 0;
+    }
+    bool is_top_aligned_with_bottom =
+        (unsigned long)(top - bottom) % sizeof(AOTTinyFrame) == 0;
+    if (!is_top_aligned_with_bottom) {
+        char *err_msg = "Top of the stack is not aligned with the bottom";
+        strncpy(error_buf, err_msg, error_buf_size);
+        return 0;
+    }
+
+    AOTTinyFrame *frame = (AOTTinyFrame *)(top - sizeof(AOTTinyFrame));
+    WASMCApiFrame record_frame;
+    while (frame && (uint8_t *)frame >= bottom && count < (skip_n + length)) {
+        if (count < skip_n) {
+            ++count;
+            frame -= 1;
+            continue;
+        }
+        record_frame.instance = exec_env->module_inst;
+        record_frame.module_offset = 0;
+        record_frame.func_index = frame->func_index;
+        record_frame.func_offset = frame->ip_offset;
+        buffer[count - skip_n] = record_frame;
+        frame -= 1;
+        ++count;
+    }
+    return count >= skip_n ? count - skip_n : 0;
+}
+
+uint32
+aot_copy_callstack_standard_frame(WASMExecEnv *exec_env, wasm_frame_t *buffer,
+                                  const uint32 length, const uint32 skip_n,
+                                  char *error_buf, uint32_t error_buf_size)
+{
+    /*
+     * Note for devs: please refrain from such modifications inside of
+     * aot_iterate_callstack_standard_frame
+     * - any allocations/freeing memory
+     * - dereferencing any pointers other than: exec_env, exec_env->module_inst,
+     * exec_env->module_inst->module, pointers between stack's bottom and
+     * top_boundary For more details check wasm_iterate_callstack in
+     * wasm_export.h
+     */
+
+    uint32 count = 0;
+#if WASM_ENABLE_GC == 0
+    WASMModuleInstance *module_inst =
+        (WASMModuleInstance *)wasm_exec_env_get_module_inst(exec_env);
+    AOTFrame *cur_frame = (AOTFrame *)wasm_exec_env_get_cur_frame(exec_env);
+    uint8 *top_boundary = exec_env->wasm_stack.top_boundary;
+    uint8 *bottom = exec_env->wasm_stack.bottom;
+    uint32 frame_size = (uint32)offsetof(AOTFrame, lp);
+
+    WASMCApiFrame record_frame;
+    while (cur_frame && (uint8_t *)cur_frame >= bottom
+           && (uint8_t *)cur_frame + frame_size <= top_boundary
+           && count < (skip_n + length)) {
+        if (count < skip_n) {
+            ++count;
+            cur_frame = cur_frame->prev_frame;
+            continue;
+        }
+        record_frame.instance = module_inst;
+        record_frame.module_offset = 0;
+        record_frame.func_index = (uint32)cur_frame->func_index;
+        record_frame.func_offset = (uint32)cur_frame->ip_offset;
+        buffer[count - skip_n] = record_frame;
+        cur_frame = cur_frame->prev_frame;
+        ++count;
+    }
+#else
+/*
+ * TODO: add support for standard frames when GC is enabled
+ * now it poses a risk due to variable size of the frame
+ */
+#endif
+    return count >= skip_n ? count - skip_n : 0;
+}
+
+uint32
+aot_copy_callstack(WASMExecEnv *exec_env, wasm_frame_t *buffer,
+                   const uint32 length, const uint32 skip_n, char *error_buf,
+                   uint32_t error_buf_size)
+{
+    /*
+     * Note for devs: please refrain from such modifications inside of
+     * aot_iterate_callstack
+     * - any allocations/freeing memory
+     * - dereferencing any pointers other than: exec_env, exec_env->module_inst,
+     * exec_env->module_inst->module, pointers between stack's bottom and
+     * top_boundary For more details check wasm_iterate_callstack in
+     * wasm_export.h
+     */
+    if (!is_tiny_frame(exec_env)) {
+        return aot_copy_callstack_standard_frame(
+            exec_env, buffer, length, skip_n, error_buf, error_buf_size);
+    }
+    else {
+        return aot_copy_callstack_tiny_frame(exec_env, buffer, length, skip_n,
+                                             error_buf, error_buf_size);
+    }
+}
+#endif // WAMR_ENABLE_COPY_CALLSTACK
+
 #if WASM_ENABLE_DUMP_CALL_STACK != 0
 bool
 aot_create_call_stack(struct WASMExecEnv *exec_env)

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

@@ -777,6 +777,13 @@ aot_frame_update_profile_info(WASMExecEnv *exec_env, bool alloc_frame);
 bool
 aot_create_call_stack(struct WASMExecEnv *exec_env);
 
+#if WAMR_ENABLE_COPY_CALLSTACK != 0
+uint32
+aot_copy_callstack(WASMExecEnv *exec_env, wasm_frame_t *buffer,
+                   const uint32 length, const uint32 skip_n, char *error_buf,
+                   uint32_t error_buf_size);
+#endif // WAMR_ENABLE_COPY_CALLSTACK
+
 /**
  * @brief Dump wasm call stack or get the size
  *

+ 39 - 0
core/iwasm/common/wasm_runtime_common.c

@@ -1740,6 +1740,45 @@ wasm_runtime_destroy_exec_env(WASMExecEnv *exec_env)
     wasm_exec_env_destroy(exec_env);
 }
 
+#if WAMR_ENABLE_COPY_CALLSTACK != 0
+uint32
+wasm_copy_callstack(const wasm_exec_env_t exec_env, wasm_frame_t *buffer,
+                    const uint32 length, const uint32 skip_n, char *error_buf,
+                    uint32_t error_buf_size)
+{
+    /*
+     * Note for devs: please refrain from such modifications inside of
+     * wasm_copy_callstack to preserve async-signal-safety
+     * - any allocations/freeing memory
+     * - dereferencing any pointers other than: exec_env, exec_env->module_inst,
+     * exec_env->module_inst->module, pointers between stack's bottom and
+     * top_boundary For more details check wasm_copy_callstack in
+     * wasm_export.h
+     */
+#if WASM_ENABLE_DUMP_CALL_STACK
+    WASMModuleInstance *module_inst =
+        (WASMModuleInstance *)get_module_inst(exec_env);
+
+#if WASM_ENABLE_INTERP != 0
+    if (module_inst->module_type == Wasm_Module_Bytecode) {
+        return wasm_interp_copy_callstack(exec_env, buffer, length, skip_n,
+                                          error_buf, error_buf_size);
+    }
+#endif
+
+#if WASM_ENABLE_AOT != 0
+    if (module_inst->module_type == Wasm_Module_AoT) {
+        return aot_copy_callstack(exec_env, buffer, length, skip_n, error_buf,
+                                  error_buf_size);
+    }
+#endif
+#endif
+    char *err_msg = "No copy_callstack API was actually executed";
+    strncpy(error_buf, err_msg, error_buf_size);
+    return 0;
+}
+#endif // WAMR_ENABLE_COPY_CALLSTACK
+
 bool
 wasm_runtime_init_thread_env(void)
 {

+ 7 - 13
core/iwasm/common/wasm_runtime_common.h

@@ -464,19 +464,6 @@ typedef struct WASMRegisteredModule {
 typedef package_type_t PackageType;
 typedef wasm_section_t WASMSection, AOTSection;
 
-typedef struct wasm_frame_t {
-    /*  wasm_instance_t */
-    void *instance;
-    uint32 module_offset;
-    uint32 func_index;
-    uint32 func_offset;
-    const char *func_name_wp;
-
-    uint32 *sp;
-    uint8 *frame_ref;
-    uint32 *lp;
-} WASMCApiFrame;
-
 #if WASM_ENABLE_JIT != 0
 typedef struct LLVMJITOptions {
     uint32 opt_level;
@@ -652,6 +639,13 @@ wasm_runtime_create_exec_env(WASMModuleInstanceCommon *module_inst,
 WASM_RUNTIME_API_EXTERN void
 wasm_runtime_destroy_exec_env(WASMExecEnv *exec_env);
 
+#if WAMR_ENABLE_COPY_CALLSTACK != 0
+WASM_RUNTIME_API_EXTERN uint32_t
+wasm_copy_callstack(const wasm_exec_env_t exec_env, wasm_frame_t *buffer,
+                    const uint32 length, const uint32 skip_n, char *error_buf,
+                    uint32 error_buf_size);
+#endif // WAMR_ENABLE_COPY_CALLSTACK
+
 /* See wasm_export.h for description */
 WASM_RUNTIME_API_EXTERN WASMModuleInstanceCommon *
 wasm_runtime_get_module_inst(WASMExecEnv *exec_env);

+ 44 - 0
core/iwasm/include/wasm_export.h

@@ -126,6 +126,21 @@ typedef WASMFunctionInstanceCommon *wasm_function_inst_t;
 struct WASMMemoryInstance;
 typedef struct WASMMemoryInstance *wasm_memory_inst_t;
 
+typedef struct wasm_frame_t {
+    /*  wasm_instance_t */
+    void *instance;
+    uint32_t module_offset;
+    uint32_t func_index;
+    uint32_t func_offset;
+    const char *func_name_wp;
+
+    uint32_t *sp;
+    uint8_t *frame_ref;
+    uint32_t *lp;
+} WASMCApiFrame;
+
+typedef WASMCApiFrame wasm_frame_t;
+
 /* WASM section */
 typedef struct wasm_section_t {
     struct wasm_section_t *next;
@@ -864,6 +879,35 @@ wasm_runtime_create_exec_env(wasm_module_inst_t module_inst,
 WASM_RUNTIME_API_EXTERN void
 wasm_runtime_destroy_exec_env(wasm_exec_env_t exec_env);
 
+/**
+ * @brief Copy callstack frames.
+ *
+ * Caution: This is not a thread-safe function. Ensure the exec_env
+ * is suspended before calling it from another thread.
+ *
+ * Usage: In the callback to read frames fields use APIs
+ * for wasm_frame_t from wasm_c_api.h
+ *
+ * Note: The function is async-signal-safe if called with verified arguments.
+ * Meaning it's safe to call it from a signal handler even on a signal
+ * interruption from another thread if next variables hold valid pointers
+ * - exec_env
+ * - exec_env->module_inst
+ * - exec_env->module_inst->module
+ *
+ * @param exec_env the execution environment that containes frames
+ * @param buffer the buffer of size equal length * sizeof(wasm_frame_t) to copy
+ * frames to
+ * @param length the number of frames to copy
+ * @param skip_n the number of frames to skip from the top of the stack
+ *
+ * @return number of copied frames
+ */
+WASM_RUNTIME_API_EXTERN uint32_t
+wasm_copy_callstack(const wasm_exec_env_t exec_env, wasm_frame_t *buffer,
+                    const uint32_t length, const uint32_t skip_n,
+                    char *error_buf, uint32_t error_buf_size);
+
 /**
  * Get the singleton execution environment for the instance.
  *

+ 49 - 0
core/iwasm/interpreter/wasm_runtime.c

@@ -4195,6 +4195,55 @@ wasm_get_module_inst_mem_consumption(const WASMModuleInstance *module_inst,
 #endif /* end of (WASM_ENABLE_MEMORY_PROFILING != 0) \
                  || (WASM_ENABLE_MEMORY_TRACING != 0) */
 
+#if WAMR_ENABLE_COPY_CALLSTACK != 0
+uint32
+wasm_interp_copy_callstack(WASMExecEnv *exec_env, wasm_frame_t *buffer,
+                           uint32 length, uint32 skip_n, char *error_buf,
+                           uint32_t error_buf_size)
+{
+    /*
+     * Note for devs: please refrain from such modifications inside of
+     * wasm_interp_copy_callstack
+     * - any allocations/freeing memory
+     * - dereferencing any pointers other than: exec_env, exec_env->module_inst,
+     * exec_env->module_inst->module, pointers between stack's bottom and
+     * top_boundary For more details check wasm_copy_callstack in
+     * wasm_export.h
+     */
+    WASMModuleInstance *module_inst =
+        (WASMModuleInstance *)wasm_exec_env_get_module_inst(exec_env);
+    WASMInterpFrame *cur_frame = wasm_exec_env_get_cur_frame(exec_env);
+    uint8 *top_boundary = exec_env->wasm_stack.top_boundary;
+    uint8 *bottom = exec_env->wasm_stack.bottom;
+    uint32 count = 0;
+
+    WASMCApiFrame record_frame;
+    while (cur_frame && (uint8_t *)cur_frame >= bottom
+           && (uint8_t *)cur_frame + sizeof(WASMInterpFrame) <= top_boundary
+           && count < (skip_n + length)) {
+        if (!cur_frame->function) {
+            cur_frame = cur_frame->prev_frame;
+            continue;
+        }
+        if (count < skip_n) {
+            ++count;
+            cur_frame = cur_frame->prev_frame;
+            continue;
+        }
+        record_frame.instance = module_inst;
+        record_frame.module_offset = 0;
+        // It's safe to dereference module_inst->e because "e" is asigned only
+        // once in wasm_instantiate
+        record_frame.func_index =
+            (uint32)(cur_frame->function - module_inst->e->functions);
+        buffer[count - skip_n] = record_frame;
+        cur_frame = cur_frame->prev_frame;
+        ++count;
+    }
+    return count >= skip_n ? count - skip_n : 0;
+}
+#endif // WAMR_ENABLE_COPY_CALLSTACK
+
 #if WASM_ENABLE_DUMP_CALL_STACK != 0
 bool
 wasm_interp_create_call_stack(struct WASMExecEnv *exec_env)

+ 8 - 0
core/iwasm/interpreter/wasm_runtime.h

@@ -730,6 +730,14 @@ wasm_get_table_inst(const WASMModuleInstance *module_inst, uint32 tbl_idx)
 }
 
 #if WASM_ENABLE_DUMP_CALL_STACK != 0
+
+#if WAMR_ENABLE_COPY_CALLSTACK != 0
+uint32
+wasm_interp_copy_callstack(WASMExecEnv *exec_env, wasm_frame_t *buffer,
+                           uint32 length, uint32 skip_n, char *error_buf,
+                           uint32_t error_buf_size);
+#endif // WAMR_ENABLE_COPY_CALLSTACK
+
 bool
 wasm_interp_create_call_stack(struct WASMExecEnv *exec_env);