diff --git a/src/sentry_os.c b/src/sentry_os.c index d7cc2d58d6..fefcf01ebb 100644 --- a/src/sentry_os.c +++ b/src/sentry_os.c @@ -263,6 +263,30 @@ sentry_set_thread_stack_guarantee(uint32_t stack_guarantee_in_bytes) return 1; } +// Resolves the handler stack size (in KiB) from the `SENTRY_HANDLER_STACK_SIZE` +// environment variable, falling back to the compile-time default. This is +// called from `DllMain` via `sentry__set_default_thread_stack_guarantee`, so +// it must avoid CRT (`getenv`, `strtod`, ...) and only use kernel32 exports. +static size_t +sentry__handler_stack_size_kib(void) +{ + char buf[12]; + DWORD n = GetEnvironmentVariableA( + "SENTRY_HANDLER_STACK_SIZE", buf, sizeof(buf)); + if (n == 0 || n >= sizeof(buf)) { + return SENTRY_HANDLER_STACK_SIZE; + } + size_t val = 0; + for (DWORD i = 0; i < n; i++) { + if (buf[i] < '0' || buf[i] > '9') { + return SENTRY_HANDLER_STACK_SIZE; + } + val = val * 10 + (size_t)(buf[i] - '0'); + } + return (val > 0 && val <= 0xFFFFFFFFu / 1024) ? val + : SENTRY_HANDLER_STACK_SIZE; +} + void sentry__set_default_thread_stack_guarantee(void) { @@ -270,8 +294,9 @@ sentry__set_default_thread_stack_guarantee(void) return; } + const size_t stack_size_kib = sentry__handler_stack_size_kib(); const unsigned long expected_stack_guarantee - = SENTRY_HANDLER_STACK_SIZE * 1024; + = (unsigned long)stack_size_kib * 1024; DWORD thread_id = GetThreadId(GetCurrentThread()); ULONG_PTR high = 0; ULONG_PTR low = 0; @@ -284,8 +309,7 @@ sentry__set_default_thread_stack_guarantee(void) SENTRY_WARNF( "Cannot set handler stack guarantee of %zuKiB for thread %lu " "(stack reserve: %zuKiB, expected factor: %zux, actual: %.2fx)", - (size_t)SENTRY_HANDLER_STACK_SIZE, thread_id, - thread_stack_reserve / 1024, + stack_size_kib, thread_id, thread_stack_reserve / 1024, (size_t)SENTRY_THREAD_STACK_GUARANTEE_FACTOR, expected_stack_reserve / (double)expected_stack_guarantee); return; diff --git a/tests/test_integration_crashpad.py b/tests/test_integration_crashpad.py index a2dca88174..adaf3ccff7 100644 --- a/tests/test_integration_crashpad.py +++ b/tests/test_integration_crashpad.py @@ -440,18 +440,18 @@ def test_crashpad_dumping_crash(cmake, httpserver, run_args, build_args): @pytest.mark.parametrize( - "build_args", + "stack_size", [ - ({}), # uses default of 64KiB + None, # uses default of 64KiB pytest.param( - {"SENTRY_HANDLER_STACK_SIZE": "16"}, + "16", marks=pytest.mark.skipif( sys.platform != "win32", reason="handler stack size parameterization tests stack guarantee on windows only", ), ), pytest.param( - {"SENTRY_HANDLER_STACK_SIZE": "32"}, + "32", marks=pytest.mark.skipif( sys.platform != "win32", reason="handler stack size parameterization tests stack guarantee on windows only", @@ -459,11 +459,12 @@ def test_crashpad_dumping_crash(cmake, httpserver, run_args, build_args): ), ], ) -def test_crashpad_dumping_stack_overflow(cmake, httpserver, build_args): - build_args.update({"SENTRY_BACKEND": "crashpad"}) - tmp_path = cmake(["sentry_example"], build_args) +def test_crashpad_dumping_stack_overflow(cmake, httpserver, stack_size): + tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "crashpad"}) env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver)) + if stack_size: + env["SENTRY_HANDLER_STACK_SIZE"] = stack_size httpserver.expect_oneshot_request("/api/123456/minidump/").respond_with_data("OK") httpserver.expect_request("/api/123456/envelope/").respond_with_data("OK") diff --git a/tests/test_integration_stdout.py b/tests/test_integration_stdout.py index ecbdac1a5c..83ff40656a 100644 --- a/tests/test_integration_stdout.py +++ b/tests/test_integration_stdout.py @@ -158,13 +158,13 @@ def test_multi_process(cmake): assert len(runs) == 0 -def run_stdout_for(backend, cmake, example_args, build_args=None): +def run_stdout_for(backend, cmake, example_args, build_args=None, env=None): build_args = dict(build_args or {}) build_args.update({"SENTRY_BACKEND": backend, "SENTRY_TRANSPORT": "none"}) tmp_path = cmake(["sentry_example"], build_args) - run(tmp_path, "sentry_example", example_args, expect_failure=True) + run(tmp_path, "sentry_example", example_args, expect_failure=True, env=env) return tmp_path, check_output(tmp_path, "sentry_example", ["stdout", "no-setup"]) @@ -243,12 +243,12 @@ def test_inproc_crash_stdout_before_send_and_on_crash(cmake): @pytest.mark.parametrize( - "build_args", + "stack_size", [ - ({}), # uses default of 64KiB + None, # uses default of 64KiB # no test with 16KiB since `inproc` fails with that handler stack size pytest.param( - {"SENTRY_HANDLER_STACK_SIZE": "32"}, + "32", marks=pytest.mark.skipif( sys.platform != "win32", reason="handler stack size parameterization tests stack guarantee on windows only", @@ -256,9 +256,12 @@ def test_inproc_crash_stdout_before_send_and_on_crash(cmake): ), ], ) -def test_inproc_stack_overflow_stdout(cmake, build_args): +def test_inproc_stack_overflow_stdout(cmake, stack_size): + env = dict(os.environ) + if stack_size: + env["SENTRY_HANDLER_STACK_SIZE"] = stack_size tmp_path, output = run_stdout_for( - "inproc", cmake, ["log", "attachment", "stack-overflow"], build_args + "inproc", cmake, ["log", "attachment", "stack-overflow"], env=env ) envelope = Envelope.deserialize(output) @@ -328,18 +331,18 @@ def test_breakpad_crash_stdout_before_send_and_on_crash(cmake): @pytest.mark.parametrize( - "build_args", + "stack_size", [ - ({}), # uses default of 64KiB + None, # uses default of 64KiB pytest.param( - {"SENTRY_HANDLER_STACK_SIZE": "16"}, + "16", marks=pytest.mark.skipif( sys.platform != "win32", reason="handler stack size parameterization tests stack guarantee on windows only", ), ), pytest.param( - {"SENTRY_HANDLER_STACK_SIZE": "32"}, + "32", marks=pytest.mark.skipif( sys.platform != "win32", reason="handler stack size parameterization tests stack guarantee on windows only", @@ -348,9 +351,12 @@ def test_breakpad_crash_stdout_before_send_and_on_crash(cmake): ], ) @pytest.mark.skipif(not has_breakpad, reason="test needs breakpad backend") -def test_breakpad_stack_overflow_stdout(cmake, build_args): +def test_breakpad_stack_overflow_stdout(cmake, stack_size): + env = dict(os.environ) + if stack_size: + env["SENTRY_HANDLER_STACK_SIZE"] = stack_size tmp_path, output = run_stdout_for( - "breakpad", cmake, ["attachment", "stack-overflow"], build_args + "breakpad", cmake, ["attachment", "stack-overflow"], env=env ) envelope = Envelope.deserialize(output)