Ver Fonte

Add wasm_runtime_detect_native_stack_overflow_size (#3355)

- Add a few API (https://github.com/bytecodealliance/wasm-micro-runtime/issues/3325)
   ```c
   wasm_runtime_detect_native_stack_overflow_size
   wasm_runtime_detect_native_stack_overflow
   ```
- Adapt the runtime to use them
- Adapt samples/native-stack-overflow to use them
- Add a few missing overflow checks in the interpreters
- Build and run the sample on the CI
YAMAMOTO Takashi há 1 ano atrás
pai
commit
410ee580ae

+ 7 - 0
.github/workflows/compilation_on_android_ubuntu.yml

@@ -491,6 +491,13 @@ jobs:
           ./iwasm wasm-apps/trap.aot | grep "#" > call_stack_aot.txt
           bash -x ../symbolicate.sh
 
+      - name: Build Sample [native-stack-overflow]
+        run: |
+          cd samples/native-stack-overflow
+          ./build.sh
+          ./run.sh test1
+          ./run.sh test2
+
   test:
     needs:
       [

+ 9 - 0
.github/workflows/compilation_on_macos.yml

@@ -379,3 +379,12 @@ jobs:
           ./iwasm wasm-apps/trap.wasm | grep "#" > call_stack.txt
           ./iwasm wasm-apps/trap.aot | grep "#" > call_stack_aot.txt
           bash -x ../symbolicate.sh
+
+      # skip on arm64 (macos-14) for now
+      - name: Build Sample [native-stack-overflow]
+        if: matrix.os != 'macos-14'
+        run: |
+          cd samples/native-stack-overflow
+          ./build.sh
+          ./run.sh test1
+          ./run.sh test2

+ 8 - 0
.github/workflows/nightly_run.yml

@@ -548,6 +548,13 @@ jobs:
           ./build.sh
           ./run.sh
 
+      - name: Build Sample [native-stack-overflow]
+        run: |
+          cd samples/native-stack-overflow
+          ./build.sh
+          ./run.sh test1
+          ./run.sh test2
+
       - name: Build Sample [native-lib]
         run: |
           mkdir build && cd build
@@ -567,6 +574,7 @@ jobs:
           python3 ./sample_test_run.py $(pwd)/out
           exit $?
         working-directory: ./wamr-app-framework/samples/simple
+
   test:
     needs:
       [

+ 2 - 9
core/iwasm/aot/aot_runtime.c

@@ -1967,8 +1967,6 @@ invoke_native_with_hw_bound_check(WASMExecEnv *exec_env, void *func_ptr,
     AOTModuleInstance *module_inst = (AOTModuleInstance *)exec_env->module_inst;
     WASMExecEnv *exec_env_tls = wasm_runtime_get_exec_env_tls();
     WASMJmpBuf jmpbuf_node = { 0 }, *jmpbuf_node_pop;
-    uint32 page_size = os_getpagesize();
-    uint32 guard_page_count = STACK_OVERFLOW_CHECK_GUARD_PAGE_COUNT;
 #ifdef BH_PLATFORM_WINDOWS
     int result;
     bool has_exception;
@@ -1979,10 +1977,7 @@ invoke_native_with_hw_bound_check(WASMExecEnv *exec_env, void *func_ptr,
     /* Check native stack overflow firstly to ensure we have enough
        native stack to run the following codes before actually calling
        the aot function in invokeNative function. */
-    RECORD_STACK_USAGE(exec_env, (uint8 *)&module_inst);
-    if ((uint8 *)&module_inst
-        < exec_env->native_stack_boundary + page_size * guard_page_count) {
-        aot_set_exception_with_id(module_inst, EXCE_NATIVE_STACK_OVERFLOW);
+    if (!wasm_runtime_detect_native_stack_overflow(exec_env)) {
         return false;
     }
 
@@ -2790,9 +2785,7 @@ aot_call_indirect(WASMExecEnv *exec_env, uint32 tbl_idx, uint32 table_elem_idx,
        exec_env->native_stack_boundary must have been set, we don't set
        it again */
 
-    RECORD_STACK_USAGE(exec_env, (uint8 *)&module_inst);
-    if ((uint8 *)&module_inst < exec_env->native_stack_boundary) {
-        aot_set_exception_with_id(module_inst, EXCE_NATIVE_STACK_OVERFLOW);
+    if (!wasm_runtime_detect_native_stack_overflow(exec_env)) {
         goto fail;
     }
 

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

@@ -7015,3 +7015,59 @@ wasm_runtime_get_module_name(wasm_module_t module)
 
     return "";
 }
+
+/*
+ * wasm_runtime_detect_native_stack_overflow
+ *
+ * - raise "native stack overflow" exception if available native stack
+ *   at this point is less than WASM_STACK_GUARD_SIZE. in that case,
+ *   return false.
+ *
+ * - update native_stack_top_min.
+ */
+bool
+wasm_runtime_detect_native_stack_overflow(WASMExecEnv *exec_env)
+{
+    uint8 *boundary = exec_env->native_stack_boundary;
+    RECORD_STACK_USAGE(exec_env, (uint8 *)&boundary);
+    if (boundary == NULL) {
+        /* the platfrom doesn't support os_thread_get_stack_boundary */
+        return true;
+    }
+#if defined(OS_ENABLE_HW_BOUND_CHECK) && WASM_DISABLE_STACK_HW_BOUND_CHECK == 0
+    uint32 page_size = os_getpagesize();
+    uint32 guard_page_count = STACK_OVERFLOW_CHECK_GUARD_PAGE_COUNT;
+    boundary = boundary + page_size * guard_page_count;
+#endif
+    if ((uint8 *)&boundary < boundary) {
+        wasm_runtime_set_exception(wasm_runtime_get_module_inst(exec_env),
+                                   "native stack overflow");
+        return false;
+    }
+    return true;
+}
+
+bool
+wasm_runtime_detect_native_stack_overflow_size(WASMExecEnv *exec_env,
+                                               uint32 requested_size)
+{
+    uint8 *boundary = exec_env->native_stack_boundary;
+    RECORD_STACK_USAGE(exec_env, (uint8 *)&boundary);
+    if (boundary == NULL) {
+        /* the platfrom doesn't support os_thread_get_stack_boundary */
+        return true;
+    }
+#if defined(OS_ENABLE_HW_BOUND_CHECK) && WASM_DISABLE_STACK_HW_BOUND_CHECK == 0
+    uint32 page_size = os_getpagesize();
+    uint32 guard_page_count = STACK_OVERFLOW_CHECK_GUARD_PAGE_COUNT;
+    boundary = boundary + page_size * guard_page_count;
+#endif
+    /* adjust the boundary for the requested size */
+    boundary = boundary - WASM_STACK_GUARD_SIZE + requested_size;
+    if ((uint8 *)&boundary < boundary) {
+        wasm_runtime_set_exception(wasm_runtime_get_module_inst(exec_env),
+                                   "native stack overflow");
+        return false;
+    }
+    return true;
+}

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

@@ -1189,6 +1189,13 @@ wasm_runtime_end_blocking_op(WASMExecEnv *exec_env);
 void
 wasm_runtime_interrupt_blocking_op(WASMExecEnv *exec_env);
 
+WASM_RUNTIME_API_EXTERN bool
+wasm_runtime_detect_native_stack_overflow(WASMExecEnv *exec_env);
+
+WASM_RUNTIME_API_EXTERN bool
+wasm_runtime_detect_native_stack_overflow_size(WASMExecEnv *exec_env,
+                                               uint32 requested_size);
+
 #if WASM_ENABLE_LINUX_PERF != 0
 bool
 wasm_runtime_get_linux_perf(void);

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

@@ -1756,6 +1756,57 @@ wasm_runtime_set_module_name(wasm_module_t module, const char *name,
 WASM_RUNTIME_API_EXTERN const char *
 wasm_runtime_get_module_name(wasm_module_t module);
 
+/*
+ * wasm_runtime_detect_native_stack_overflow
+ *
+ * Detect native stack shortage.
+ * Ensure that the calling thread still has a reasonable amount of
+ * native stack (WASM_STACK_GUARD_SIZE bytes) available.
+ *
+ * If enough stack is left, this function returns true.
+ * Otherwise, this function raises a "native stack overflow" trap and
+ * returns false.
+ *
+ * Note: please do not expect a very strict detection. it's a good idea
+ * to give some margins. wasm_runtime_detect_native_stack_overflow itself
+ * requires a small amount of stack to run.
+ */
+WASM_RUNTIME_API_EXTERN bool
+wasm_runtime_detect_native_stack_overflow(wasm_exec_env_t exec_env);
+
+/*
+ * wasm_runtime_detect_native_stack_overflow_size
+ *
+ * Similar to wasm_runtime_detect_native_stack_overflow,
+ * but use the caller-specified size instead of WASM_STACK_GUARD_SIZE.
+ *
+ * An expected usage:
+ * ```c
+ * __attribute__((noinline))  // inlining can break the stack check
+ * void stack_hog(void)
+ * {
+ *     // consume a lot of stack here
+ * }
+ *
+ * void
+ * stack_hog_wrapper(exec_env) {
+ *     // the amount of stack stack_hog would consume,
+ *     // plus a small margin
+ *     uint32_t size = 10000000;
+ *
+ *     if (!wasm_runtime_detect_native_stack_overflow_size(exec_env, size)) {
+ *         // wasm_runtime_detect_native_stack_overflow_size has raised
+ *         // a trap.
+ *         return;
+ *     }
+ *     stack_hog();
+ * }
+ * ```
+ */
+WASM_RUNTIME_API_EXTERN bool
+wasm_runtime_detect_native_stack_overflow_size(wasm_exec_env_t exec_env,
+                                               uint32_t required_size);
+
 #ifdef __cplusplus
 }
 #endif

+ 19 - 6
core/iwasm/interpreter/wasm_interp_classic.c

@@ -1159,6 +1159,10 @@ wasm_interp_call_func_native(WASMModuleInstance *module_inst,
     uint8 *frame_ref;
 #endif
 
+    if (!wasm_runtime_detect_native_stack_overflow(exec_env)) {
+        return;
+    }
+
     all_cell_num = local_cell_num;
 #if WASM_ENABLE_GC != 0
     all_cell_num += (local_cell_num + 3) / 4;
@@ -1290,6 +1294,14 @@ wasm_interp_call_func_import(WASMModuleInstance *module_inst,
     uintptr_t aux_stack_origin_boundary = 0;
     uintptr_t aux_stack_origin_bottom = 0;
 
+    /*
+     * perform stack overflow check before calling
+     * wasm_interp_call_func_bytecode recursively.
+     */
+    if (!wasm_runtime_detect_native_stack_overflow(exec_env)) {
+        return;
+    }
+
     if (!sub_func_inst) {
         snprintf(buf, sizeof(buf),
                  "failed to call unlinked import function (%s, %s)",
@@ -7108,12 +7120,13 @@ wasm_interp_call_wasm(WASMModuleInstance *module_inst, WASMExecEnv *exec_env,
     }
     argc = function->param_cell_num;
 
-    RECORD_STACK_USAGE(exec_env, (uint8 *)&prev_frame);
-#if !(defined(OS_ENABLE_HW_BOUND_CHECK) \
-      && WASM_DISABLE_STACK_HW_BOUND_CHECK == 0)
-    if ((uint8 *)&prev_frame < exec_env->native_stack_boundary) {
-        wasm_set_exception((WASMModuleInstance *)exec_env->module_inst,
-                           "native stack overflow");
+#if defined(OS_ENABLE_HW_BOUND_CHECK) && WASM_DISABLE_STACK_HW_BOUND_CHECK == 0
+    /*
+     * wasm_runtime_detect_native_stack_overflow is done by
+     * call_wasm_with_hw_bound_check.
+     */
+#else
+    if (!wasm_runtime_detect_native_stack_overflow(exec_env)) {
         return;
     }
 #endif

+ 19 - 6
core/iwasm/interpreter/wasm_interp_fast.c

@@ -1167,6 +1167,10 @@ wasm_interp_call_func_native(WASMModuleInstance *module_inst,
     all_cell_num += (local_cell_num + 3) / 4;
 #endif
 
+    if (!wasm_runtime_detect_native_stack_overflow(exec_env)) {
+        return;
+    }
+
     if (!(frame =
               ALLOC_FRAME(exec_env, wasm_interp_interp_frame_size(all_cell_num),
                           prev_frame)))
@@ -1275,6 +1279,14 @@ wasm_interp_call_func_import(WASMModuleInstance *module_inst,
     uintptr_t aux_stack_origin_boundary = 0;
     uintptr_t aux_stack_origin_bottom = 0;
 
+    /*
+     * perform stack overflow check before calling
+     * wasm_interp_call_func_bytecode recursively.
+     */
+    if (!wasm_runtime_detect_native_stack_overflow(exec_env)) {
+        return;
+    }
+
     if (!sub_func_inst) {
         snprintf(buf, sizeof(buf),
                  "failed to call unlinked import function (%s, %s)",
@@ -6081,12 +6093,13 @@ wasm_interp_call_wasm(WASMModuleInstance *module_inst, WASMExecEnv *exec_env,
     }
     argc = function->param_cell_num;
 
-    RECORD_STACK_USAGE(exec_env, (uint8 *)&prev_frame);
-#if !(defined(OS_ENABLE_HW_BOUND_CHECK) \
-      && WASM_DISABLE_STACK_HW_BOUND_CHECK == 0)
-    if ((uint8 *)&prev_frame < exec_env->native_stack_boundary) {
-        wasm_set_exception((WASMModuleInstance *)exec_env->module_inst,
-                           "native stack overflow");
+#if defined(OS_ENABLE_HW_BOUND_CHECK) && WASM_DISABLE_STACK_HW_BOUND_CHECK == 0
+    /*
+     * wasm_runtime_detect_native_stack_overflow is done by
+     * call_wasm_with_hw_bound_check.
+     */
+#else
+    if (!wasm_runtime_detect_native_stack_overflow(exec_env)) {
         return;
     }
 #endif

+ 1 - 6
core/iwasm/interpreter/wasm_runtime.c

@@ -3139,8 +3139,6 @@ call_wasm_with_hw_bound_check(WASMModuleInstance *module_inst,
 {
     WASMExecEnv *exec_env_tls = wasm_runtime_get_exec_env_tls();
     WASMJmpBuf jmpbuf_node = { 0 }, *jmpbuf_node_pop;
-    uint32 page_size = os_getpagesize();
-    uint32 guard_page_count = STACK_OVERFLOW_CHECK_GUARD_PAGE_COUNT;
     WASMRuntimeFrame *prev_frame = wasm_exec_env_get_cur_frame(exec_env);
     uint8 *prev_top = exec_env->wasm_stack.top;
 #ifdef BH_PLATFORM_WINDOWS
@@ -3153,10 +3151,7 @@ call_wasm_with_hw_bound_check(WASMModuleInstance *module_inst,
     /* Check native stack overflow firstly to ensure we have enough
        native stack to run the following codes before actually calling
        the aot function in invokeNative function. */
-    RECORD_STACK_USAGE(exec_env, (uint8 *)&exec_env_tls);
-    if ((uint8 *)&exec_env_tls
-        < exec_env->native_stack_boundary + page_size * guard_page_count) {
-        wasm_set_exception(module_inst, "native stack overflow");
+    if (!wasm_runtime_detect_native_stack_overflow(exec_env)) {
         return;
     }
 

+ 5 - 0
samples/native-stack-overflow/CMakeLists.txt

@@ -72,6 +72,11 @@ if (CMAKE_C_COMPILER_ID MATCHES "Clang" AND CMAKE_C_COMPILER_VERSION VERSION_GRE
 set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fstack-usage")
 endif ()
 
+# GCC doesn't have disable_tail_calls attribute
+if (CMAKE_C_COMPILER_ID MATCHES "GNU")
+set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-optimize-sibling-calls")
+endif ()
+
 # build out vmlib
 set (WAMR_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../..)
 include (${WAMR_ROOT_DIR}/build-scripts/runtime_lib.cmake)

+ 2 - 2
samples/native-stack-overflow/build.sh

@@ -69,7 +69,7 @@ echo "#################### build wasm apps done"
 
 echo "#################### aot-compile"
 WAMRC=${WAMR_DIR}/wamr-compiler/build/wamrc
-${WAMRC} -o ${OUT_DIR}/wasm-apps/${OUT_FILE}.aot ${OUT_DIR}/wasm-apps/${OUT_FILE}
+${WAMRC} -o ${OUT_DIR}/wasm-apps/${OUT_FILE}.aot --size-level=0 ${OUT_DIR}/wasm-apps/${OUT_FILE}
 
 echo "#################### aot-compile (--bounds-checks=1)"
-${WAMRC} -o ${OUT_DIR}/wasm-apps/${OUT_FILE}.aot.bounds-checks --bounds-checks=1 ${OUT_DIR}/wasm-apps/${OUT_FILE}
+${WAMRC} -o ${OUT_DIR}/wasm-apps/${OUT_FILE}.aot.bounds-checks --size-level=0 --bounds-checks=1 ${OUT_DIR}/wasm-apps/${OUT_FILE}

+ 1 - 1
samples/native-stack-overflow/src/main.c

@@ -114,7 +114,7 @@ main(int argc, char **argv)
            "--------\n");
 
     unsigned int stack;
-    unsigned int prevstack;
+    unsigned int prevstack = 0; /* appease GCC -Wmaybe-uninitialized */
     unsigned int stack_range_start = 0;
     unsigned int stack_range_end = 4096 * 6;
     unsigned int step = 16;

+ 14 - 23
samples/native-stack-overflow/src/native_impl.c

@@ -9,10 +9,6 @@
 #include <stdio.h>
 #include <string.h>
 
-#if defined(__APPLE__)
-#include <Availability.h>
-#endif
-
 #include "wasm_export.h"
 #include "bh_platform.h"
 
@@ -45,9 +41,12 @@ host_consume_stack_and_call_indirect(wasm_exec_env_t exec_env, uint32_t funcidx,
     void *boundary = os_thread_get_stack_boundary();
     void *fp = __builtin_frame_address(0);
     ptrdiff_t diff = fp - boundary;
-    if ((unsigned char *)fp < (unsigned char *)boundary + 1024 * 5) {
-        wasm_runtime_set_exception(wasm_runtime_get_module_inst(exec_env),
-                                   "native stack overflow 2");
+    /*
+     * because this function performs recursive calls depending on
+     * the user input, we don't have an apriori knowledge how much stack
+     * we need. perform the overflow check on each iteration.
+     */
+    if (!wasm_runtime_detect_native_stack_overflow(exec_env)) {
         return 0;
     }
     if (diff > stack) {
@@ -63,27 +62,13 @@ host_consume_stack_and_call_indirect(wasm_exec_env_t exec_env, uint32_t funcidx,
 
 __attribute__((noinline)) static uint32_t
 consume_stack1(wasm_exec_env_t exec_env, void *base, uint32_t stack)
+#if defined(__clang__)
     __attribute__((disable_tail_calls))
+#endif
 {
     void *fp = __builtin_frame_address(0);
     ptrdiff_t diff = (unsigned char *)base - (unsigned char *)fp;
     assert(diff > 0);
-    char buf[16];
-    /*
-     * note: we prefer to use memset_s here because, unlike memset,
-     * memset_s is not allowed to be optimized away.
-     *
-     * memset_s is available for macOS 10.13+ according to:
-     * https://developer.apple.com/documentation/kernel/2876438-memset_s
-     */
-#if defined(__STDC_LIB_EXT1__)                  \
-    || (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) \
-        && __MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_3)
-    memset_s(buf, sizeof(buf), 0, sizeof(buf));
-#else
-#warning memset_s is not available
-    memset(buf, 0, sizeof(buf));
-#endif
     if (diff > stack) {
         return diff;
     }
@@ -93,6 +78,12 @@ consume_stack1(wasm_exec_env_t exec_env, void *base, uint32_t stack)
 uint32_t
 host_consume_stack(wasm_exec_env_t exec_env, uint32_t stack)
 {
+    /*
+     * this function consumes a bit more than "stack" bytes.
+     */
+    if (!wasm_runtime_detect_native_stack_overflow_size(exec_env, 64 + stack)) {
+        return 0;
+    }
     void *base = __builtin_frame_address(0);
     return consume_stack1(exec_env, base, stack);
 }