diff options
Diffstat (limited to 'src/unwinder/amd64')
-rw-r--r-- | src/unwinder/amd64/.gitmirror | 1 | ||||
-rw-r--r-- | src/unwinder/amd64/dbs_stack_x64.cpp | 1499 | ||||
-rw-r--r-- | src/unwinder/amd64/unwinder_amd64.cpp | 1858 | ||||
-rw-r--r-- | src/unwinder/amd64/unwinder_amd64.h | 70 |
4 files changed, 3428 insertions, 0 deletions
diff --git a/src/unwinder/amd64/.gitmirror b/src/unwinder/amd64/.gitmirror new file mode 100644 index 0000000000..f507630f94 --- /dev/null +++ b/src/unwinder/amd64/.gitmirror @@ -0,0 +1 @@ +Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror.
\ No newline at end of file diff --git a/src/unwinder/amd64/dbs_stack_x64.cpp b/src/unwinder/amd64/dbs_stack_x64.cpp new file mode 100644 index 0000000000..b6ad2f904c --- /dev/null +++ b/src/unwinder/amd64/dbs_stack_x64.cpp @@ -0,0 +1,1499 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +//---------------------------------------------------------------------------- +// + +// +// Stack unwinding implementation for x64. +// + +// +//---------------------------------------------------------------------------- + +#include "pch.cpp" +#pragma hdrstop + +//---------------------------------------------------------------------------- +// +// Copied OS code. +// +// This must be kept in sync with the system unwinder. +// base\ntos\rtl\amd64\exdsptch.c +// +//---------------------------------------------------------------------------- + +// +// Lookup table providing the number of slots used by each unwind code. +// + +UCHAR +DbsX64StackUnwinder::s_UnwindOpSlotTable[] = +{ + 1, // UWOP_PUSH_NONVOL + 2, // UWOP_ALLOC_LARGE (or 3, special cased in lookup code) + 1, // UWOP_ALLOC_SMALL + 1, // UWOP_SET_FPREG + 2, // UWOP_SAVE_NONVOL + 3, // UWOP_SAVE_NONVOL_FAR + 2, // UWOP_SAVE_XMM + 3, // UWOP_SAVE_XMM_FAR + 2, // UWOP_SAVE_XMM128 + 3, // UWOP_SAVE_XMM128_FAR + 1 // UWOP_PUSH_MACHFRAME +}; + +// +// ****** temp - defin elsewhere ****** +// + +#define SIZE64_PREFIX 0x48 +#define ADD_IMM8_OP 0x83 +#define ADD_IMM32_OP 0x81 +#define JMP_IMM8_OP 0xeb +#define JMP_IMM32_OP 0xe9 +#define JMP_IND_OP 0xff +#define LEA_OP 0x8d +#define REP_PREFIX 0xf3 +#define POP_OP 0x58 +#define RET_OP 0xc3 +#define RET_OP_2 0xc2 + +#define IS_REX_PREFIX(x) (((x) & 0xf0) == 0x40) + +HRESULT +DbsX64StackUnwinder::UnwindPrologue( + __in ULONG64 ImageBase, + __in ULONG64 ControlPc, + __in ULONG64 FrameBase, + __in _PIMAGE_RUNTIME_FUNCTION_ENTRY FunctionEntry, + __inout PAMD64_CONTEXT ContextRecord + ) + +/*++ + +Routine Description: + + This function processes unwind codes and reverses the state change + effects of a prologue. If the specified unwind information contains + chained unwind information, then that prologue is unwound recursively. + As the prologue is unwound state changes are recorded in the specified + context structure and optionally in the specified context pointers + structures. + +Arguments: + + ImageBase - Supplies the base address of the image that contains the + function being unwound. + + ControlPc - Supplies the address where control left the specified + function. + + FrameBase - Supplies the base of the stack frame subject function stack + frame. + + FunctionEntry - Supplies the address of the function table entry for the + specified function. + + ContextRecord - Supplies the address of a context record. + +--*/ + +{ + + HRESULT Status = E_UNEXPECTED; + ULONG64 FloatingAddress; + PAMD64_M128 FloatingRegister; + ULONG FrameOffset; + ULONG Index; + ULONG64 IntegerAddress; + PULONG64 IntegerRegister; + BOOLEAN MachineFrame; + ULONG OpInfo; + ULONG PrologOffset; + ULONG64 ReturnAddress; + ULONG64 StackAddress; + ULONG64 UnwindInfoBuffer[32]; + PAMD64_UNWIND_INFO UnwindInfo; + ULONG UnwindOp; + + // + // Process the unwind codes. + // + + FloatingRegister = &ContextRecord->Xmm0; + IntegerRegister = &ContextRecord->Rax; + Index = 0; + MachineFrame = FALSE; + PrologOffset = (ULONG)(ControlPc - (FunctionEntry->BeginAddress + ImageBase)); + + m_Services->Status(1, "Prol: RIP %I64X, 0x%X bytes in function at %I64X\n", + ControlPc, PrologOffset, + FunctionEntry->BeginAddress + ImageBase); + m_Services->Status(1, "Prol: Read unwind info at %I64X\n", + FunctionEntry->UnwindInfoAddress + ImageBase); + + if ((Status = + GetUnwindInfo(ImageBase, FunctionEntry->UnwindInfoAddress, + false, + UnwindInfoBuffer, sizeof(UnwindInfoBuffer), + (PVOID*)&UnwindInfo)) != S_OK) { + m_Services->Status(1, "Prol: Unable to read unwind info\n"); + return Status; + } + + m_Services->Status(1, " Unwind info has 0x%X codes\n", + UnwindInfo->CountOfCodes); + + while (Index < UnwindInfo->CountOfCodes) { + + m_Services->Status(1, " %02X: Code %X offs %03X, RSP %I64X\n", + Index, UnwindInfo->UnwindCode[Index].UnwindOp, + UnwindInfo->UnwindCode[Index].CodeOffset, + ContextRecord->Rsp); + + // + // If the prologue offset is greater than the next unwind code offset, + // then simulate the effect of the unwind code. + // + + UnwindOp = UnwindInfo->UnwindCode[Index].UnwindOp; + if (UnwindOp > AMD64_UWOP_PUSH_MACHFRAME) { + m_Services->Status(1, "Prol: Invalid unwind op %X at index %X\n", + UnwindOp, Index); + goto Fail; + } + + OpInfo = UnwindInfo->UnwindCode[Index].OpInfo; + if (PrologOffset >= UnwindInfo->UnwindCode[Index].CodeOffset) { + switch (UnwindOp) { + + // + // Push nonvolatile integer register. + // + // The operation information is the register number of the + // register than was pushed. + // + + case AMD64_UWOP_PUSH_NONVOL: + IntegerAddress = ContextRecord->Rsp; + if ((Status = m_Services-> + ReadAllMemory(IntegerAddress, + &IntegerRegister[OpInfo], + sizeof(ULONG64))) != S_OK) { + m_Services->Status(1, "Prol: Op %X memory " + "read failed at %I64X\n", + UnwindOp, IntegerAddress); + goto Fail; + } + + ContextRecord->Rsp += 8; + break; + + // + // Allocate a large sized area on the stack. + // + // The operation information determines if the size is + // 16- or 32-bits. + // + + case AMD64_UWOP_ALLOC_LARGE: + Index += 1; + FrameOffset = UnwindInfo->UnwindCode[Index].FrameOffset; + if (OpInfo != 0) { + Index += 1; + FrameOffset += (UnwindInfo->UnwindCode[Index].FrameOffset << 16); + } else { + // The 16-bit form is scaled. + FrameOffset *= 8; + } + + ContextRecord->Rsp += FrameOffset; + break; + + // + // Allocate a small sized area on the stack. + // + // The operation information is the size of the unscaled + // allocation size (8 is the scale factor) minus 8. + // + + case AMD64_UWOP_ALLOC_SMALL: + ContextRecord->Rsp += (OpInfo * 8) + 8; + break; + + // + // Establish the the frame pointer register. + // + // The operation information is not used. + // + + case AMD64_UWOP_SET_FPREG: + ContextRecord->Rsp = IntegerRegister[UnwindInfo->FrameRegister]; + ContextRecord->Rsp -= UnwindInfo->FrameOffset * 16; + break; + + // + // Save nonvolatile integer register on the stack using a + // 16-bit displacment. + // + // The operation information is the register number. + // + + case AMD64_UWOP_SAVE_NONVOL: + Index += 1; + FrameOffset = UnwindInfo->UnwindCode[Index].FrameOffset * 8; + IntegerAddress = FrameBase + FrameOffset; + if ((Status = m_Services-> + ReadAllMemory(IntegerAddress, + &IntegerRegister[OpInfo], + sizeof(ULONG64))) != S_OK) { + m_Services->Status(1, "Prol: Op %X memory read " + "failed at %I64X\n", + UnwindOp, IntegerAddress); + goto Fail; + } + break; + + // + // Save nonvolatile integer register on the stack using a + // 32-bit displacment. + // + // The operation information is the register number. + // + + case AMD64_UWOP_SAVE_NONVOL_FAR: + Index += 2; + FrameOffset = UnwindInfo->UnwindCode[Index - 1].FrameOffset; + FrameOffset += UnwindInfo->UnwindCode[Index].FrameOffset << 16; + IntegerAddress = FrameBase + FrameOffset; + if ((Status = m_Services-> + ReadAllMemory(IntegerAddress, + &IntegerRegister[OpInfo], + sizeof(ULONG64))) != S_OK) { + m_Services->Status(1, "Prol: Op %X memory read " + "failed at %I64X\n", + UnwindOp, IntegerAddress); + goto Fail; + } + break; + + // + // Save a nonvolatile XMM(64) register on the stack using a + // 16-bit displacement. + // + // The operation information is the register number. + // + + case AMD64_UWOP_SAVE_XMM: + Index += 1; + FrameOffset = UnwindInfo->UnwindCode[Index].FrameOffset * 8; + FloatingAddress = FrameBase + FrameOffset; + FloatingRegister[OpInfo].High = 0; + if ((Status = m_Services-> + ReadAllMemory(FloatingAddress, + &FloatingRegister[OpInfo].Low, + sizeof(ULONG64))) != S_OK) { + m_Services->Status(1, "Prol: Op %X memory read " + "failed at %I64X\n", + UnwindOp, FloatingAddress); + goto Fail; + } + break; + + // + // Save a nonvolatile XMM(64) register on the stack using a + // 32-bit displacement. + // + // The operation information is the register number. + // + + case AMD64_UWOP_SAVE_XMM_FAR: + Index += 2; + FrameOffset = UnwindInfo->UnwindCode[Index - 1].FrameOffset; + FrameOffset += UnwindInfo->UnwindCode[Index].FrameOffset << 16; + FloatingAddress = FrameBase + FrameOffset; + FloatingRegister[OpInfo].High = 0; + if ((Status = m_Services-> + ReadAllMemory(FloatingAddress, + &FloatingRegister[OpInfo].Low, + sizeof(ULONG64))) != S_OK) { + m_Services->Status(1, "Prol: Op %X memory read " + "failed at %I64X\n", + UnwindOp, FloatingAddress); + goto Fail; + } + break; + + // + // Save a nonvolatile XMM(128) register on the stack using a + // 16-bit displacement. + // + // The operation information is the register number. + // + + case AMD64_UWOP_SAVE_XMM128: + Index += 1; + FrameOffset = UnwindInfo->UnwindCode[Index].FrameOffset * 16; + FloatingAddress = FrameBase + FrameOffset; + if ((Status = m_Services-> + ReadAllMemory(FloatingAddress, + &FloatingRegister[OpInfo], + sizeof(AMD64_M128))) != S_OK) { + m_Services->Status(1, "Prol: Op %X memory read " + "failed at %I64X\n", + UnwindOp, FloatingAddress); + goto Fail; + } + break; + + // + // Save a nonvolatile XMM(128) register on the stack using a + // 32-bit displacement. + // + // The operation information is the register number. + // + + case AMD64_UWOP_SAVE_XMM128_FAR: + Index += 2; + FrameOffset = UnwindInfo->UnwindCode[Index - 1].FrameOffset; + FrameOffset += UnwindInfo->UnwindCode[Index].FrameOffset << 16; + FloatingAddress = FrameBase + FrameOffset; + if ((Status = m_Services-> + ReadAllMemory(FloatingAddress, + &FloatingRegister[OpInfo], + sizeof(AMD64_M128))) != S_OK) { + m_Services->Status(1, "Prol: Op %X memory read " + "failed at %I64X\n", + UnwindOp, FloatingAddress); + goto Fail; + } + break; + + // + // Push a machine frame on the stack. + // + // The operation information determines whether the machine + // frame contains an error code or not. + // + + case AMD64_UWOP_PUSH_MACHFRAME: + MachineFrame = TRUE; + ReturnAddress = ContextRecord->Rsp; + StackAddress = ContextRecord->Rsp + (3 * 8); + if (OpInfo != 0) { + ReturnAddress += 8; + StackAddress += 8; + } + + m_RestartFrame = true; + m_TrapAddr = ReturnAddress - + FIELD_OFFSET(AMD64_KTRAP_FRAME, Rip); + + if ((Status = m_Services-> + ReadAllMemory(ReturnAddress, + &ContextRecord->Rip, + sizeof(ULONG64))) != S_OK) { + m_Services->Status(1, "Prol: Op %X memory " + "read 1 failed at %I64X\n", + UnwindOp, ReturnAddress); + goto Fail; + } + if ((Status = m_Services-> + ReadAllMemory(StackAddress, + &ContextRecord->Rsp, + sizeof(ULONG64))) != S_OK) { + m_Services->Status(1, "Prol: Op %X memory " + "read 2 failed at %I64X\n", + UnwindOp, StackAddress); + goto Fail; + } + break; + + // + // Unused codes. + // + + default: + break; + } + + Index += 1; + + } else { + + // + // Skip this unwind operation by advancing the slot index by the + // number of slots consumed by this operation. + // + + Index += s_UnwindOpSlotTable[UnwindOp]; + + // + // Special case any unwind operations that can consume a variable + // number of slots. + // + + switch (UnwindOp) { + + // + // A non-zero operation information indicates that an + // additional slot is consumed. + // + + case AMD64_UWOP_ALLOC_LARGE: + if (OpInfo != 0) { + Index += 1; + } + + break; + + // + // No other special cases. + // + + default: + break; + } + } + } + + // + // If chained unwind information is specified, then recursively unwind + // the chained information. Otherwise, determine the return address if + // a machine frame was not encountered during the scan of the unwind + // codes. + // + + if ((UnwindInfo->Flags & AMD64_UNW_FLAG_CHAININFO) != 0) { + + _PIMAGE_RUNTIME_FUNCTION_ENTRY ChainEntry; + + Index = UnwindInfo->CountOfCodes; + if ((Index & 1) != 0) { + Index += 1; + } + + // GetUnwindInfo looks for CHAININFO and reads + // the trailing RUNTIME_FUNCTION so we can just + // directly use the data sitting in UnwindInfo. + ChainEntry = (_PIMAGE_RUNTIME_FUNCTION_ENTRY) + &UnwindInfo->UnwindCode[Index]; + + m_Services->Status(1, " Chain with entry at %I64X\n", + FunctionEntry->UnwindInfoAddress + ImageBase + + (ULONG64)((PUCHAR)&UnwindInfo->UnwindCode[Index] - + (PUCHAR)UnwindInfo)); + + Status = UnwindPrologue(ImageBase, + ControlPc, + FrameBase, + ChainEntry, + ContextRecord); + + FreeUnwindInfo(UnwindInfo, UnwindInfoBuffer); + return Status; + + } else { + FreeUnwindInfo(UnwindInfo, UnwindInfoBuffer); + + if (MachineFrame == FALSE) { + if ((Status = m_Services-> + ReadAllMemory(ContextRecord->Rsp, + &ContextRecord->Rip, + sizeof(ULONG64))) != S_OK) { + return Status; + } + ContextRecord->Rsp += 8; + } + + m_Services->Status(1, "Prol: Returning with RIP %I64X, RSP %I64X\n", + ContextRecord->Rip, ContextRecord->Rsp); + return S_OK; + } + + Fail: + FreeUnwindInfo(UnwindInfo, UnwindInfoBuffer); + m_Services->Status(1, "Prol: Unwind failed, 0x%08X\n", Status); + return Status; +} + +HRESULT +DbsX64StackUnwinder::VirtualUnwind( + __in ULONG64 ImageBase, + __in ULONG64 ControlPc, + __in _PIMAGE_RUNTIME_FUNCTION_ENTRY FunctionEntry, + __inout PAMD64_CONTEXT ContextRecord, + __out PULONG64 EstablisherFrame + ) + +/*++ + +Routine Description: + + This function virtually unwinds the specified function by executing its + prologue code backward or its epilogue code forward. + + If a context pointers record is specified, then the address where each + nonvolatile registers is restored from is recorded in the appropriate + element of the context pointers record. + +Arguments: + + ImageBase - Supplies the base address of the image that contains the + function being unwound. + + ControlPc - Supplies the address where control left the specified + function. + + FunctionEntry - Supplies the address of the function table entry for the + specified function. + + ContextRecord - Supplies the address of a context record. + + EstablisherFrame - Supplies a pointer to a variable that receives the + the establisher frame pointer value. + +--*/ + +{ + + HRESULT Status; + ULONG64 BranchTarget; + LONG Displacement; + ULONG FrameRegister; + ULONG Index; + LOGICAL InEpilogue; + PULONG64 IntegerRegister; + PUCHAR NextByte; + _PIMAGE_RUNTIME_FUNCTION_ENTRY PrimaryFunctionEntry; + ULONG PrologOffset; + ULONG RegisterNumber; + PAMD64_UNWIND_INFO UnwindInfo; + ULONG64 UnwindInfoBuffer[8]; + ULONG Done; + UCHAR InstrBuffer[32]; + ULONG InstrBytes; + ULONG Bytes; + ULONG UnwindFrameReg; + + // + // If the specified function does not use a frame pointer, then the + // establisher frame is the contents of the stack pointer. This may + // not actually be the real establisher frame if control left the + // function from within the prologue. In this case the establisher + // frame may be not required since control has not actually entered + // the function and prologue entries cannot refer to the establisher + // frame before it has been established, i.e., if it has not been + // established, then no save unwind codes should be encountered during + // the unwind operation. + // + // If the specified function uses a frame pointer and control left the + // function outside of the prologue or the unwind information contains + // a chained information structure, then the establisher frame is the + // contents of the frame pointer. + // + // If the specified function uses a frame pointer and control left the + // function from within the prologue, then the set frame pointer unwind + // code must be looked up in the unwind codes to detetermine if the + // contents of the stack pointer or the contents of the frame pointer + // should be used for the establisher frame. This may not actually be + // the real establisher frame. In this case the establisher frame may + // not be required since control has not actually entered the function + // and prologue entries cannot refer to the establisher frame before it + // has been established, i.e., if it has not been established, then no + // save unwind codes should be encountered during the unwind operation. + // + // N.B. The correctness of these assumptions is based on the ordering of + // unwind codes. + // + + if ((Status = GetUnwindInfo(ImageBase, FunctionEntry->UnwindInfoAddress, + true, + UnwindInfoBuffer, sizeof(UnwindInfoBuffer), + (PVOID*)&UnwindInfo)) != S_OK) { + return Status; + } + + PrologOffset = (ULONG)(ControlPc - (FunctionEntry->BeginAddress + ImageBase)); + UnwindFrameReg = UnwindInfo->FrameRegister; + if (UnwindFrameReg == 0) { + *EstablisherFrame = ContextRecord->Rsp; + + } else if ((PrologOffset >= UnwindInfo->SizeOfProlog) || + ((UnwindInfo->Flags & AMD64_UNW_FLAG_CHAININFO) != 0)) { + *EstablisherFrame = (&ContextRecord->Rax)[UnwindFrameReg]; + *EstablisherFrame -= UnwindInfo->FrameOffset * 16; + + } else { + + // Read all the data. + if ((Status = GetUnwindInfo(ImageBase, + FunctionEntry->UnwindInfoAddress, + false, + UnwindInfoBuffer, + sizeof(UnwindInfoBuffer), + (PVOID*)&UnwindInfo)) != S_OK) { + return Status; + } + + Index = 0; + while (Index < UnwindInfo->CountOfCodes) { + if (UnwindInfo->UnwindCode[Index].UnwindOp == AMD64_UWOP_SET_FPREG) { + break; + } + + Index += 1; + } + + if (PrologOffset >= UnwindInfo->UnwindCode[Index].CodeOffset) { + *EstablisherFrame = (&ContextRecord->Rax)[UnwindFrameReg]; + *EstablisherFrame -= UnwindInfo->FrameOffset * 16; + + } else { + *EstablisherFrame = ContextRecord->Rsp; + } + + FreeUnwindInfo(UnwindInfo, UnwindInfoBuffer); + } + + if ((Status = m_Services-> + ReadMemory(ControlPc, InstrBuffer, sizeof(InstrBuffer), + &InstrBytes)) != S_OK) { + m_Services->Status(1, "Unable to read instruction stream at %I64X\n", + ControlPc); + + // We need the code to look for epilogue ops. + // It's very rare to be stopped in an epilogue when + // getting a stack trace, so if we can't read the + // code just assume we aren't in an epilogue. + InstrBytes = 0; + } + + // + // If the point at which control left the specified function is in an + // epilogue, then emulate the execution of the epilogue forward and + // return no exception handler. + // + + IntegerRegister = &ContextRecord->Rax; + NextByte = InstrBuffer; + Bytes = InstrBytes; + + // + // Check for one of: + // + // add rsp, imm8 + // or + // add rsp, imm32 + // or + // lea rsp, -disp8[fp] + // or + // lea rsp, -disp32[fp] + // + + if (Bytes >= 4 && + (NextByte[0] == SIZE64_PREFIX) && + (NextByte[1] == ADD_IMM8_OP) && + (NextByte[2] == 0xc4)) { + + // + // add rsp, imm8. + // + + NextByte += 4; + Bytes -= 4; + + } else if (Bytes >= 7 && + (NextByte[0] == SIZE64_PREFIX) && + (NextByte[1] == ADD_IMM32_OP) && + (NextByte[2] == 0xc4)) { + + // + // add rsp, imm32. + // + + NextByte += 7; + Bytes -= 7; + + } else if (Bytes >= 4 && + ((NextByte[0] & 0xf8) == SIZE64_PREFIX) && + (NextByte[1] == LEA_OP)) { + + FrameRegister = ((NextByte[0] & 0x7) << 3) | (NextByte[2] & 0x7); + if ((FrameRegister != 0) && + (FrameRegister == UnwindFrameReg)) { + if ((NextByte[2] & 0xf8) == 0x60) { + + // + // lea rsp, disp8[fp]. + // + + NextByte += 4; + Bytes -= 4; + + } else if (Bytes >= 7 && + (NextByte[2] &0xf8) == 0xa0) { + + // + // lea rsp, disp32[fp]. + // + + NextByte += 7; + Bytes -= 7; + } + } + } + + // + // Check for any number of: + // + // pop nonvolatile-integer-register[0..15]. + // + + while (TRUE) { + if (Bytes >= 1 && + (NextByte[0] & 0xf8) == POP_OP) { + NextByte += 1; + Bytes -= 1; + + } else if (Bytes >= 2 && + IS_REX_PREFIX(NextByte[0]) && + ((NextByte[1] & 0xf8) == POP_OP)) { + + NextByte += 2; + Bytes -= 2; + + } else { + break; + } + } + + // + // If the next instruction is a return or an appropriate jump, then + // control is currently in an epilogue and execution of the epilogue + // should be emulated. Otherwise, execution is not in an epilogue and + // the prologue should be unwound. + // + + InEpilogue = FALSE; + if ((Bytes >= 1 && + ((NextByte[0] == RET_OP) || + (NextByte[0] == RET_OP_2))) || + (Bytes >= 2 && + ((NextByte[0] == REP_PREFIX) && (NextByte[1] == RET_OP)))) { + + // + // A return is an unambiguous indication of an epilogue. + // + + InEpilogue = TRUE; + + } else if ((Bytes >= 2 && NextByte[0] == JMP_IMM8_OP) || + (Bytes >= 5 && NextByte[0] == JMP_IMM32_OP)) { + + // + // An unconditional branch to a target that is equal to the start of + // or outside of this routine is logically a call to another function. + // + + BranchTarget = (ULONG64)(NextByte - InstrBuffer) + ControlPc - ImageBase; + if (NextByte[0] == JMP_IMM8_OP) { + BranchTarget += 2 + (CHAR)NextByte[1]; + } else { + BranchTarget += 5 + *((LONG UNALIGNED *)&NextByte[1]); + } + + // + // Determine whether the branch target refers to code within this + // function. If not, then it is an epilogue indicator. + // + // A branch to the start of self implies a recursive call, so + // is treated as an epilogue. + // + + if (BranchTarget < FunctionEntry->BeginAddress || + BranchTarget >= FunctionEntry->EndAddress) { + + _IMAGE_RUNTIME_FUNCTION_ENTRY PrimaryEntryBuffer; + + // + // The branch target is outside of the region described by + // this function entry. See whether it is contained within + // an indirect function entry associated with this same + // function. + // + // If not, then the branch target really is outside of + // this function. + // + + PrimaryFunctionEntry = + SameFunction(FunctionEntry, + ImageBase, + BranchTarget + ImageBase, + &PrimaryEntryBuffer); + + if ((PrimaryFunctionEntry == NULL) || + (BranchTarget == PrimaryFunctionEntry->BeginAddress)) { + + InEpilogue = TRUE; + } + + } else if ((BranchTarget == FunctionEntry->BeginAddress) && + ((UnwindInfo->Flags & AMD64_UNW_FLAG_CHAININFO) == 0)) { + + InEpilogue = TRUE; + } + + } else if (Bytes >= 2 && + (NextByte[0] == JMP_IND_OP) && (NextByte[1] == 0x25)) { + + // + // An unconditional jump indirect. + // + // This is a jmp outside of the function, probably a tail call + // to an import function. + // + + InEpilogue = TRUE; + + } else if (Bytes >= 3 && + ((NextByte[0] & 0xf8) == SIZE64_PREFIX) && + (NextByte[1] == 0xff) && + (NextByte[2] & 0x38) == 0x20) { + + // + // This is an indirect jump opcode: 0x48 0xff /4. The 64-bit + // flag (REX.W) is always redundant here, so its presence is + // overloaded to indicate a branch out of the function - a tail + // call. + // + // Such an opcode is an unambiguous epilogue indication. + // + + InEpilogue = TRUE; + } + + if (InEpilogue != FALSE) { + NextByte = InstrBuffer; + Bytes = InstrBytes; + + // + // Emulate one of (if any): + // + // add rsp, imm8 + // or + // add rsp, imm32 + // or + // lea rsp, disp8[frame-register] + // or + // lea rsp, disp32[frame-register] + // + + if (Bytes >= 1 && + (NextByte[0] & 0xf8) == SIZE64_PREFIX) { + + if (Bytes >= 4 && + NextByte[1] == ADD_IMM8_OP) { + + // + // add rsp, imm8. + // + + ContextRecord->Rsp += (CHAR)NextByte[3]; + NextByte += 4; + Bytes -= 4; + + } else if (Bytes >= 7 && + NextByte[1] == ADD_IMM32_OP) { + + // + // add rsp, imm32. + // + + Displacement = NextByte[3] | (NextByte[4] << 8); + Displacement |= (NextByte[5] << 16) | (NextByte[6] << 24); + ContextRecord->Rsp += Displacement; + NextByte += 7; + Bytes -= 7; + + } else if (Bytes >= 4 && + NextByte[1] == LEA_OP) { + if ((NextByte[2] & 0xf8) == 0x60) { + + // + // lea rsp, disp8[frame-register]. + // + + ContextRecord->Rsp = IntegerRegister[FrameRegister]; + ContextRecord->Rsp += (CHAR)NextByte[3]; + NextByte += 4; + Bytes -= 4; + + } else if (Bytes >= 7 && + (NextByte[2] & 0xf8) == 0xa0) { + + // + // lea rsp, disp32[frame-register]. + // + + Displacement = NextByte[3] | (NextByte[4] << 8); + Displacement |= (NextByte[5] << 16) | (NextByte[6] << 24); + ContextRecord->Rsp = IntegerRegister[FrameRegister]; + ContextRecord->Rsp += Displacement; + NextByte += 7; + Bytes -= 7; + } + } + } + + // + // Emulate any number of (if any): + // + // pop nonvolatile-integer-register. + // + + while (TRUE) { + if (Bytes >= 1 && + (NextByte[0] & 0xf8) == POP_OP) { + + // + // pop nonvolatile-integer-register[0..7] + // + + RegisterNumber = NextByte[0] & 0x7; + if ((Status = m_Services-> + ReadAllMemory(ContextRecord->Rsp, + &IntegerRegister[RegisterNumber], + sizeof(ULONG64))) != S_OK) { + m_Services->Status(1, "Unable to read stack at %I64X\n", + ContextRecord->Rsp); + return Status; + } + ContextRecord->Rsp += 8; + NextByte += 1; + Bytes -= 1; + + } else if (Bytes >= 2 && + IS_REX_PREFIX(NextByte[0]) && + (NextByte[1] & 0xf8) == POP_OP) { + + // + // pop nonvolatile-integer-register[8..15] + // + + RegisterNumber = ((NextByte[0] & 1) << 3) | (NextByte[1] & 0x7); + if ((Status = m_Services-> + ReadAllMemory(ContextRecord->Rsp, + &IntegerRegister[RegisterNumber], + sizeof(ULONG64))) != S_OK) { + m_Services->Status(1, "Unable to read stack at %I64X\n", + ContextRecord->Rsp); + return Status; + } + ContextRecord->Rsp += 8; + NextByte += 2; + Bytes -= 2; + + } else { + break; + } + } + + // + // Emulate return and return null exception handler. + // + // Note: this instruction might in fact be a jmp, however + // we want to emulate a return regardless. + // + + if ((Status = m_Services-> + ReadAllMemory(ContextRecord->Rsp, + &ContextRecord->Rip, + sizeof(ULONG64))) != S_OK) { + m_Services->Status(1, "Unable to read stack at %I64X\n", + ContextRecord->Rsp); + return Status; + } + ContextRecord->Rsp += 8; + return S_OK; + } + + // + // Control left the specified function outside an epilogue. Unwind the + // subject function and any chained unwind information. + // + + return UnwindPrologue(ImageBase, + ControlPc, + *EstablisherFrame, + FunctionEntry, + ContextRecord); +} + +ULONG64 +DbsX64StackUnwinder::LookupPrimaryUnwindInfo( + __in _PIMAGE_RUNTIME_FUNCTION_ENTRY FunctionEntry, + __in ULONG64 ImageBase, + __out _PIMAGE_RUNTIME_FUNCTION_ENTRY PrimaryEntry + ) + +/*++ + +Routine Description: + + This function determines whether the supplied function entry is a primary + function entry or a chained function entry. If it is a chained function + entry, the unwind information associated with the primary function entry + is returned. + +Arguments: + + FunctionEntry - Supplies a pointer to the function entry for which the + associated primary function entry will be located. + + ImageBase - Supplies the base address of the image containing the + supplied function entry. + + PrimaryEntry - Supplies the address of a variable that receives a pointer + to the primary function entry. + +Return Value: + + A pointer to the unwind information for the primary function entry is + returned as the function value. + +--*/ + +{ + + ULONG Index; + ULONG64 UnwindInfoBuffer[32]; + PAMD64_UNWIND_INFO UnwindInfo; + ULONG UnwindRel; + ULONG64 UnwindAbs; + + // + // Locate the unwind information and determine whether it is chained. + // If the unwind information is chained, then locate the parent function + // entry and loop again. + // + + UnwindRel = FunctionEntry->UnwindInfoAddress; + // Copy the function entry before it becomes invalid. + *PrimaryEntry = *FunctionEntry; + + do { + UnwindAbs = ImageBase + UnwindRel; + if (GetUnwindInfo(ImageBase, UnwindRel, + false, + UnwindInfoBuffer, sizeof(UnwindInfoBuffer), + (PVOID*)&UnwindInfo) != S_OK || + (UnwindInfo->Flags & AMD64_UNW_FLAG_CHAININFO) == 0) { + break; + } + + Index = UnwindInfo->CountOfCodes; + if ((Index & 1) != 0) { + Index += 1; + } + + FunctionEntry = (_PIMAGE_RUNTIME_FUNCTION_ENTRY) + &UnwindInfo->UnwindCode[Index]; + UnwindRel = FunctionEntry->UnwindInfoAddress; + + // Copy the function entry before it becomes invalid. + *PrimaryEntry = *FunctionEntry; + + FreeUnwindInfo(UnwindInfo, UnwindInfoBuffer); + + } while (TRUE); + + return UnwindAbs; +} + +_PIMAGE_RUNTIME_FUNCTION_ENTRY +DbsX64StackUnwinder::SameFunction( + __in _PIMAGE_RUNTIME_FUNCTION_ENTRY FunctionEntry, + __in ULONG64 ImageBase, + __in ULONG64 ControlPc, + __out _PIMAGE_RUNTIME_FUNCTION_ENTRY FunctionReturnBuffer + ) + +/*++ + +Routine Description: + + This function determines whether the address supplied by ControlPc lies + anywhere within the function associated with FunctionEntry. + +Arguments: + + FunctionEntry - Supplies a pointer to a function entry (primary or chained) + associated with the function. + + ImageBase - Supplies the base address of the image containing the supplied + function entry. + + ControlPc - Supplies the address that will be tested for inclusion within + the function associated with FunctionEntry. + +Return Value: + + If the address of the unwind information for the specified function is + equal to the address of the unwind information for the control PC, then + a pointer to a function table entry that describes the primary function + table entry is returned as the function value. Otherwise, NULL is returned. + +--*/ + +{ + + _IMAGE_RUNTIME_FUNCTION_ENTRY TargetFunctionEntry; + ULONG64 TargetImageBase; + ULONG64 UnwindInfo1; + ULONG64 UnwindInfo2; + + // + // Find the unwind information referenced by the primary function entry + // associated with the specified function entry. + // + + UnwindInfo1 = LookupPrimaryUnwindInfo(FunctionEntry, ImageBase, + FunctionReturnBuffer); + + // + // Determine the function entry containing the control Pc and similarly + // resolve it's primary function entry. + // + + if (m_Services->GetModuleBase(ControlPc, &TargetImageBase) != S_OK || + m_Services->GetFunctionEntry(ControlPc, + &TargetFunctionEntry, + sizeof(TargetFunctionEntry)) != S_OK) { + return NULL; + } + + UnwindInfo2 = LookupPrimaryUnwindInfo(&TargetFunctionEntry, + TargetImageBase, + FunctionReturnBuffer); + + // + // If the address of the two sets of unwind information are equal, then + // return the address of the primary function entry. Otherwise, return + // NULL. + // + + if (UnwindInfo1 == UnwindInfo2) { + return FunctionReturnBuffer; + + } else { + return NULL; + } +} + +//---------------------------------------------------------------------------- +// +// DbsX64StackUnwinder. +// +//---------------------------------------------------------------------------- + +#define DBHX64_SAVE_TRAP(_DbhFrame) ((_DbhFrame)->Reserved[0]) + +// +// Flags word. +// + +#define DBHX64_IS_RESTART_FLAG (0x1UI64) + +#define DBHX64_GET_IS_RESTART(_DbhFrame) \ + ((((_DbhFrame)->Reserved[2]) & DBHX64_IS_RESTART_FLAG) != 0) +#define DBHX64_SET_IS_RESTART(_DbhFrame, _IsRestart) \ + ((_DbhFrame)->Reserved[2] = \ + (((_DbhFrame)->Reserved[2]) & ~DBHX64_IS_RESTART_FLAG) | \ + ((_IsRestart) ? DBHX64_IS_RESTART_FLAG : 0)) + +DbsX64StackUnwinder::DbsX64StackUnwinder(__in_opt DbsStackServices* Services) + : DbsStackUnwinder(Services, "x64", IMAGE_FILE_MACHINE_AMD64, + sizeof(m_Context), + sizeof(_IMAGE_RUNTIME_FUNCTION_ENTRY), + sizeof(AMD64_UNWIND_INFO), 16, 8, 1) +{ + m_ContextBuffer = &m_Context; +} + +HRESULT +DbsX64StackUnwinder::Unwind(void) +{ + HRESULT Status; + + ClearUnwindDerived(); + + if (SUCCEEDED(Status = BaseUnwind())) + { + return Status; + } + + // + // Unable to do a normal unwind, so check for + // alternate transitions like kernel/user boundaries. + // If this fails just return the original error + // as that's more likely to be interesting. + // + + DWORD64 ImageBase; + _IMAGE_RUNTIME_FUNCTION_ENTRY FunctionEntry; + DWORD64 StackPointer; + + if (UnwindNtKernelCallback(&ImageBase, + &FunctionEntry, + sizeof(FunctionEntry), + &StackPointer) != S_OK) + { + return Status; + } + + m_InstructionPointer = ImageBase + FunctionEntry.BeginAddress; + m_CallPointer = m_InstructionPointer; + m_Context.Rip = m_InstructionPointer; + m_StackPointer = StackPointer; + m_Context.Rsp = m_StackPointer; + m_RestartFrame = true; + + return S_OK; +} + +DWORD +DbsX64StackUnwinder:: +GetFullUnwindInfoSize(__in PVOID InfoHeader) +{ + PAMD64_UNWIND_INFO UnwindInfo = (PAMD64_UNWIND_INFO)InfoHeader; + + DWORD UnwindInfoSize = FIELD_OFFSET(AMD64_UNWIND_INFO, UnwindCode) + + UnwindInfo->CountOfCodes * sizeof(AMD64_UNWIND_CODE); + + // An extra alignment code and function entry may be added on to handle + // the chained info case where the chain function entry is just + // beyond the end of the normal code array. + if ((UnwindInfo->Flags & AMD64_UNW_FLAG_CHAININFO) != 0) + { + if ((UnwindInfo->CountOfCodes & 1) != 0) + { + UnwindInfoSize += sizeof(AMD64_UNWIND_CODE); + } + UnwindInfoSize += sizeof(_IMAGE_RUNTIME_FUNCTION_ENTRY); + } + + return UnwindInfoSize; +} + +HRESULT +DbsX64StackUnwinder::DbhStart(__inout LPSTACKFRAME64 StackFrame, + __in DWORD DbhVersion, + __in_bcount(DbhStorageBytes) PVOID DbhStorage, + __in DWORD DbhStorageBytes, + __inout PVOID Context) +{ + HRESULT Status; + + if ((StackFrame->AddrPC.Offset && + StackFrame->AddrPC.Mode != AddrModeFlat) || + (StackFrame->AddrStack.Offset && + StackFrame->AddrStack.Mode != AddrModeFlat) || + (StackFrame->AddrFrame.Offset && + StackFrame->AddrFrame.Mode != AddrModeFlat)) + { + return E_INVALIDARG; + } + + if ((Status = DbsStackUnwinder:: + DbhStart(StackFrame, DbhVersion, DbhStorage, DbhStorageBytes, + Context)) != S_OK) + { + return Status; + } + + // dbghelp doesn't give a context size so we + // have to assume the buffer is large enough. + memcpy(&m_Context, Context, sizeof(m_Context)); + + // + // Override context values from the stack frame if necessary. + // + + if (StackFrame->AddrPC.Offset) + { + m_Context.Rip = StackFrame->AddrPC.Offset; + } + if (StackFrame->AddrStack.Offset) + { + m_Context.Rsp = StackFrame->AddrStack.Offset; + } + if (StackFrame->AddrFrame.Offset) + { + m_Context.Rbp = StackFrame->AddrFrame.Offset; + } + UpdateAbstractPointers(); + m_CallPointer = m_InstructionPointer; + + SetRestart(); + return S_OK; +} + +HRESULT +DbsX64StackUnwinder:: +DbhContinue(__inout LPSTACKFRAME64 StackFrame, + __in DWORD DbhVersion, + __in_bcount(DbhStorageBytes) PVOID DbhStorage, + __in DWORD DbhStorageBytes, + __inout PVOID Context) +{ + HRESULT Status; + + if ((Status = DbsStackUnwinder:: + DbhContinue(StackFrame, DbhVersion, + DbhStorage, DbhStorageBytes, + Context)) != S_OK) + { + return Status; + } + + if (DBHX64_GET_IS_RESTART(StackFrame)) + { + m_RestartFrame = true; + // The base DbhContinue always assumes it + // isn't a restart frame, so override it. + m_CallPointer = m_InstructionPointer; + } + + return Status; +} + +HRESULT +DbsX64StackUnwinder::DbhUpdatePreUnwind(__inout LPSTACKFRAME64 StackFrame) +{ + HRESULT Status; + + if ((Status = DbsStackUnwinder::DbhUpdatePreUnwind(StackFrame)) != S_OK) + { + return Status; + } + + DBHX64_SET_IS_RESTART(StackFrame, m_RestartFrame); + return S_OK; +} + +HRESULT +DbsX64StackUnwinder::DbhUpdatePostUnwind(__inout LPSTACKFRAME64 StackFrame, + __in HRESULT UnwindStatus) +{ + HRESULT Status; + + if ((Status = DbsStackUnwinder::DbhUpdatePostUnwind(StackFrame, + UnwindStatus)) != S_OK) + { + return Status; + } + + // The frame pointer is an artificial value set + // to a pointer below the return address. This + // matches an RBP-chain style of frame while + // also allowing easy access to the return + // address and homed arguments above it. + StackFrame->AddrFrame.Offset = m_FramePointer; + + DBHX64_SAVE_TRAP(StackFrame) = m_TrapAddr; + return S_OK; +} + +void +DbsX64StackUnwinder::UpdateAbstractPointers(void) +{ + m_InstructionPointer = m_Context.Rip; + m_StackPointer = m_Context.Rsp; + m_FramePointer = m_Context.Rbp; +} + +HRESULT +DbsX64StackUnwinder::BaseUnwind(void) +{ + HRESULT Status; + _IMAGE_RUNTIME_FUNCTION_ENTRY FunctionEntry; + + Status = m_Services->GetFunctionEntry(m_CallPointer, + &FunctionEntry, + sizeof(FunctionEntry)); + if (Status == S_OK) + { + DWORD64 ImageBase; + DWORD64 EstablisherFrame; + + // + // The return value coming out of mainCRTStartup is set by some + // run-time routine to be 0; this serves to cause an error if someone + // actually does a return from the mainCRTStartup frame. + // + + if ((Status = m_Services-> + GetModuleBase(m_Context.Rip, &ImageBase)) != S_OK || + (Status = VirtualUnwind(ImageBase, + m_Context.Rip, + &FunctionEntry, + &m_Context, + &EstablisherFrame)) != S_OK) + { + return Status; + } + + DWORD64 OldIp = m_InstructionPointer; + + UpdateAbstractPointers(); + UpdateCallPointer(); + m_FramePointer = m_StackPointer - 2 * sizeof(DWORD64); + + // Check for end frame. + if (m_Context.Rip == 0 || + (m_Context.Rip == OldIp && + EstablisherFrame == m_Context.Rsp)) + { + return S_FALSE; + } + } + else if (Status == E_NOINTERFACE) + { + // + // If there's no function entry for a function + // we assume that it's a leaf and that RSP points + // directly to the return address. + // + + m_Services->Status(1, "Leaf %I64X RSP %I64X\n", + m_Context.Rip, m_Context.Rsp); + + if ((Status = m_Services-> + ReadAllMemory(m_Context.Rsp, &m_Context.Rip, + sizeof(m_Context.Rip))) != S_OK) + { + return Status; + } + + // Update the context values to what they should be in + // the caller. + m_Context.Rsp += sizeof(m_Context.Rip); + UpdateAbstractPointers(); + m_CallPointer = m_InstructionPointer - 1; + m_FramePointer = m_StackPointer - 2 * sizeof(DWORD64); + } + else + { + return Status; + } + + m_FrameIndex++; + if (!m_RestartFrame) + { + AdjustForNoReturn(&m_InstructionPointer); + } + return S_OK; +} diff --git a/src/unwinder/amd64/unwinder_amd64.cpp b/src/unwinder/amd64/unwinder_amd64.cpp new file mode 100644 index 0000000000..c04db21dfd --- /dev/null +++ b/src/unwinder/amd64/unwinder_amd64.cpp @@ -0,0 +1,1858 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// + +#include "stdafx.h" +#include "unwinder_amd64.h" + +typedef DPTR(M128A) PTR_M128A; + +//--------------------------------------------------------------------------------------- +// +// Read 64 bit unsigned value from the specified address. When the unwinder is built +// for jitted code unwinding on non-Windows systems, this is just a plain memory read. +// When the unwinder is built for DAC though, this reads data from the target debugged +// process. +// +// Arguments: +// addr - address to read from +// +// Return Value: +// The value that was read +// +// Notes: +// If the memory read fails in the DAC mode, the failure is reported as an exception +// via the DacError function. +// +static ULONG64 MemoryRead64(PULONG64 addr) +{ + return *dac_cast<PTR_ULONG64>((TADDR)addr); +} + +//--------------------------------------------------------------------------------------- +// +// Read 128 bit value from the specified address. When the unwinder is built +// for jitted code unwinding on non-Windows systems, this is just a plain memory read. +// When the unwinder is built for DAC though, this reads data from the target debugged +// process. +// +// Arguments: +// addr - address to read from +// +// Return Value: +// The value that was read +// +// Notes: +// If the memory read fails in the DAC mode, the failure is reported as an exception +// via the DacError function. +// +static M128A MemoryRead128(PM128A addr) +{ + return *dac_cast<PTR_M128A>((TADDR)addr); +} + +#ifdef DACCESS_COMPILE + +// Report failure in the unwinder if the condition is FALSE +#define UNWINDER_ASSERT(Condition) if (!(Condition)) DacError(CORDBG_E_TARGET_INCONSISTENT) + +//--------------------------------------------------------------------------------------- +// +// The InstructionBuffer class abstracts accessing assembler instructions in the function +// being unwound. It behaves as a memory byte pointer, but it reads the instruction codes +// from the target process being debugged and removes all changes that the debugger +// may have made to the code, e.g. breakpoint instructions. +// +class InstructionBuffer +{ + UINT m_offset; + SIZE_T m_address; + UCHAR m_buffer[32]; + + // Load the instructions from the target process being debugged + HRESULT Load() + { + HRESULT hr = DacReadAll(TO_TADDR(m_address), m_buffer, sizeof(m_buffer), false); + if (SUCCEEDED(hr)) + { + // On X64, we need to replace any patches which are within the requested memory range. + // This is because the X64 unwinder needs to disassemble the native instructions in order to determine + // whether the IP is in an epilog. + MemoryRange range(dac_cast<PTR_VOID>((TADDR)m_address), sizeof(m_buffer)); + hr = DacReplacePatchesInHostMemory(range, m_buffer); + } + + return hr; + } + +public: + + // Construct the InstructionBuffer for the given address in the target process + InstructionBuffer(SIZE_T address) + : m_offset(0), + m_address(address) + { + HRESULT hr = Load(); + if (FAILED(hr)) + { + // If we have failed to read from the target process, just pretend + // we've read zeros. + // The InstructionBuffer is used in code driven epilogue unwinding + // when we read processor instructions and simulate them. + // It's very rare to be stopped in an epilogue when + // getting a stack trace, so if we can't read the + // code just assume we aren't in an epilogue instead of failing + // the unwind. + memset(m_buffer, 0, sizeof(m_buffer)); + } + } + + // Move to the next byte in the buffer + InstructionBuffer& operator++() + { + m_offset++; + return *this; + } + + // Skip delta bytes in the buffer + InstructionBuffer& operator+=(INT delta) + { + m_offset += delta; + return *this; + } + + // Return address of the current byte in the buffer + explicit operator ULONG64() + { + return m_address + m_offset; + } + + // Get the byte at the given index from the current position + // Invoke DacError if the index is out of the buffer + UCHAR operator[](int index) + { + int realIndex = m_offset + index; + UNWINDER_ASSERT(realIndex < sizeof(m_buffer)); + return m_buffer[realIndex]; + } +}; + +//--------------------------------------------------------------------------------------- +// +// Given the target address of an UNWIND_INFO structure, this function retrieves all the memory used for +// the UNWIND_INFO, including the variable size array of UNWIND_CODE. The function returns a host copy +// of the UNWIND_INFO. +// +// Arguments: +// taUnwindInfo - the target address of an UNWIND_INFO +// +// Return Value: +// Return a host copy of the UNWIND_INFO, including the array of UNWIND_CODE. +// +// Notes: +// The host copy of UNWIND_INFO is created from DAC memory, which will be flushed when the DAC cache +// is flushed (i.e. when the debugee is continued). Thus, the caller doesn't need to worry about freeing +// this memory. +// +UNWIND_INFO * DacGetUnwindInfo(TADDR taUnwindInfo) +{ + PTR_UNWIND_INFO pUnwindInfo = PTR_UNWIND_INFO(taUnwindInfo); + DWORD cbUnwindInfo = offsetof(UNWIND_INFO, UnwindCode) + + pUnwindInfo->CountOfUnwindCodes * sizeof(UNWIND_CODE); + + // Check if there is a chained unwind info. If so, it has an extra RUNTIME_FUNCTION tagged to the end. + if ((pUnwindInfo->Flags & UNW_FLAG_CHAININFO) != 0) + { + // If there is an odd number of UNWIND_CODE, we need to adjust for alignment. + if ((pUnwindInfo->CountOfUnwindCodes & 1) != 0) + { + cbUnwindInfo += sizeof(UNWIND_CODE); + } + cbUnwindInfo += sizeof(T_RUNTIME_FUNCTION); + } + return reinterpret_cast<UNWIND_INFO *>(DacInstantiateTypeByAddress(taUnwindInfo, cbUnwindInfo, true)); +} + +//--------------------------------------------------------------------------------------- +// +// This function just wraps the DacGetUnwindInfo. +// The DacGetUnwindInfo is called from other places outside of the unwinder, so it +// cannot be merged into the body of this method. +// +UNWIND_INFO * OOPStackUnwinderAMD64::GetUnwindInfo(TADDR taUnwindInfo) +{ + return DacGetUnwindInfo(taUnwindInfo); +} + + +//--------------------------------------------------------------------------------------- +// +// This function is just a wrapper over OOPStackUnwinder. The runtime can call this function to +// virtually unwind a CONTEXT out-of-process. +// +// Arguments: +// pContext - This is an in-out parameter. On entry, this is the CONTEXT to be unwound. +// On exit, this is the caller CONTEXT. +// +// Return Value: +// TRUE if the unwinding is successful +// +// Notes: +// This function overwrites the specified CONTEXT to store the caller CONTEXT. +// + +BOOL DacUnwindStackFrame(CONTEXT * pContext, KNONVOLATILE_CONTEXT_POINTERS* pContextPointers) +{ + BOOL res = OOPStackUnwinderAMD64::Unwind(pContext); + + if (res && pContextPointers) + { + for (int i = 0; i < 16; i++) + { + *(&pContextPointers->Rax + i) = &pContext->Rax + i; + } + } + + return res; +} + +//--------------------------------------------------------------------------------------- +// +// Unwind the given CONTEXT to the caller CONTEXT. The given CONTEXT will be overwritten. +// +// Arguments: +// pContext - in-out parameter storing the specified CONTEXT on entry and the unwound CONTEXT on exit +// +// Return Value: +// TRUE if the unwinding is successful +// + +BOOL OOPStackUnwinderAMD64::Unwind(CONTEXT * pContext) +{ + HRESULT hr = E_FAIL; + + ULONG64 uControlPC = (DWORD64)dac_cast<PCODE>(::GetIP(pContext)); + + // get the module base + ULONG64 uImageBase; + hr = GetModuleBase(uControlPC, &uImageBase); + if (FAILED(hr)) + { + return FALSE; + } + + // get the function entry + IMAGE_RUNTIME_FUNCTION_ENTRY functionEntry; + hr = GetFunctionEntry(uControlPC, &functionEntry, sizeof(functionEntry)); + if (FAILED(hr)) + { + return FALSE; + } + + // call VirtualUnwind() to do the real work + ULONG64 EstablisherFrame; + hr = VirtualUnwind(0, uImageBase, uControlPC, &functionEntry, pContext, NULL, &EstablisherFrame, NULL, NULL); + + return (hr == S_OK); +} + +#else // DACCESS_COMPILE + +// Report failure in the unwinder if the condition is FALSE +#define UNWINDER_ASSERT _ASSERTE + +// For unwinding of the jitted code on non-Windows platforms, the Instruction buffer is +// just a plain pointer to the instruction data. +typedef UCHAR * InstructionBuffer; + +//--------------------------------------------------------------------------------------- +// +// Return UNWIND_INFO pointer for the given address. +// +UNWIND_INFO * OOPStackUnwinderAMD64::GetUnwindInfo(TADDR taUnwindInfo) +{ + return (UNWIND_INFO *)taUnwindInfo; +} + +//--------------------------------------------------------------------------------------- +// +// This function behaves like the RtlVirtualUnwind in Windows. +// It virtually unwinds the specified function by executing its +// prologue code backward or its epilogue code forward. +// +// If a context pointers record is specified, then the address where each +// nonvolatile registers is restored from is recorded in the appropriate +// element of the context pointers record. +// +// Arguments: +// +// HandlerType - Supplies the handler type expected for the virtual unwind. +// This may be either an exception or an unwind handler. A flag may +// optionally be supplied to avoid epilogue detection if it is known +// the specified control PC is not located inside a function epilogue. +// +// ImageBase - Supplies the base address of the image that contains the +// function being unwound. +// +// ControlPc - Supplies the address where control left the specified +// function. +// +// FunctionEntry - Supplies the address of the function table entry for the +// specified function. +// +// ContextRecord - Supplies the address of a context record. +// +// HandlerData - Supplies a pointer to a variable that receives a pointer +// the the language handler data. +// +// EstablisherFrame - Supplies a pointer to a variable that receives the +// the establisher frame pointer value. +// +// ContextPointers - Supplies an optional pointer to a context pointers +// record. +// +// Return value: +// +// The handler routine address. If control did not leave the specified +// function in either the prologue or an epilogue and a handler of the +// proper type is associated with the function, then the address of the +// language specific exception handler is returned. Otherwise, NULL is +// returned. +// +PEXCEPTION_ROUTINE RtlVirtualUnwind_Unsafe( + __in ULONG HandlerType, + __in ULONG64 ImageBase, + __in ULONG64 ControlPc, + __in PT_RUNTIME_FUNCTION FunctionEntry, + __in OUT PCONTEXT ContextRecord, + __out PVOID *HandlerData, + __out PULONG64 EstablisherFrame, + __inout_opt PKNONVOLATILE_CONTEXT_POINTERS ContextPointers + ) +{ + PEXCEPTION_ROUTINE handlerRoutine; + + HRESULT res = OOPStackUnwinderAMD64::VirtualUnwind( + HandlerType, + ImageBase, + ControlPc, + (_PIMAGE_RUNTIME_FUNCTION_ENTRY)FunctionEntry, + ContextRecord, + HandlerData, + EstablisherFrame, + ContextPointers, + &handlerRoutine); + + _ASSERTE(SUCCEEDED(res)); + + return handlerRoutine; +} + + +#endif // DACCESS_COMPILE + +// +// +// <NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE> +// +// Everything below is borrowed from minkernel\ntos\rtl\amd64\exdsptch.c file from Windows +// +// <NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE> +// +// + + +//---------------------------------------------------------------------------- +// +// Copied OS code. +// +// This must be kept in sync with the system unwinder. +// minkernel\ntos\rtl\amd64\exdsptch.c +// +//---------------------------------------------------------------------------- + +// +// ****** temp - defin elsewhere ****** +// + +#define SIZE64_PREFIX 0x48 +#define ADD_IMM8_OP 0x83 +#define ADD_IMM32_OP 0x81 +#define JMP_IMM8_OP 0xeb +#define JMP_IMM32_OP 0xe9 +#define JMP_IND_OP 0xff +#define LEA_OP 0x8d +#define REPNE_PREFIX 0xf2 +#define REP_PREFIX 0xf3 +#define POP_OP 0x58 +#define RET_OP 0xc3 +#define RET_OP_2 0xc2 + +#define IS_REX_PREFIX(x) (((x) & 0xf0) == 0x40) + +#define UNWIND_CHAIN_LIMIT 32 + +HRESULT +OOPStackUnwinderAMD64::UnwindEpilogue( + __in ULONG64 ImageBase, + __in ULONG64 ControlPc, + __in ULONG EpilogueOffset, + __in _PIMAGE_RUNTIME_FUNCTION_ENTRY FunctionEntry, + __inout PCONTEXT ContextRecord, + __inout_opt PKNONVOLATILE_CONTEXT_POINTERS ContextPointers +) + +/*++ + +Routine Description: + + This function emulates the state change associated with a function + epilogue by using the corresponding prologue unwind codes of the + primary function entry corresponding to the specified function. + + The prologue unwind codes can be used to reverse the epilogue since + the epilogue operations are structured as a mirror-image of the initial + prologue instructions prior to the establishment of the frame. + +Arguments: + + ImageBase - Supplies the base address of the image that contains the + function being unwound. + + ControlPc - Supplies the address where control left the specified function. + + EpilogueOffset - Supplies the offset within an epilogue of the specified + instruction pointer address. + + FunctionEntry - Supplies a pointer to the function table entry for the + specified function. If appropriate, this has already been probed. + + ContextRecord - Supplies a pointer to a context record. + + ContextPointers - Supplies an optional pointer to a context pointers record. + + +Return Value: + +HRESULT. + +--*/ + +{ + + ULONG ChainCount; + ULONG CountOfCodes; + ULONG CurrentOffset; + ULONG FirstPushIndex; + ULONG Index; + PULONG64 IntegerAddress; + PULONG64 IntegerRegister; + ULONG OpInfo; + PULONG64 ReturnAddress; + PULONG64 StackAddress; + NTSTATUS Status; + PUNWIND_INFO UnwindInfo; + UNWIND_CODE UnwindOp; + + // + // A canonical epilogue sequence consists of the following operations: + // + // 1. Optional cleanup of fixed and dynamic stack allocations, which is + // considered to be outside of the epilogue region. + // + // add rsp, imm + // or + // lea rsp, disp[fp] + // + // 2. Zero or more pop nonvolatile-integer-register[0..15] instructions, + // which are unwound using the corresponding UWOP_PUSH_NONVOL opcodes. + // + // pop r64 + // or + // REX.R pop r64 + // + // 3. An optional one-byte pop r64 to a volatile register to clean up an + // RFLAGS register pushed with pushfq. This is marked with a + // UWOP_ALLOC_SMALL 8 opcode. + // + // pop rcx + // + // 4. A control transfer instruction (ret or jump). In both cases, there + // will be no prologue unwind codes remaining after the previous set of + // recognized operations are emulated. + // + // ret 0 + // or + // jmp imm + // or + // jmp [target] + // or + // iretq + // + // N.B. The correctness of these assumptions is based on the ordering + // of unwind codes and the mirroring of epilogue and prologue + // regions. + // + // Find the function's primary entry, which contains the relevant frame + // adjustment unwind codes. + // + // Locate the first push unwind code. This code requires that all pushes + // occur within a single function entry, though not necessarily within the + // root function entry of a chained function. + // + + ChainCount = 0; + for (;;) { + UnwindInfo = GetUnwindInfo(FunctionEntry->UnwindInfoAddress + ImageBase); + if (UnwindInfo == NULL) + { + return HRESULT_FROM_WIN32(ERROR_READ_FAULT); + } + CountOfCodes = UnwindInfo->CountOfUnwindCodes; + FirstPushIndex = 0; + while (FirstPushIndex < CountOfCodes) { + UnwindOp = UnwindInfo->UnwindCode[FirstPushIndex]; + if ((UnwindOp.UnwindOp == UWOP_PUSH_NONVOL) || + (UnwindOp.UnwindOp == UWOP_PUSH_MACHFRAME)) { + + break; + } + + FirstPushIndex += UnwindOpSlots(UnwindOp); + } + + if (FirstPushIndex < CountOfCodes) { + break; + } + + // + // If a chained parent function entry exists, continue looking for + // push opcodes in the parent. + // + + if ((UnwindInfo->Flags & UNW_FLAG_CHAININFO) == 0) { + break; + } + + ChainCount += 1; + if (ChainCount > UNWIND_CHAIN_LIMIT) { + return E_FAIL; + } + + Index = CountOfCodes; + if (Index % 2 != 0) { + Index += 1; + } + + FunctionEntry = (_PIMAGE_RUNTIME_FUNCTION_ENTRY)&UnwindInfo->UnwindCode[Index]; + } + + // + // Unwind any push codes that have not already been reversed by the + // epilogue. + // + + CurrentOffset = 0; + IntegerRegister = &ContextRecord->Rax; + for (Index = FirstPushIndex; Index < CountOfCodes; Index += 1) { + UnwindOp = UnwindInfo->UnwindCode[Index]; + OpInfo = UnwindOp.OpInfo; + + if (UnwindOp.UnwindOp != UWOP_PUSH_NONVOL) { + break; + } + + if (CurrentOffset >= EpilogueOffset) { + IntegerAddress = (PULONG64)(ContextRecord->Rsp); + + ContextRecord->Rsp += 8; + IntegerRegister[OpInfo] = MemoryRead64(IntegerAddress); + if (ARGUMENT_PRESENT(ContextPointers)) { + ContextPointers->IntegerContext[OpInfo] = IntegerAddress; + } + } + + // + // POP r64 is encoded as (58h + r64) for the lower 8 general-purpose + // registers and REX.R, (58h + r64) for r8 - r15. + // + + CurrentOffset += 1; + if (OpInfo >= 8) { + CurrentOffset += 1; + } + } + + // + // Check for an UWOP_ALLOC_SMALL 8 directive, which corresponds to a push + // of the FLAGS register. + // + + if ((Index < CountOfCodes) && + (UnwindOp.UnwindOp == UWOP_ALLOC_SMALL) && (OpInfo == 0)) { + + if (CurrentOffset >= EpilogueOffset) { + ContextRecord->Rsp += 8; + } + + CurrentOffset += 1; + Index += 1; + } + + // + // Check for a machine frame. + // + + if (Index < CountOfCodes) { + UnwindOp = UnwindInfo->UnwindCode[Index]; + if (UnwindOp.UnwindOp == UWOP_PUSH_MACHFRAME) { + ReturnAddress = (PULONG64)(ContextRecord->Rsp); + StackAddress = (PULONG64)(ContextRecord->Rsp + (3 * 8)); + + ContextRecord->Rip = MemoryRead64(ReturnAddress); + ContextRecord->Rsp = MemoryRead64(StackAddress); + return S_OK; + } + + // + // Any remaining operation must be a machine frame. + // + + UNWINDER_ASSERT(FALSE); + } + + // + // Emulate a return operation. + // + + IntegerAddress = (PULONG64)(ContextRecord->Rsp); + + ContextRecord->Rip = MemoryRead64(IntegerAddress); + ContextRecord->Rsp += 8; + return S_OK; +} + +HRESULT +OOPStackUnwinderAMD64::UnwindPrologue( + __in ULONG64 ImageBase, + __in ULONG64 ControlPc, + __in ULONG64 FrameBase, + __in _PIMAGE_RUNTIME_FUNCTION_ENTRY FunctionEntry, + __inout PCONTEXT ContextRecord, + __inout_opt PKNONVOLATILE_CONTEXT_POINTERS ContextPointers, + __deref_out _PIMAGE_RUNTIME_FUNCTION_ENTRY *FinalFunctionEntry + ) + +/*++ + +Routine Description: + + This function processes unwind codes and reverses the state change + effects of a prologue. If the specified unwind information contains + chained unwind information, then that prologue is unwound recursively. + As the prologue is unwound state changes are recorded in the specified + context structure and optionally in the specified context pointers + structures. + +Arguments: + + ImageBase - Supplies the base address of the image that contains the + function being unwound. + + ControlPc - Supplies the address where control left the specified + function. + + FrameBase - Supplies the base of the stack frame subject function stack + frame. + + FunctionEntry - Supplies the address of the function table entry for the + specified function. + + ContextRecord - Supplies the address of a context record. + + ContextPointers - Supplies an optional pointer to a context pointers + record. + + FinalFunctionEntry - Supplies a pointer to a variable that receives the + final function entry after the specified function entry and all + descendent chained entries have been unwound. This will have been + probed as appropriate. + +Return Value: + + HRESULT. + +--*/ + +{ + + ULONG ChainCount; + PM128A FloatingAddress; + PM128A FloatingRegister; + ULONG FrameOffset; + ULONG Index; + PULONG64 IntegerAddress; + PULONG64 IntegerRegister; + BOOLEAN MachineFrame; + ULONG OpInfo; + ULONG PrologOffset; + PULONG64 ReturnAddress; + PULONG64 StackAddress; + PUNWIND_INFO UnwindInfo; + ULONG UnwindOp; + + // + // Process the unwind codes for the specified function entry and all its + // descendent chained function entries. + // + + ChainCount = 0; + FloatingRegister = &ContextRecord->Xmm0; + IntegerRegister = &ContextRecord->Rax; + do { + Index = 0; + MachineFrame = FALSE; + PrologOffset = (ULONG)(ControlPc - (FunctionEntry->BeginAddress + ImageBase)); + + UnwindInfo = GetUnwindInfo(ImageBase + FunctionEntry->UnwindInfoAddress); + if (UnwindInfo == NULL) + { + return HRESULT_FROM_WIN32(ERROR_READ_FAULT); + } + + while (Index < UnwindInfo->CountOfUnwindCodes) { + + // + // If the prologue offset is greater than the next unwind code + // offset, then simulate the effect of the unwind code. + // + + UnwindOp = UnwindInfo->UnwindCode[Index].UnwindOp; +#ifdef PLATFORM_UNIX + if (UnwindOp > UWOP_SET_FPREG_LARGE) { + return E_UNEXPECTED; + } +#else // !PLATFORM_UNIX + if (UnwindOp > UWOP_PUSH_MACHFRAME) { + return E_UNEXPECTED; + } +#endif // !PLATFORM_UNIX + + OpInfo = UnwindInfo->UnwindCode[Index].OpInfo; + if (PrologOffset >= UnwindInfo->UnwindCode[Index].CodeOffset) { + switch (UnwindOp) { + + // + // Push nonvolatile integer register. + // + // The operation information is the register number of + // the register than was pushed. + // + + case UWOP_PUSH_NONVOL: + IntegerAddress = (PULONG64)ContextRecord->Rsp; + IntegerRegister[OpInfo] = MemoryRead64(IntegerAddress); + + if (ARGUMENT_PRESENT(ContextPointers)) { + ContextPointers->IntegerContext[OpInfo] = IntegerAddress; + } + + ContextRecord->Rsp += 8; + break; + + // + // Allocate a large sized area on the stack. + // + // The operation information determines if the size is + // 16- or 32-bits. + // + + case UWOP_ALLOC_LARGE: + Index += 1; + FrameOffset = UnwindInfo->UnwindCode[Index].FrameOffset; + if (OpInfo != 0) { + Index += 1; + FrameOffset += (UnwindInfo->UnwindCode[Index].FrameOffset << 16); + + } else { + // The 16-bit form is scaled. + FrameOffset *= 8; + } + + ContextRecord->Rsp += FrameOffset; + break; + + // + // Allocate a small sized area on the stack. + // + // The operation information is the size of the unscaled + // allocation size (8 is the scale factor) minus 8. + // + + case UWOP_ALLOC_SMALL: + ContextRecord->Rsp += (OpInfo * 8) + 8; + break; + + // + // Establish the the frame pointer register. + // + // The operation information is not used. + // + + case UWOP_SET_FPREG: + ContextRecord->Rsp = IntegerRegister[UnwindInfo->FrameRegister]; + ContextRecord->Rsp -= UnwindInfo->FrameOffset * 16; + break; + +#ifdef PLATFORM_UNIX + + // + // Establish the the frame pointer register using a large size displacement. + // UNWIND_INFO.FrameOffset must be 15 (the maximum value, corresponding to a scaled + // offset of 15 * 16 == 240). The next two codes contain a 32-bit offset, which + // is also scaled by 16, since the stack must remain 16-bit aligned. + // + + case UWOP_SET_FPREG_LARGE: + UNWINDER_ASSERT(UnwindInfo->FrameOffset == 15); + Index += 2; + FrameOffset = UnwindInfo->UnwindCode[Index - 1].FrameOffset; + FrameOffset += UnwindInfo->UnwindCode[Index].FrameOffset << 16; + UNWINDER_ASSERT((FrameOffset & 0xF0000000) == 0); + ContextRecord->Rsp = IntegerRegister[UnwindInfo->FrameRegister]; + ContextRecord->Rsp -= FrameOffset * 16; + break; + +#endif // PLATFORM_UNIX + + // + // Save nonvolatile integer register on the stack using a + // 16-bit displacment. + // + // The operation information is the register number. + // + + case UWOP_SAVE_NONVOL: + Index += 1; + FrameOffset = UnwindInfo->UnwindCode[Index].FrameOffset * 8; + IntegerAddress = (PULONG64)(FrameBase + FrameOffset); + IntegerRegister[OpInfo] = MemoryRead64(IntegerAddress); + + if (ARGUMENT_PRESENT(ContextPointers)) { + ContextPointers->IntegerContext[OpInfo] = IntegerAddress; + } + + break; + + // + // Save nonvolatile integer register on the stack using a + // 32-bit displacment. + // + // The operation information is the register number. + // + + case UWOP_SAVE_NONVOL_FAR: + Index += 2; + FrameOffset = UnwindInfo->UnwindCode[Index - 1].FrameOffset; + FrameOffset += UnwindInfo->UnwindCode[Index].FrameOffset << 16; + IntegerAddress = (PULONG64)(FrameBase + FrameOffset); + IntegerRegister[OpInfo] = MemoryRead64(IntegerAddress); + + if (ARGUMENT_PRESENT(ContextPointers)) { + ContextPointers->IntegerContext[OpInfo] = IntegerAddress; + } + + break; + + // + // Function epilog marker (ignored for prologue unwind). + // + + case UWOP_EPILOG: + Index += 1; + break; + + // + // Spare unused codes. + // + + + case UWOP_SPARE_CODE: + + UNWINDER_ASSERT(FALSE); + + Index += 2; + break; + + // + // Save a nonvolatile XMM(128) register on the stack using a + // 16-bit displacement. + // + // The operation information is the register number. + // + + case UWOP_SAVE_XMM128: + Index += 1; + FrameOffset = UnwindInfo->UnwindCode[Index].FrameOffset * 16; + FloatingAddress = (PM128A)(FrameBase + FrameOffset); + FloatingRegister[OpInfo] = MemoryRead128(FloatingAddress); + + if (ARGUMENT_PRESENT(ContextPointers)) { + ContextPointers->FloatingContext[OpInfo] = FloatingAddress; + } + + break; + + // + // Save a nonvolatile XMM(128) register on the stack using + // a 32-bit displacement. + // + // The operation information is the register number. + // + + case UWOP_SAVE_XMM128_FAR: + Index += 2; + FrameOffset = UnwindInfo->UnwindCode[Index - 1].FrameOffset; + FrameOffset += UnwindInfo->UnwindCode[Index].FrameOffset << 16; + FloatingAddress = (PM128A)(FrameBase + FrameOffset); + FloatingRegister[OpInfo] = MemoryRead128(FloatingAddress); + + if (ARGUMENT_PRESENT(ContextPointers)) { + ContextPointers->FloatingContext[OpInfo] = FloatingAddress; + } + + break; + + // + // Push a machine frame on the stack. + // + // The operation information determines whether the + // machine frame contains an error code or not. + // + + case UWOP_PUSH_MACHFRAME: + MachineFrame = TRUE; + ReturnAddress = (PULONG64)ContextRecord->Rsp; + StackAddress = (PULONG64)(ContextRecord->Rsp + (3 * 8)); + if (OpInfo != 0) { + ReturnAddress += 1; + StackAddress += 1; + } + + ContextRecord->Rip = MemoryRead64(ReturnAddress); + ContextRecord->Rsp = MemoryRead64(StackAddress); + + break; + + // + // Unused codes. + // + + default: + //RtlRaiseStatus(STATUS_BAD_FUNCTION_TABLE); + break; + } + + Index += 1; + + } else { + + // + // Skip this unwind operation by advancing the slot index + // by the number of slots consumed by this operation. + // + + Index += UnwindOpSlots(UnwindInfo->UnwindCode[Index]); + } + } + + // + // If chained unwind information is specified, then set the function + // entry address to the chained function entry and continue the scan. + // Otherwise, determine the return address if a machine frame was not + // encountered during the scan of the unwind codes and terminate the + // scan. + // + + if ((UnwindInfo->Flags & UNW_FLAG_CHAININFO) != 0) { + + _PIMAGE_RUNTIME_FUNCTION_ENTRY ChainEntry; + + Index = UnwindInfo->CountOfUnwindCodes; + if ((Index & 1) != 0) { + Index += 1; + } + + // GetUnwindInfo looks for CHAININFO and reads + // the trailing RUNTIME_FUNCTION so we can just + // directly use the data sitting in UnwindInfo. + FunctionEntry = (_PIMAGE_RUNTIME_FUNCTION_ENTRY) + &UnwindInfo->UnwindCode[Index]; + } else { + + if (MachineFrame == FALSE) { + ContextRecord->Rip = MemoryRead64((PULONG64)ContextRecord->Rsp); + ContextRecord->Rsp += 8; + } + + break; + } + + // + // Limit the number of iterations possible for chained function table + // entries. + // + + ChainCount += 1; + UNWINDER_ASSERT(ChainCount <= UNWIND_CHAIN_LIMIT); + + } while (TRUE); + + *FinalFunctionEntry = FunctionEntry; + return S_OK; +} + +HRESULT +OOPStackUnwinderAMD64::VirtualUnwind( + __in DWORD HandlerType, + __in ULONG64 ImageBase, + __in ULONG64 ControlPc, + __in _PIMAGE_RUNTIME_FUNCTION_ENTRY FunctionEntry, + __inout PCONTEXT ContextRecord, + __out PVOID *HandlerData, + __out PULONG64 EstablisherFrame, + __inout_opt PKNONVOLATILE_CONTEXT_POINTERS ContextPointers, + __deref_opt_out_opt PEXCEPTION_ROUTINE *HandlerRoutine + ) + +/*++ + +Routine Description: + + This function virtually unwinds the specified function by executing its + prologue code backward or its epilogue code forward. + + If a context pointers record is specified, then the address where each + nonvolatile registers is restored from is recorded in the appropriate + element of the context pointers record. + +Arguments: + + HandlerType - Supplies the handler type expected for the virtual unwind. + This may be either an exception or an unwind handler. A flag may + optionally be supplied to avoid epilogue detection if it is known + the specified control PC is not located inside a function epilogue. + + ImageBase - Supplies the base address of the image that contains the + function being unwound. + + ControlPc - Supplies the address where control left the specified + function. + + FunctionEntry - Supplies the address of the function table entry for the + specified function. + + ContextRecord - Supplies the address of a context record. + + + HandlerData - Supplies a pointer to a variable that receives a pointer + the the language handler data. + + EstablisherFrame - Supplies a pointer to a variable that receives the + the establisher frame pointer value. + + ContextPointers - Supplies an optional pointer to a context pointers + record. + + HandlerRoutine - Supplies an optional pointer to a variable that receives + the handler routine address. If control did not leave the specified + function in either the prologue or an epilogue and a handler of the + proper type is associated with the function, then the address of the + language specific exception handler is returned. Otherwise, NULL is + returned. +--*/ + +{ + + ULONG64 BranchTarget; + LONG Displacement; + ULONG EpilogueOffset; + ULONG EpilogueSize; + PEXCEPTION_ROUTINE FoundHandler; + ULONG FrameRegister; + ULONG FrameOffset; + ULONG Index; + BOOL InEpilogue; + PULONG64 IntegerAddress; + PULONG64 IntegerRegister; + _PIMAGE_RUNTIME_FUNCTION_ENTRY PrimaryFunctionEntry; + ULONG PrologOffset; + ULONG RegisterNumber; + ULONG RelativePc; + HRESULT Status; + PUNWIND_INFO UnwindInfo; + ULONG UnwindVersion; + UNWIND_CODE UnwindOp; + + FoundHandler = NULL; + UnwindInfo = GetUnwindInfo(ImageBase + FunctionEntry->UnwindInfoAddress); + if (UnwindInfo == NULL) + { + return HRESULT_FROM_WIN32(ERROR_READ_FAULT); + } + + UnwindVersion = UnwindInfo->Version; + + // + // If the specified function does not use a frame pointer, then the + // establisher frame is the contents of the stack pointer. This may + // not actually be the real establisher frame if control left the + // function from within the prologue. In this case the establisher + // frame may be not required since control has not actually entered + // the function and prologue entries cannot refer to the establisher + // frame before it has been established, i.e., if it has not been + // established, then no save unwind codes should be encountered during + // the unwind operation. + // + // If the specified function uses a frame pointer and control left the + // function outside of the prologue or the unwind information contains + // a chained information structure, then the establisher frame is the + // contents of the frame pointer. + // + // If the specified function uses a frame pointer and control left the + // function from within the prologue, then the set frame pointer unwind + // code must be looked up in the unwind codes to determine if the + // contents of the stack pointer or the contents of the frame pointer + // should be used for the establisher frame. This may not actually be + // the real establisher frame. In this case the establisher frame may + // not be required since control has not actually entered the function + // and prologue entries cannot refer to the establisher frame before it + // has been established, i.e., if it has not been established, then no + // save unwind codes should be encountered during the unwind operation. + // + // N.B. The correctness of these assumptions is based on the ordering of + // unwind codes. + // + + PrologOffset = (ULONG)(ControlPc - (FunctionEntry->BeginAddress + ImageBase)); + if (UnwindInfo->FrameRegister == 0) { + *EstablisherFrame = ContextRecord->Rsp; + + } else if ((PrologOffset >= UnwindInfo->SizeOfProlog) || + ((UnwindInfo->Flags & UNW_FLAG_CHAININFO) != 0)) { + + FrameOffset = UnwindInfo->FrameOffset; + +#ifdef PLATFORM_UNIX + // If UnwindInfo->FrameOffset == 15 (the maximum value), then there might be a UWOP_SET_FPREG_LARGE. + // However, it is still legal for a UWOP_SET_FPREG to set UnwindInfo->FrameOffset == 15 (since this + // was always part of the specification), so we need to look through the UnwindCode array to determine + // if there is indeed a UWOP_SET_FPREG_LARGE. If we don't find UWOP_SET_FPREG_LARGE, then just use + // (scaled) FrameOffset of 240, as before. (We don't verify there is a UWOP_SET_FPREG code, but we could.) + if (FrameOffset == 15) { + Index = 0; + while (Index < UnwindInfo->CountOfUnwindCodes) { + UnwindOp = UnwindInfo->UnwindCode[Index]; + if (UnwindOp.UnwindOp == UWOP_SET_FPREG_LARGE) { + FrameOffset = UnwindInfo->UnwindCode[Index + 1].FrameOffset; + FrameOffset += UnwindInfo->UnwindCode[Index + 2].FrameOffset << 16; + break; + } + + Index += UnwindOpSlots(UnwindOp); + } + } +#endif // PLATFORM_UNIX + + *EstablisherFrame = (&ContextRecord->Rax)[UnwindInfo->FrameRegister]; + *EstablisherFrame -= FrameOffset * 16; + + } else { + FrameOffset = UnwindInfo->FrameOffset; + Index = 0; + while (Index < UnwindInfo->CountOfUnwindCodes) { + UnwindOp = UnwindInfo->UnwindCode[Index]; + if (UnwindOp.UnwindOp == UWOP_SET_FPREG) { + break; + } +#ifdef PLATFORM_UNIX + else if (UnwindOp.UnwindOp == UWOP_SET_FPREG_LARGE) { + UNWINDER_ASSERT(UnwindInfo->FrameOffset == 15); + FrameOffset = UnwindInfo->UnwindCode[Index + 1].FrameOffset; + FrameOffset += UnwindInfo->UnwindCode[Index + 2].FrameOffset << 16; + break; + } +#endif // PLATFORM_UNIX + + Index += UnwindOpSlots(UnwindOp); + } + + if (PrologOffset >= UnwindInfo->UnwindCode[Index].CodeOffset) { + *EstablisherFrame = (&ContextRecord->Rax)[UnwindInfo->FrameRegister]; + *EstablisherFrame -= FrameOffset * 16; + + } else { + *EstablisherFrame = ContextRecord->Rsp; + } + } + + // + // Check if control left the specified function during an epilogue + // sequence and emulate the execution of the epilogue forward and + // return no exception handler. + // + // If the unwind version indicates the absence of epilogue unwind codes + // this is done by emulating the instruction stream. Otherwise, epilogue + // detection and emulation is performed using the function unwind codes. + // + + InEpilogue = FALSE; + if (UnwindVersion < 2) { + InstructionBuffer InstrBuffer = (InstructionBuffer)ControlPc; + InstructionBuffer NextByte = InstrBuffer; + + // + // Check for one of: + // + // add rsp, imm8 + // or + // add rsp, imm32 + // or + // lea rsp, -disp8[fp] + // or + // lea rsp, -disp32[fp] + // + + if ((NextByte[0] == SIZE64_PREFIX) && + (NextByte[1] == ADD_IMM8_OP) && + (NextByte[2] == 0xc4)) { + + // + // add rsp, imm8. + // + + NextByte += 4; + + } else if ((NextByte[0] == SIZE64_PREFIX) && + (NextByte[1] == ADD_IMM32_OP) && + (NextByte[2] == 0xc4)) { + + // + // add rsp, imm32. + // + + NextByte += 7; + + } else if (((NextByte[0] & 0xfe) == SIZE64_PREFIX) && + (NextByte[1] == LEA_OP)) { + + FrameRegister = ((NextByte[0] & 0x1) << 3) | (NextByte[2] & 0x7); + if ((FrameRegister != 0) && + (FrameRegister == UnwindInfo->FrameRegister)) { + + if ((NextByte[2] & 0xf8) == 0x60) { + + // + // lea rsp, disp8[fp]. + // + + NextByte += 4; + + } else if ((NextByte[2] &0xf8) == 0xa0) { + + // + // lea rsp, disp32[fp]. + // + + NextByte += 7; + } + } + } + + // + // Check for any number of: + // + // pop nonvolatile-integer-register[0..15]. + // + + while (TRUE) { + if ((NextByte[0] & 0xf8) == POP_OP) { + NextByte += 1; + + } else if (IS_REX_PREFIX(NextByte[0]) && + ((NextByte[1] & 0xf8) == POP_OP)) { + + NextByte += 2; + + } else { + break; + } + } + + // + // A REPNE prefix may optionally precede a control transfer + // instruction with no effect on unwinding. + // + + if (NextByte[0] == REPNE_PREFIX) { + NextByte += 1; + } + + // + // If the next instruction is a return or an appropriate jump, then + // control is currently in an epilogue and execution of the epilogue + // should be emulated. Otherwise, execution is not in an epilogue and + // the prologue should be unwound. + // + + InEpilogue = FALSE; + if ( ((NextByte[0] == RET_OP) || + (NextByte[0] == RET_OP_2)) || + (((NextByte[0] == REP_PREFIX) && (NextByte[1] == RET_OP)))) { + + // + // A return is an unambiguous indication of an epilogue. + // + + InEpilogue = TRUE; + + } else if ((NextByte[0] == JMP_IMM8_OP) || + (NextByte[0] == JMP_IMM32_OP)) { + + // + // An unconditional branch to a target that is equal to the start of + // or outside of this routine is logically a call to another function. + // + + BranchTarget = (ULONG64)NextByte - ImageBase; + if (NextByte[0] == JMP_IMM8_OP) { + BranchTarget += 2 + (CHAR)NextByte[1]; + + } else { + LONG32 delta = NextByte[1] | (NextByte[2] << 8) | + (NextByte[3] << 16) | (NextByte[4] << 24); + BranchTarget += 5 + delta; + + } + + // + // Determine whether the branch target refers to code within this + // function. If not, then it is an epilogue indicator. + // + // A branch to the start of self implies a recursive call, so + // is treated as an epilogue. + // + + if (BranchTarget < FunctionEntry->BeginAddress || + BranchTarget >= FunctionEntry->EndAddress) { + + // + // The branch target is outside of the region described by + // this function entry. See whether it is contained within + // an indirect function entry associated with this same + // function. + // + // If not, then the branch target really is outside of + // this function. + // + + PrimaryFunctionEntry = + SameFunction(FunctionEntry, + ImageBase, + BranchTarget + ImageBase); + + if ((PrimaryFunctionEntry == NULL) || + (BranchTarget == PrimaryFunctionEntry->BeginAddress)) { + + InEpilogue = TRUE; + } + + } else if ((BranchTarget == FunctionEntry->BeginAddress) && + ((UnwindInfo->Flags & UNW_FLAG_CHAININFO) == 0)) { + + InEpilogue = TRUE; + } + + } else if ((NextByte[0] == JMP_IND_OP) && (NextByte[1] == 0x25)) { + + // + // An unconditional jump indirect. + // + // This is a jmp outside of the function, probably a tail call + // to an import function. + // + + InEpilogue = TRUE; + + } else if (((NextByte[0] & 0xf8) == SIZE64_PREFIX) && + (NextByte[1] == 0xff) && + (NextByte[2] & 0x38) == 0x20) { + + // + // This is an indirect jump opcode: 0x48 0xff /4. The 64-bit + // flag (REX.W) is always redundant here, so its presence is + // overloaded to indicate a branch out of the function - a tail + // call. + // + // Such an opcode is an unambiguous epilogue indication. + // + + InEpilogue = TRUE; + } + + if (InEpilogue != FALSE) { + IntegerRegister = &ContextRecord->Rax; + NextByte = InstrBuffer; + + // + // Emulate one of (if any): + // + // add rsp, imm8 + // or + // add rsp, imm32 + // or + // lea rsp, disp8[frame-register] + // or + // lea rsp, disp32[frame-register] + // + + if ((NextByte[0] & 0xf8) == SIZE64_PREFIX) { + + if (NextByte[1] == ADD_IMM8_OP) { + + // + // add rsp, imm8. + // + + ContextRecord->Rsp += (CHAR)NextByte[3]; + NextByte += 4; + + } + else if (NextByte[1] == ADD_IMM32_OP) { + + // + // add rsp, imm32. + // + + Displacement = NextByte[3] | (NextByte[4] << 8); + Displacement |= (NextByte[5] << 16) | (NextByte[6] << 24); + ContextRecord->Rsp += Displacement; + NextByte += 7; + + } + else if (NextByte[1] == LEA_OP) { + if ((NextByte[2] & 0xf8) == 0x60) { + + // + // lea rsp, disp8[frame-register]. + // + + ContextRecord->Rsp = IntegerRegister[FrameRegister]; + ContextRecord->Rsp += (CHAR)NextByte[3]; + NextByte += 4; + + } + else if ((NextByte[2] & 0xf8) == 0xa0) { + + // + // lea rsp, disp32[frame-register]. + // + + Displacement = NextByte[3] | (NextByte[4] << 8); + Displacement |= (NextByte[5] << 16) | (NextByte[6] << 24); + ContextRecord->Rsp = IntegerRegister[FrameRegister]; + ContextRecord->Rsp += Displacement; + NextByte += 7; + } + } + } + + // + // Emulate any number of (if any): + // + // pop nonvolatile-integer-register. + // + + while (TRUE) { + if ((NextByte[0] & 0xf8) == POP_OP) { + + // + // pop nonvolatile-integer-register[0..7] + // + + RegisterNumber = NextByte[0] & 0x7; + IntegerAddress = (PULONG64)ContextRecord->Rsp; + IntegerRegister[RegisterNumber] = MemoryRead64(IntegerAddress); + + if (ARGUMENT_PRESENT(ContextPointers)) { + ContextPointers->IntegerContext[RegisterNumber] = IntegerAddress; + } + + ContextRecord->Rsp += 8; + NextByte += 1; + + } + else if (IS_REX_PREFIX(NextByte[0]) && + (NextByte[1] & 0xf8) == POP_OP) { + + // + // pop nonvolatile-integer-register[8..15] + // + + RegisterNumber = ((NextByte[0] & 1) << 3) | (NextByte[1] & 0x7); + IntegerAddress = (PULONG64)ContextRecord->Rsp; + IntegerRegister[RegisterNumber] = MemoryRead64(IntegerAddress); + + if (ARGUMENT_PRESENT(ContextPointers)) { + ContextPointers->IntegerContext[RegisterNumber] = IntegerAddress; + } + + ContextRecord->Rsp += 8; + NextByte += 2; + + } + else { + break; + } + } + + // + // Emulate return and return null exception handler. + // + // Note: This instruction might in fact be a jmp, however + // we want to emulate a return regardless. + // + + ContextRecord->Rip = MemoryRead64((PULONG64)ContextRecord->Rsp); + ContextRecord->Rsp += 8; + goto ExitSetHandler; + } + + } else if (UnwindInfo->CountOfUnwindCodes != 0) { + + UNWINDER_ASSERT(UnwindVersion >= 2); + + // + // Capture the first unwind code and check if it is an epilogue code. + // If it is not an epilogue code, the current function entry does not + // contain any epilogues (it could represent a body region of a + // separated function or it could represent a function which never + // returns). + // + + UnwindOp = UnwindInfo->UnwindCode[0]; + if (UnwindOp.UnwindOp == UWOP_EPILOG) { + EpilogueSize = UnwindOp.CodeOffset; + + UNWINDER_ASSERT(EpilogueSize != 0); + // + // If the low bit of the OpInfo field of the first epilogue code + // is set, the function has a single epilogue at the end of the + // function. Otherwise, subsequent epilogue unwind codes indicate + // the offset of the epilogue(s) from the function end and the + // relative PC must be compared against each epilogue record. + // + // N.B. The relative instruction pointer may not be within the + // bounds of the runtime function entry if control left the + // function in a region described by an indirect function + // entry. Such a region cannot contain any epilogues. + // + + RelativePc = (ULONG)(ControlPc - ImageBase); + if ((UnwindOp.OpInfo & 1) != 0) { + EpilogueOffset = FunctionEntry->EndAddress - EpilogueSize; + if (RelativePc - EpilogueOffset < EpilogueSize) { + InEpilogue = TRUE; + } + } + + if (InEpilogue == FALSE) { + for (Index = 1; Index < UnwindInfo->CountOfUnwindCodes; Index += 1) { + UnwindOp = UnwindInfo->UnwindCode[Index]; + + if (UnwindOp.UnwindOp == UWOP_EPILOG) { + EpilogueOffset = UnwindOp.EpilogueCode.OffsetLow + + UnwindOp.EpilogueCode.OffsetHigh * 256; + + // + // An epilogue offset of 0 indicates that this is + // a padding entry (the number of epilogue codes + // is a multiple of 2). + // + + if (EpilogueOffset == 0) { + break; + } + + EpilogueOffset = FunctionEntry->EndAddress - EpilogueOffset; + if (RelativePc - EpilogueOffset < EpilogueSize) { + + UNWINDER_ASSERT(EpilogueOffset != FunctionEntry->EndAddress); + InEpilogue = TRUE; + break; + } + + } else { + break; + } + } + } + + if (InEpilogue != FALSE) { + Status = UnwindEpilogue(ImageBase, + ControlPc, + RelativePc - EpilogueOffset, + FunctionEntry, + ContextRecord, + ContextPointers); + + goto ExitSetHandler; + } + } + } + + // + // Control left the specified function outside an epilogue. Unwind the + // subject function and any chained unwind information. + // + + Status = UnwindPrologue(ImageBase, + ControlPc, + *EstablisherFrame, + FunctionEntry, + ContextRecord, + ContextPointers, + &FunctionEntry); + + if (Status != S_OK) { + return Status; + } + + // + // If control left the specified function outside of the prologue and + // the function has a handler that matches the specified type, then + // return the address of the language specific exception handler. + // Otherwise, return NULL. + // + + if (HandlerType != 0) { + PrologOffset = (ULONG)(ControlPc - (FunctionEntry->BeginAddress + ImageBase)); + UnwindInfo = GetUnwindInfo(FunctionEntry->UnwindInfoAddress + ImageBase); + if (UnwindInfo == NULL) + { + return HRESULT_FROM_WIN32(ERROR_READ_FAULT); + } + if ((PrologOffset >= UnwindInfo->SizeOfProlog) && + ((UnwindInfo->Flags & HandlerType) != 0)) { + + Index = UnwindInfo->CountOfUnwindCodes; + if ((Index & 1) != 0) { + Index += 1; + } + + *HandlerData = &UnwindInfo->UnwindCode[Index + 2]; + FoundHandler = (PEXCEPTION_ROUTINE)(*((PULONG)&UnwindInfo->UnwindCode[Index]) + ImageBase); + } + } + +ExitSetHandler: + if (ARGUMENT_PRESENT(HandlerRoutine)) { + *HandlerRoutine = FoundHandler; + } + + return S_OK; +} + +_PIMAGE_RUNTIME_FUNCTION_ENTRY +OOPStackUnwinderAMD64::LookupPrimaryFunctionEntry( + __in _PIMAGE_RUNTIME_FUNCTION_ENTRY FunctionEntry, + __in ULONG64 ImageBase + + ) + +/*++ + +Routine Description: + + This function determines whether the supplied function entry is a primary + function entry or a chained function entry. If it is a chained function + entry, then the primary function entry is returned. + +Arguments: + + FunctionEntry - Supplies a pointer to the function entry for which the + associated primary function entry will be located. + + ImageBase - Supplies the base address of the image containing the + supplied function entry. + +Return Value: + + A pointer to the primary function entry is returned as the function value. + +--*/ + +{ + + ULONG ChainCount; + ULONG Index; + PUNWIND_INFO UnwindInfo; + + // + // Locate the unwind information and determine whether it is chained. + // If the unwind information is chained, then locate the parent function + // entry and loop again. + // + + ChainCount = 0; + do { + UnwindInfo = GetUnwindInfo(FunctionEntry->UnwindInfoAddress + ImageBase); + if ((UnwindInfo == NULL) || ((UnwindInfo->Flags & UNW_FLAG_CHAININFO) == 0)) + { + break; + } + + Index = UnwindInfo->CountOfUnwindCodes; + if ((Index & 1) != 0) { + Index += 1; + } + + FunctionEntry = (_PIMAGE_RUNTIME_FUNCTION_ENTRY)&UnwindInfo->UnwindCode[Index]; + + // + // Limit the number of iterations possible for chained function table + // entries. + // + + ChainCount += 1; + UNWINDER_ASSERT(ChainCount <= UNWIND_CHAIN_LIMIT); + + } while (TRUE); + + return FunctionEntry; +} + +_PIMAGE_RUNTIME_FUNCTION_ENTRY +OOPStackUnwinderAMD64::SameFunction( + __in _PIMAGE_RUNTIME_FUNCTION_ENTRY FunctionEntry, + __in ULONG64 ImageBase, + __in ULONG64 ControlPc + ) + +/*++ + +Routine Description: + + This function determines whether the address supplied by ControlPc lies + anywhere within the function associated with FunctionEntry. + +Arguments: + + FunctionEntry - Supplies a pointer to a function entry (primary or chained) + associated with the function. + + ImageBase - Supplies the base address of the image containing the supplied + function entry. + + ControlPc - Supplies the address that will be tested for inclusion within + the function associated with FunctionEntry. + +Return Value: + + If the address of the unwind information for the specified function is + equal to the address of the unwind information for the control PC, then + a pointer to a function table entry that describes the primary function + table entry is returned as the function value. Otherwise, NULL is returned. + +--*/ + +{ + _PIMAGE_RUNTIME_FUNCTION_ENTRY PrimaryFunctionEntry; + _IMAGE_RUNTIME_FUNCTION_ENTRY TargetFunctionEntry; + ULONG64 TargetImageBase; + + // + // Find the unwind information referenced by the primary function entry + // associated with the specified function entry. + // + + PrimaryFunctionEntry = LookupPrimaryFunctionEntry(FunctionEntry, + ImageBase); + + // + // Determine the function entry containing the control Pc and similarly + // resolve its primary function entry. If no function entry can be + // found then the control pc resides in a different function. + // + + if (GetModuleBase(ControlPc, &TargetImageBase) != S_OK || + GetFunctionEntry(ControlPc, + &TargetFunctionEntry, + sizeof(TargetFunctionEntry)) != S_OK) { + return NULL; + } + + // + // Lookup the primary function entry associated with the target function + // entry. + // + + TargetFunctionEntry = *LookupPrimaryFunctionEntry(&TargetFunctionEntry, + TargetImageBase); + + // + // If the beginning offset of the two function entries are equal, then + // return the address of the primary function entry. Otherwise, return + // NULL. + // + + if (PrimaryFunctionEntry->BeginAddress == TargetFunctionEntry.BeginAddress) { + return PrimaryFunctionEntry; + + } else { + return NULL; + } +} + +ULONG OOPStackUnwinderAMD64::UnwindOpSlots(__in UNWIND_CODE UnwindCode) +/*++ + +Routine Description: + + This routine determines the number of unwind code slots ultimately + consumed by an unwind code sequence. + +Arguments: + + UnwindCode - Supplies the first unwind code in the sequence. + +Return Value: + + Returns the total count of the number of slots consumed by the unwind + code sequence. + +--*/ +{ + + ULONG Slots; + ULONG UnwindOp; + + // + // UWOP_SPARE_CODE may be found in very old x64 images. + // + + UnwindOp = UnwindCode.UnwindOp; + + UNWINDER_ASSERT(UnwindOp != UWOP_SPARE_CODE); + UNWINDER_ASSERT(UnwindOp < sizeof(UnwindOpExtraSlotTable)); + + Slots = UnwindOpExtraSlotTable[UnwindOp] + 1; + if ((UnwindOp == UWOP_ALLOC_LARGE) && (UnwindCode.OpInfo != 0)) { + Slots += 1; + } + + return Slots; +} + diff --git a/src/unwinder/amd64/unwinder_amd64.h b/src/unwinder/amd64/unwinder_amd64.h new file mode 100644 index 0000000000..7e33c28773 --- /dev/null +++ b/src/unwinder/amd64/unwinder_amd64.h @@ -0,0 +1,70 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// + +#ifndef __unwinder_amd64_h__ +#define __unwinder_amd64_h__ + +#include "unwinder.h" + + +//--------------------------------------------------------------------------------------- +// +// See the comment for the base class code:OOPStackUnwinder. +// + +class OOPStackUnwinderAMD64 : public OOPStackUnwinder +{ +public: + // Unwind the given CONTEXT to the caller CONTEXT. The CONTEXT will be overwritten. + static BOOL Unwind(CONTEXT * pContext); + + // + // Everything below comes from dbghelp.dll. + // + + static HRESULT VirtualUnwind(__in DWORD HandlerType, + __in DWORD64 ImageBase, + __in DWORD64 ControlPc, + __in _PIMAGE_RUNTIME_FUNCTION_ENTRY FunctionEntry, + __inout PCONTEXT ContextRecord, + __out PVOID *HandlerData, + __out PDWORD64 EstablisherFrame, + __inout_opt PKNONVOLATILE_CONTEXT_POINTERS ContextPointers, + __deref_opt_out_opt PEXCEPTION_ROUTINE *HandlerRoutine); + +protected: + + static ULONG UnwindOpSlots(__in UNWIND_CODE UnwindCode); + + static HRESULT UnwindEpilogue(__in ULONG64 ImageBase, + __in ULONG64 ControlPc, + __in ULONG EpilogueOffset, + __in _PIMAGE_RUNTIME_FUNCTION_ENTRY FunctionEntry, + __inout PCONTEXT ContextRecord, + __inout_opt PKNONVOLATILE_CONTEXT_POINTERS ContextPointers); + + static HRESULT UnwindPrologue(__in DWORD64 ImageBase, + __in DWORD64 ControlPc, + __in DWORD64 FrameBase, + __in _PIMAGE_RUNTIME_FUNCTION_ENTRY FunctionEntry, + __inout PCONTEXT ContextRecord, + __inout_opt PKNONVOLATILE_CONTEXT_POINTERS ContextPointers, + __deref_out _PIMAGE_RUNTIME_FUNCTION_ENTRY *FinalFunctionEntry); + + static _PIMAGE_RUNTIME_FUNCTION_ENTRY LookupPrimaryFunctionEntry + (__in _PIMAGE_RUNTIME_FUNCTION_ENTRY FunctionEntry, + __in DWORD64 ImageBase); + + static _PIMAGE_RUNTIME_FUNCTION_ENTRY SameFunction + (__in _PIMAGE_RUNTIME_FUNCTION_ENTRY FunctionEntry, + __in DWORD64 ImageBase, + __in DWORD64 ControlPc); + + static UNWIND_INFO * GetUnwindInfo(TADDR taUnwindInfo); +}; + +#endif // __unwinder_amd64_h__ + |