call_user_exception_dispatcher() on x64 sets thread stack to Rsp from the provided context and allocates stack layout for KiUserExceptionDispatcher relative to that one. However, the stack provided by the userspace upon calling NtRaiseException may reside below Rsp value provided in the context. And that is always the case when plain RaiseException() is called while BeingDebugged PEB flag is set (so that NtRaiseException is used). The problem is that the original context is overridden with memmove (also potentially when setting xstate), and frame->rbp gets wrong value breaking consequent unwind.
From: Paul Gofman pgofman@codeweavers.com
--- dlls/ntdll/unix/signal_x86_64.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/dlls/ntdll/unix/signal_x86_64.c b/dlls/ntdll/unix/signal_x86_64.c index 97d3a009c03..2875ead70d4 100644 --- a/dlls/ntdll/unix/signal_x86_64.c +++ b/dlls/ntdll/unix/signal_x86_64.c @@ -1554,15 +1554,15 @@ NTSTATUS call_user_exception_dispatcher( EXCEPTION_RECORD *rec, CONTEXT *context rsp = (rsp - sizeof(XSTATE)) & ~63; stack = (struct stack_layout *)rsp - 1; assert( !((ULONG_PTR)stack->xstate & 63) ); + memmove( &stack->context, context, sizeof(*context) ); context_init_xstate( &stack->context, stack->xstate ); memcpy( stack->xstate, &frame->xstate, sizeof(frame->xstate) ); } - - memmove( &stack->context, context, sizeof(*context) ); + else memmove( &stack->context, context, sizeof(*context) ); stack->rec = *rec; /* fix up instruction pointer in context for EXCEPTION_BREAKPOINT */ if (stack->rec.ExceptionCode == EXCEPTION_BREAKPOINT) stack->context.Rip--; - frame->rbp = context->Rbp; + frame->rbp = stack->context.Rbp; frame->rsp = (ULONG64)stack; frame->rip = (ULONG64)pKiUserExceptionDispatcher; frame->restore_flags |= CONTEXT_CONTROL;