From: "Rafał Harabień" Subject: [PATCH] ntdll: Allow getting/setting x86_64 context of x86 processes in wine64 Message-Id: Date: Wed, 24 Feb 2021 23:49:44 +0100 WoW64 process has two separate contexts: - 32 bit context used most of the time (e.g. by application code) - 64 bit context used by system when it quits x86 emulation and jumps to the kernel code A notable exception are debug registers - their state is shared. Some debuggers make use of that fact and sets/gets debug registers of 32 bit processes using 64 bit thread context. This change adds support for setting and getting debug registers using 64 bit thread context. Getting other registers is allowed too and will return values from 32 bit thread context. This change fixes hardware breakpoints in IDA 7.0 disassembler (64 bit app) when debugging 32 bit applications. Signed-off-by: Rafał Harabień --- dlls/ntdll/unix/signal_x86_64.c | 62 +++++++++++++++++++++++++++++++++ server/thread.c | 24 +++++++++++-- 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/dlls/ntdll/unix/signal_x86_64.c b/dlls/ntdll/unix/signal_x86_64.c index 22a5d34e06c..4c1aa1bfc74 100644 --- a/dlls/ntdll/unix/signal_x86_64.c +++ b/dlls/ntdll/unix/signal_x86_64.c @@ -1709,6 +1709,68 @@ NTSTATUS context_to_server( context_t *to, const CONTEXT *from ) */ NTSTATUS context_from_server( CONTEXT *to, const context_t *from ) { + if (from->cpu == CPU_x86) + { + // WoW64 process has two separate contexts: one 32 bit used when executing normal user code and one 64 bit + // used by WoW64 internals for talking with the kernel. Debug registers are shared between those contexts + // and some debuggers make use of that fact so handle getting them here. Other registers could be set to + // anything but to keep things sane lets copy them from 32 bit context. + to->ContextFlags = CONTEXT_AMD64; + if (from->flags & SERVER_CTX_CONTROL) + { + to->ContextFlags |= CONTEXT_CONTROL; + to->Rbp = from->ctl.i386_regs.ebp; + to->Rip = from->ctl.i386_regs.eip; + to->Rsp = from->ctl.i386_regs.esp; + to->SegCs = from->ctl.i386_regs.cs; + to->SegSs = from->ctl.i386_regs.ss; + to->EFlags = from->ctl.i386_regs.eflags; + } + + if (from->flags & SERVER_CTX_INTEGER) + { + to->ContextFlags |= CONTEXT_INTEGER; + to->Rax = from->integer.i386_regs.eax; + to->Rcx = from->integer.i386_regs.ecx; + to->Rdx = from->integer.i386_regs.edx; + to->Rbx = from->integer.i386_regs.ebx; + to->Rsi = from->integer.i386_regs.esi; + to->Rdi = from->integer.i386_regs.edi; + to->R8 = 0; + to->R9 = 0; + to->R10 = 0; + to->R11 = 0; + to->R12 = 0; + to->R13 = 0; + to->R14 = 0; + to->R15 = 0; + } + if (from->flags & SERVER_CTX_SEGMENTS) + { + to->ContextFlags |= CONTEXT_SEGMENTS; + to->SegDs = from->seg.i386_regs.ds; + to->SegEs = from->seg.i386_regs.es; + to->SegFs = from->seg.i386_regs.fs; + to->SegGs = from->seg.i386_regs.gs; + } + if (from->flags & SERVER_CTX_FLOATING_POINT) + { + to->ContextFlags |= CONTEXT_FLOATING_POINT; + memset(&to->u.FltSave, 0, sizeof(to->u.FltSave)); + } + if (from->flags & SERVER_CTX_DEBUG_REGISTERS) + { + to->ContextFlags |= CONTEXT_DEBUG_REGISTERS; + to->Dr0 = from->debug.i386_regs.dr0; + to->Dr1 = from->debug.i386_regs.dr1; + to->Dr2 = from->debug.i386_regs.dr2; + to->Dr3 = from->debug.i386_regs.dr3; + to->Dr6 = from->debug.i386_regs.dr6; + to->Dr7 = from->debug.i386_regs.dr7; + } + return STATUS_SUCCESS; + } + if (from->cpu != CPU_x86_64) return STATUS_INVALID_PARAMETER; to->ContextFlags = CONTEXT_AMD64 | (to->ContextFlags & 0x40); diff --git a/server/thread.c b/server/thread.c index 3cee717e169..99e82d113fa 100644 --- a/server/thread.c +++ b/server/thread.c @@ -1887,8 +1887,7 @@ DECL_HANDLER(set_thread_context) reply->self = (thread == current); if (thread->state == TERMINATED) set_error( STATUS_UNSUCCESSFUL ); - else if (context->cpu != thread->process->cpu) set_error( STATUS_INVALID_PARAMETER ); - else + else if (context->cpu == thread->process->cpu) { unsigned int system_flags = get_context_system_regs(context->cpu) & context->flags; @@ -1900,6 +1899,27 @@ DECL_HANDLER(set_thread_context) thread->context->regs.flags |= context->flags; } } + else if (context->cpu == CPU_x86_64 && thread->process->cpu == CPU_x86) + { + // WoW64 process has two separate contexts: one 32 bit used when executing normal user code and one 64 bit + // used by WoW64 internals for talking with the kernel. Debug registers are shared between those contexts + // and some debuggers make use of that fact so handle setting them here. + unsigned int system_flags = get_context_system_regs( context->cpu ) & context->flags; + if (system_flags) + { + set_thread_context( thread, context, system_flags ); + if (thread->context && !get_error()) + { + thread->context->regs.debug.i386_regs.dr0 = context->debug.x86_64_regs.dr0; + thread->context->regs.debug.i386_regs.dr1 = context->debug.x86_64_regs.dr1; + thread->context->regs.debug.i386_regs.dr2 = context->debug.x86_64_regs.dr2; + thread->context->regs.debug.i386_regs.dr3 = context->debug.x86_64_regs.dr3; + thread->context->regs.debug.i386_regs.dr6 = context->debug.x86_64_regs.dr6; + thread->context->regs.debug.i386_regs.dr7 = context->debug.x86_64_regs.dr7; + } + } + } + else set_error( STATUS_INVALID_PARAMETER ); release_object( thread ); } -- 2.25.1