[PATCH 0/2] MR9944: ntdll: Stop walk in RtlWalkFrameChain() if there is no function entry on x64.
From: Paul Gofman <pgofman@codeweavers.com> --- dlls/ntdll/tests/exception.c | 86 ++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/dlls/ntdll/tests/exception.c b/dlls/ntdll/tests/exception.c index 30b60398f4b..226e446989f 100644 --- a/dlls/ntdll/tests/exception.c +++ b/dlls/ntdll/tests/exception.c @@ -6364,6 +6364,91 @@ static void test_base_init_thunk_unwind(void) ok( count == 2, "got count %lu.\n", count ); } +static void test_backtrace_without_runtime_function_func( BOOL todo ) +{ + unsigned int count; + void *addrs[256]; + + memset( addrs, 0xcc, sizeof(addrs) ); + count = RtlCaptureStackBackTrace( 0, 256, addrs, NULL ); + todo_wine_if(todo) ok( count == 2, "got %d.\n", count ); + ok( (char *)addrs[1] == (char *)code_mem + 22, "got %p, code_mem %p.\n", addrs[1], code_mem ); + todo_wine_if(todo) ok( addrs[2] == (void *)0xcccccccccccccccc, "got %p.\n", addrs[2]); + + memset( addrs, 0xcc, sizeof(addrs) ); + count = RtlWalkFrameChain( addrs, 256, 0 ); + todo_wine_if(todo) ok( count == 2, "got %d.\n", count ); + ok( (char *)addrs[1] == (char *)code_mem + 22, "got %p, code_mem %p.\n", addrs[1], code_mem ); + todo_wine_if(todo) ok( addrs[2] == (void *)0xcccccccccccccccc, "got %p.\n", addrs[2]); +} + +static RUNTIME_FUNCTION * CALLBACK test_backtrace_without_runtime_function_callback( DWORD_PTR pc, void *context ) +{ + ++*(unsigned int *)context; + return NULL; +} + +static void test_backtrace_without_runtime_function(void) +{ + static const BYTE unwind_info[] = + { + 1, /* version + flags */ + 0, /* prolog size */ + 0, /* opcode count */ + 0, /* frame reg */ + 0x00, 0x00, 0x00, 0x00, /* handler */ + }; + + static BYTE test_code[] = + { + 0xb8, 0xef, 0xbe, 0xad, 0xde, /* mov $0xdeadbeef,%eax */ + 0x50, /* pushq *rax */ + 0x50, /* pushq *rax */ + 0x50, /* pushq *rax */ + 0x50, /* pushq *rax */ + 0x50, /* pushq *rax */ + /* test_backtrace_function offset 12 */ + 0x48, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, /* movabs test_backtrace_without_runtime_function_func, %rax */ + 0xff, 0xd0, /* callq *%rax */ + /* offset 22 */ + 0x48, 0x83, 0xc4, 0x28, /* addq $0x28,%rsp */ + 0xc3, /* ret */ + }; + IMAGE_AMD64_RUNTIME_FUNCTION_ENTRY rt_func; + void (WINAPI *func)( BOOL todo ) = code_mem; + ULONG_PTR table; + unsigned int count; + BOOL ret; + + *(void **)((char *)test_code + 12) = test_backtrace_without_runtime_function_func; + memcpy( code_mem, test_code, sizeof(test_code) ); + func = code_mem; + + func( TRUE ); + + memcpy( (char *)code_mem + 0x1000, unwind_info, sizeof(unwind_info) ); + rt_func.BeginAddress = 0; + rt_func.EndAddress = sizeof(test_code); + rt_func.UnwindData = 0x1000; + ret = RtlAddFunctionTable( &rt_func, 1, (ULONG_PTR)code_mem ); + ok(ret, "RtlAddFunctionTable failed.\n"); + + func( TRUE ); + + ret = RtlDeleteFunctionTable( &rt_func ); + ok( ret, "RtlDeleteFunctionTable failed.\n" ); + + table = (ULONG_PTR)code_mem | 0x3; + count = 0; + ret = RtlInstallFunctionTableCallback( table, (ULONG_PTR)code_mem, 2048, + &test_backtrace_without_runtime_function_callback, (PVOID*)&count, NULL ); + ok( ret, "RtlInstallFunctionTableCallback failed.\n" ); + func( TRUE ); + todo_wine ok( !count, "got %d.\n", count ); + ret = pRtlDeleteFunctionTable( (PRUNTIME_FUNCTION)table ); + ok( ret, "RtlDeleteFunctionTable failed.\n" ); +} + #elif defined(__arm__) static void test_thread_context(void) @@ -12661,6 +12746,7 @@ START_TEST(exception) test_direct_syscalls(); test_single_step_address(); test_base_init_thunk_unwind(); + test_backtrace_without_runtime_function(); #elif defined(__aarch64__) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9944
From: Paul Gofman <pgofman@codeweavers.com> --- dlls/ntdll/signal_x86_64.c | 1 + dlls/ntdll/tests/exception.c | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/dlls/ntdll/signal_x86_64.c b/dlls/ntdll/signal_x86_64.c index 3d4bd23e8ff..0404433db6c 100644 --- a/dlls/ntdll/signal_x86_64.c +++ b/dlls/ntdll/signal_x86_64.c @@ -885,6 +885,7 @@ ULONG WINAPI RtlWalkFrameChain( void **buffer, ULONG count, ULONG flags ) for (i = 0; i < count; i++) { func = RtlLookupFunctionEntry( context.Rip, &base, &table ); + if (!func) break; if (RtlVirtualUnwind2( UNW_FLAG_NHANDLER, base, context.Rip, func, &context, NULL, &data, &frame, NULL, NULL, NULL, &handler, 0 )) break; diff --git a/dlls/ntdll/tests/exception.c b/dlls/ntdll/tests/exception.c index 226e446989f..f4c68d074d4 100644 --- a/dlls/ntdll/tests/exception.c +++ b/dlls/ntdll/tests/exception.c @@ -6424,7 +6424,7 @@ static void test_backtrace_without_runtime_function(void) memcpy( code_mem, test_code, sizeof(test_code) ); func = code_mem; - func( TRUE ); + func( FALSE ); memcpy( (char *)code_mem + 0x1000, unwind_info, sizeof(unwind_info) ); rt_func.BeginAddress = 0; @@ -6443,7 +6443,7 @@ static void test_backtrace_without_runtime_function(void) ret = RtlInstallFunctionTableCallback( table, (ULONG_PTR)code_mem, 2048, &test_backtrace_without_runtime_function_callback, (PVOID*)&count, NULL ); ok( ret, "RtlInstallFunctionTableCallback failed.\n" ); - func( TRUE ); + func( FALSE ); todo_wine ok( !count, "got %d.\n", count ); ret = pRtlDeleteFunctionTable( (PRUNTIME_FUNCTION)table ); ok( ret, "RtlDeleteFunctionTable failed.\n" ); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9944
That is needed for Rec Room which does RtlCaptureStackBackTrace() from inside obfuscated code without runtime info. That is not about RtlCaptureStackBackTrace() crashing (it wasn't actually here). The game does own checks on return addresses and fails those on bogus addresses returned from unwinding through the functions without unwind info (treated as leaf functions). I think there is some logic in this stack walk behaviour as only leaf functions can be missing unwind info and leaf functions are not supposed to call anything and thus appear in stack traces. However, this behaviour breaks RtlCaptureStackBackTrace() when, e. g., there is an access violation in leaf function and RtlCaptureStackBackTrace() is called from exception handler (I tested that separately by forcing access violation in a function without runtime function and capturing stack trace from VEH handler). The weird thing is that, as far as tests go, Windows doesn't seem to take dynamic runtime functions into account and only minds the unwind info from PE module (and stops back trace even if there is dynamic unwind info available for it). I didn't implement that part (still considering any runtime function to be enough to continue back trace), reasoning that this weird behaviour will only complicate things without clear benefit (until something depends specifically on that part). -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9944#note_127662
participants (2)
-
Paul Gofman -
Paul Gofman (@gofman)