/*++ Copyright (c) 1995-1998 Microsoft Corporation Module Name: suspend.c Abstract: This module implements CpuSuspendThread, CpuGetContext and CpuSetContext for AMD64. Author: 12-Dec-2001 Samer Arafeh (samera) Revision History: --*/ #define _WOW64CPUAPI_ #ifdef _X86_ #include "amd6432.h" #else #include #include #include #include #include "wow64.h" #include "wow64cpu.h" #include "amd64cpu.h" #endif #include "cpup.h" #include #include ASSERTNAME; ULONG_PTR ia32ShowContext = 0; VOID CpupDebugPrint( IN ULONG_PTR Flags, IN PCHAR Format, ...) { va_list ArgList; int BytesWritten; CHAR Buffer[ 512 ]; if ((ia32ShowContext & Flags) || (Flags == ERRORLOG)) { va_start(ArgList, Format); BytesWritten = _vsnprintf(Buffer, sizeof(Buffer) - 1, Format, ArgList); if (BytesWritten > 0) { DbgPrint(Buffer); } va_end(ArgList); } return; } VOID CpupPrintContext( IN PCHAR str, IN PCPUCONTEXT cpu ) /*++ Routine Description: Print out the ia32 context based on the passed in cpu context Arguments: str - String to print out as a header cpu - Pointer to the per-thread wow64 ia32 context. Return Value: none --*/ { DbgPrint(str); DbgPrint("Context addr(0x%p): EIP=0x%08x\n", &(cpu->Context), cpu->Context.Eip); DbgPrint("Context EAX=0x%08x, EBX=0x%08x, ECX=0x%08x, EDX=0x%08x\n", cpu->Context.Eax, cpu->Context.Ebx, cpu->Context.Ecx, cpu->Context.Edx); DbgPrint("Context ESP=0x%08x, EBP=0x%08x, ESI=0x%08x, EDI=0x%08x\n", cpu->Context.Esp, cpu->Context.Ebp, cpu->Context.Esi, cpu->Context.Edi); try { // // The stack may not yet be fully formed, so don't // let a missing stack cause the process to abort // DbgPrint("Context stack=0x%08x 0x%08x 0x%08x 0x%08x\n", *((PULONG) cpu->Context.Esp), *(((PULONG) cpu->Context.Esp) + 1), *(((PULONG) cpu->Context.Esp) + 2), *(((PULONG) cpu->Context.Esp) + 3)); } except ((GetExceptionCode() == STATUS_ACCESS_VIOLATION)?1:0) { // // Got an access violation, so don't print any of the stack // DbgPrint("Context stack: Can't get stack contents\n"); } DbgPrint("Context EFLAGS=0x%08x\n", cpu->Context.EFlags); } WOW64CPUAPI NTSTATUS CpuSuspendThread( IN HANDLE ThreadHandle, IN HANDLE ProcessHandle, IN PTEB Teb, OUT PULONG PreviousSuspendCount OPTIONAL) /*++ Routine Description: This routine is entered while the target thread is actually suspended, however, it's not known if the target thread is in a consistent state relative to the CPU. Arguments: ThreadHandle - Handle of target thread to suspend ProcessHandle - Handle of target thread's process Teb - Address of the target thread's TEB PreviousSuspendCount - Previous suspend count Return Value: NTSTATUS. --*/ { return STATUS_SUCCESS; } NTSTATUS CpupReadBuffer( IN HANDLE ProcessHandle, IN PVOID Source, OUT PVOID Destination, IN ULONG Size) /*++ Routine Description: This routine setup the arguments for the remoted SuspendThread call. Arguments: ProcessHandle - Target process handle to read data from Source - Target base address to read data from Destination - Address of buffer to receive data read from the specified address space Size - Size of data to read Return Value: NTSTATUS. --*/ { NTSTATUS NtStatus = STATUS_SUCCESS; if (ProcessHandle == NtCurrentProcess ()) { try { RtlCopyMemory (Destination, Source, Size); } except (EXCEPTION_EXECUTE_HANDLER) { NtStatus = GetExceptionCode (); } } else { NtStatus = NtReadVirtualMemory (ProcessHandle, Source, Destination, Size, NULL); } return NtStatus; } NTSTATUS CpupWriteBuffer( IN HANDLE ProcessHandle, IN PVOID Target, IN PVOID Source, IN ULONG Size) /*++ Routine Description: Writes data to memory taken into consideration if the write is cross-process or not Arguments: ProcessHandle - Target process handle to write data into Target - Target base address to write data at Source - Address of contents to write in the specified address space Size - Size of data to write Return Value: NTSTATUS. --*/ { NTSTATUS NtStatus = STATUS_SUCCESS; if (ProcessHandle == NtCurrentProcess ()) { try { RtlCopyMemory (Target, Source, Size); } except (EXCEPTION_EXECUTE_HANDLER) { NtStatus = GetExceptionCode (); } } else { NtStatus = NtWriteVirtualMemory (ProcessHandle, Target, Source, Size, NULL); } return NtStatus; } NTSTATUS GetContextRecord( IN PCPUCONTEXT cpu, IN PCONTEXT ContextAmd64 OPTIONAL, IN OUT PCONTEXT32 Context ) /*++ Routine Description: Retrevies the context record of the specified CPU. This routine updates only the registers that are saved on transition to 64-bit mode, and SHOULD be kept in sync with the thunk-transition code. Arguments: cpu - CPU to retreive the context record for. ContextAmd64 - Full native context. Context - IN/OUT pointer to CONTEXT32 to fill in. Context->ContextFlags should be used to determine how much of the context to copy. Return Value: NTSTATUS --*/ { NTSTATUS NtStatus = STATUS_SUCCESS; ULONG ContextFlags; try { ContextFlags = Context->ContextFlags; // // Get the initial 32-bit context. // // // Caller will fillup Context with the native context and pass ContextAmd64 as NULL. // if (ARGUMENT_PRESENT (ContextAmd64)) { Wow64CtxFromAmd64 (ContextFlags, ContextAmd64, Context); // // If the thread is running 32-bit code at the time of retreiving the context, // the direct conversion is enough. // if (ContextAmd64->SegCs == (KGDT64_R3_CMCODE | RPL_MASK)) { return NtStatus; } } if (ContextFlags & CONTEXT_AMD64) { LOGPRINT((ERRORLOG, "CpuGetContext: Request for amd64 context (0x%x) being FAILED\n", ContextFlags)); ASSERT((ContextFlags & CONTEXT_AMD64) == 0); } if ((ContextFlags & CONTEXT32_CONTROL) == CONTEXT32_CONTROL) { // // control registers // Context->Ebp = cpu->Context.Ebp; Context->Eip = cpu->Context.Eip; Context->SegCs = (KGDT64_R3_CMCODE | RPL_MASK); Context->EFlags = SANITIZE_X86EFLAGS (cpu->Context.EFlags); Context->Esp = cpu->Context.Esp; Context->SegSs = (KGDT64_R3_DATA | RPL_MASK); } if ((ContextFlags & CONTEXT32_INTEGER) == CONTEXT32_INTEGER) { // // i386 integer registers are: // edi, esi, ebx, edx, ecx, eax // Context->Edi = cpu->Context.Edi; Context->Esi = cpu->Context.Esi; Context->Ebx = cpu->Context.Ebx; Context->Edx = cpu->Context.Edx; Context->Ecx = cpu->Context.Ecx; Context->Eax = cpu->Context.Eax; } if ((ContextFlags & CONTEXT32_SEGMENTS) == CONTEXT32_SEGMENTS) { // // i386 segment registers are: // ds, es, fs, gs // And since they are a constant, force them to be the right values // Context->SegDs = (KGDT64_R3_DATA | RPL_MASK); Context->SegEs = (KGDT64_R3_DATA | RPL_MASK); Context->SegFs = (KGDT64_R3_CMTEB | RPL_MASK); Context->SegGs = (KGDT64_R3_DATA | RPL_MASK); } if ((ContextFlags & CONTEXT32_FLOATING_POINT) == CONTEXT32_FLOATING_POINT) { // // floating point (legacy) registers (ST0-ST7) // // // This must have already been done. // } if ((ContextFlags & CONTEXT32_DEBUG_REGISTERS) == CONTEXT32_DEBUG_REGISTERS) { // // Debug registers (Dr0-Dr7) // // // This must have already been done // } if ((ContextFlags & CONTEXT32_EXTENDED_REGISTERS) == CONTEXT32_EXTENDED_REGISTERS) { // // extended floating point registers (XMM0-XMM7) // } } except (EXCEPTION_EXECUTE_HANDLER) { NtStatus = GetExceptionCode (); } if (ia32ShowContext & LOG_CONTEXT_GETSET) { CpupPrintContext ("Getting ia32 context: ", cpu); } return NtStatus; } NTSTATUS CpupGetContext( IN PCONTEXT ContextAmd64, IN OUT PCONTEXT32 Context ) /*++ Routine Description: This routine extracts the context record for the currently executing thread. Arguments: ContextAmd64 - Full native context Context - Context record to fill Return Value: NTSTATUS. --*/ { DECLARE_CPU; return GetContextRecord (cpu, ContextAmd64, Context); } NTSTATUS CpupGetContextThread( IN HANDLE ThreadHandle, IN HANDLE ProcessHandle, IN PTEB Teb, IN OUT PCONTEXT32 Context) /*++ Routine Description: This routine extract the context record of any thread. This is a generic routine. When entered, if the target thread isn't the current thread, then it should be guaranteed that the target thread is suspended at a proper CPU state. Arguments: ThreadHandle - Target thread handle to retreive the context for ProcessHandle - Open handle to the process that the thread runs in Teb - Pointer to the target's thread TEB Context - Context record to fill Return Value: NTSTATUS. --*/ { NTSTATUS NtStatus = STATUS_SUCCESS; CONTEXT ContextEM; PCPUCONTEXT CpuRemoteContext; CPUCONTEXT CpuContext; // // Get the whole native context // ContextEM.ContextFlags = (CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS | CONTEXT_SEGMENTS); NtStatus = NtGetContextThread (ThreadHandle, &ContextEM); if (!NT_SUCCESS(NtStatus)) { LOGPRINT((ERRORLOG, "CpupGetContextThread: NtGetContextThread (%lx) failed - %lx\n", ThreadHandle, NtStatus)); return NtStatus; } // // If we are running 64-bit code, then get the context off the 64-bit // thread stack, which was spilled by the transition code... // if (ContextEM.SegCs != (KGDT64_R3_CMCODE | RPL_MASK)) { LOGPRINT((TRACELOG, "Getting context while thread is executing 64-bit instructions\n")); NtStatus = CpupReadBuffer( ProcessHandle, ((PCHAR)Teb + FIELD_OFFSET(TEB, TlsSlots[WOW64_TLS_CPURESERVED])), &CpuRemoteContext, sizeof(CpuRemoteContext)); if (NT_SUCCESS(NtStatus)) { NtStatus = CpupReadBuffer (ProcessHandle, CpuRemoteContext, &CpuContext, sizeof(CpuContext)); if (!NT_SUCCESS(NtStatus)) { LOGPRINT((ERRORLOG, "CpupGetContextThread: Couldn't read CPU context %lx - %lx\n", CpuRemoteContext, NtStatus)); } } else { LOGPRINT((ERRORLOG, "CpupGetContextThread: Couldn't read CPU context address - %lx\n", NtStatus)); } } // // Get the actual context context for the caller // if (NT_SUCCESS (NtStatus)) { NtStatus = GetContextRecord (&CpuContext, &ContextEM, Context); } return NtStatus; } WOW64CPUAPI NTSTATUS CpuGetContext( IN HANDLE ThreadHandle, IN HANDLE ProcessHandle, IN PTEB Teb, OUT PCONTEXT32 Context) /*++ Routine Description: Extracts the cpu context of the specified thread. When entered, it is guaranteed that the target thread is suspended at a proper CPU state. Arguments: ThreadHandle - Target thread handle to retreive the context for ProcessHandle - Open handle to the process that the thread runs in Teb - Pointer to the target's thread TEB Context - Context record to fill Return Value: NTSTATUS. --*/ { return CpupGetContextThread(ThreadHandle, ProcessHandle, Teb, Context); } NTSTATUS SetContextRecord( IN OUT PCPUCONTEXT cpu, IN OUT PCONTEXT ContextAmd64 OPTIONAL, IN PCONTEXT32 Context, OUT PBOOLEAN UpdateNativeContext ) /*++ Routine Description: Update the CPU's register set for the specified CPU. This routine updates only the registers that are saved on transition to 64-bit mode, and SHOULD be kept in sync with the thunk-transition code. Arguments: cpu - CPU to update its registers Context - Pointer to CONTEXT32 to use. Context->ContextFlags should be used to determine how much of the context to update. ContextAmd64 - Full native context, and will hold the updated 32-bit context. UpdateNativeContext - Updated on return to indicate if a NtSetContextThread call is required. Return Value: NTSTATUS --*/ { NTSTATUS NtStatus = STATUS_SUCCESS; ULONG ContextFlags; ULONG NewContextFlags = 0; BOOLEAN CmMode; try { *UpdateNativeContext = FALSE; ContextFlags = Context->ContextFlags; if (ContextFlags & CONTEXT_AMD64) { LOGPRINT((ERRORLOG, "CpuSetContext: Request with amd64 context (0x%x) FAILED\n", ContextFlags)); ASSERT((ContextFlags & CONTEXT_AMD64) == 0); } // // Caller might pass NativeContext == NULL if they already know what to do. // if (ARGUMENT_PRESENT (ContextAmd64)) { CmMode = (ContextAmd64->SegCs == (KGDT64_R3_CMCODE | RPL_MASK)); Wow64CtxToAmd64 (ContextFlags, Context, ContextAmd64); // // If we are running 32-bit code // if (CmMode == TRUE) { *UpdateNativeContext = TRUE; return NtStatus; } NewContextFlags = 0; } if ((ContextFlags & CONTEXT32_CONTROL) == CONTEXT32_CONTROL) { // // Control registers // cpu->Context.Ebp = Context->Ebp; cpu->Context.Eip = Context->Eip; cpu->Context.SegCs = (KGDT64_R3_CMCODE | RPL_MASK); cpu->Context.EFlags = SANITIZE_X86EFLAGS (Context->EFlags); cpu->Context.Esp = Context->Esp; cpu->Context.SegSs = Context->SegSs; } if ((ContextFlags & CONTEXT32_INTEGER) == CONTEXT32_INTEGER) { // // Integer registers // cpu->Context.Edi = Context->Edi; cpu->Context.Esi = Context->Esi; cpu->Context.Ebx = Context->Ebx; cpu->Context.Edx = Context->Edx; cpu->Context.Ecx = Context->Ecx; cpu->Context.Eax = Context->Eax; } if ((ContextFlags & CONTEXT32_SEGMENTS) == CONTEXT32_SEGMENTS) { // // Segment registers // // // shouldn't be touched // } if ((ContextFlags & CONTEXT32_FLOATING_POINT) == CONTEXT32_FLOATING_POINT) { // // floating point registers // NewContextFlags |= CONTEXT_FLOATING_POINT; } if ((ContextFlags & CONTEXT32_DEBUG_REGISTERS) == CONTEXT32_DEBUG_REGISTERS) { // // debug registers (Dr0-Dr7) // NewContextFlags |= CONTEXT_DEBUG_REGISTERS; } if ((ContextFlags & CONTEXT32_EXTENDED_REGISTERS) == CONTEXT32_EXTENDED_REGISTERS) { // // extended floating point registers (ST0-ST7) // NewContextFlags |= CONTEXT_FLOATING_POINT; // // Save the extended registers so that the trap frame may restore them. // The system will ALWAYS scrub XMM0-XMM5 on return from system calls. // RtlCopyMemory (cpu->Context.ExtendedRegisters, Context->ExtendedRegisters, sizeof (Context->ExtendedRegisters)); } // // Whatever they passed in before, it's an X86 context now... // cpu->Context.ContextFlags = ContextFlags; } except (EXCEPTION_EXECUTE_HANDLER) { NtStatus = GetExceptionCode(); } if (ia32ShowContext & LOG_CONTEXT_GETSET) { CpupPrintContext("Setting ia32 context: ", cpu); } if (NewContextFlags != 0) { if (ARGUMENT_PRESENT (ContextAmd64)) { ContextAmd64->ContextFlags = NewContextFlags; *UpdateNativeContext = TRUE; } } return NtStatus; } NTSTATUS CpupSetContextThread( IN HANDLE ThreadHandle, IN HANDLE ProcessHandle, IN PTEB Teb, IN OUT PCONTEXT32 Context) /*++ Routine Description: This routine sets the context record of any thread. This is a generic routine. When entered, if the target thread isn't the currently executing thread, then it should be guaranteed that the target thread is suspended at a proper CPU state. Arguments: ThreadHandle - Target thread handle to retreive the context for ProcessHandle - Open handle to the process that the thread runs in Teb - Pointer to the target's thread TEB Context - Context record to set Return Value: NTSTATUS. --*/ { NTSTATUS NtStatus = STATUS_SUCCESS; CONTEXT ContextEM; PCPUCONTEXT CpuRemoteContext; CPUCONTEXT CpuContext; BOOLEAN CmMode; BOOLEAN UpdateNativeContext; // // Get the whole native context // ContextEM.ContextFlags = (CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS | CONTEXT_SEGMENTS); NtStatus = NtGetContextThread (ThreadHandle, &ContextEM); if (!NT_SUCCESS(NtStatus)) { LOGPRINT((ERRORLOG, "CpupGetContextThread: NtGetContextThread (%lx) failed - %lx\n", ThreadHandle, NtStatus)); return NtStatus; } CmMode = (ContextEM.SegCs == (KGDT64_R3_CMCODE | RPL_MASK)); // // If we are running 64-bit code, make sure to update the cpu context off the // 64-bit thread stack... // if (CmMode == FALSE) { LOGPRINT((TRACELOG, "Setting context while thread is executing 64-bit instructions\n")); NtStatus = CpupReadBuffer (ProcessHandle, ((PCHAR)Teb + FIELD_OFFSET(TEB, TlsSlots[WOW64_TLS_CPURESERVED])), &CpuRemoteContext, sizeof(CpuRemoteContext)); if (NT_SUCCESS(NtStatus)) { NtStatus = CpupReadBuffer (ProcessHandle, CpuRemoteContext, &CpuContext, sizeof(CpuContext)); } else { LOGPRINT((ERRORLOG, "CpupSetContextThread: Couldn't read CPU context address - %lx\n", NtStatus)); } } // // We are ready to set the context now // if (NT_SUCCESS (NtStatus)) { NtStatus = SetContextRecord (&CpuContext, &ContextEM, Context, &UpdateNativeContext); if (NT_SUCCESS (NtStatus)) { if (CmMode == FALSE) { // // If the call is coming thru Wow64, then restore the volatile XMMI and integer registers on the next // tranisition to compatibility mode // if (ThreadHandle == NtCurrentThread ()) { if (((Context->ContextFlags & CONTEXT32_EXTENDED_REGISTERS) == CONTEXT32_EXTENDED_REGISTERS) || ((Context->ContextFlags & CONTEXT32_INTEGER) == CONTEXT32_INTEGER)) { CpuContext.TrapFrameFlags = TRAP_FRAME_RESTORE_VOLATILE; } } NtStatus = CpupWriteBuffer(ProcessHandle, CpuRemoteContext, &CpuContext, sizeof(CpuContext)); if (!NT_SUCCESS (NtStatus)) { LOGPRINT((ERRORLOG, "CpupSetContextThread: Couldn't write remote context %lx - %lx\n", CpuRemoteContext, NtStatus)); } } // // Set the context ultimately. This shouldn't change except the FP and debug // state if the thread is executing in 64-bit (long) mode. // if ((NT_SUCCESS (NtStatus)) && (UpdateNativeContext == TRUE)) { NtStatus = NtSetContextThread (ThreadHandle, &ContextEM); } } else { LOGPRINT((ERRORLOG, "CpupSetContextThread: Couldn't update native context %lx - %lx\n", &ContextEM, NtStatus)); } } return NtStatus; } WOW64CPUAPI NTSTATUS CpuSetContext( IN HANDLE ThreadHandle, IN HANDLE ProcessHandle, IN PTEB Teb, PCONTEXT32 Context) /*++ Routine Description: Sets the cpu context for the specified thread. When entered, if the target thread isn't the currently executing thread, then it is guaranteed that the target thread is suspended at a proper CPU state. Arguments: ThreadHandle - Target thread handle to retreive the context for ProcessHandle - Open handle to the process that the thread runs in Teb - Pointer to the target's thread TEB Context - Context record to set Return Value: NTSTATUS. --*/ { return CpupSetContextThread(ThreadHandle, ProcessHandle, Teb, Context); }