You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1444 lines
47 KiB
1444 lines
47 KiB
/*++
|
|
|
|
Copyright (c) 2000-2001 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
walkamd64.c
|
|
|
|
Abstract:
|
|
|
|
This file implements the AMD64 stack walking api.
|
|
|
|
Author:
|
|
|
|
Environment:
|
|
|
|
User Mode
|
|
|
|
--*/
|
|
|
|
#define _IMAGEHLP_SOURCE_
|
|
#include <nt.h>
|
|
#include <ntrtl.h>
|
|
#include <nturtl.h>
|
|
#include "private.h"
|
|
#define NOEXTAPI
|
|
#include "wdbgexts.h"
|
|
#include "ntdbg.h"
|
|
#include "symbols.h"
|
|
#include <stdlib.h>
|
|
#include <globals.h>
|
|
|
|
#define WDB(Args) SdbOut Args
|
|
|
|
//
|
|
// Lookup table providing the number of slots used by each unwind code.
|
|
//
|
|
|
|
UCHAR RtlpUnwindOpSlotTableAmd64[] = {
|
|
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
|
|
};
|
|
|
|
BOOL
|
|
WalkAmd64Init(
|
|
HANDLE Process,
|
|
LPSTACKFRAME64 StackFrame,
|
|
PAMD64_CONTEXT Context,
|
|
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine,
|
|
PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine,
|
|
PGET_MODULE_BASE_ROUTINE64 GetModuleBase
|
|
);
|
|
|
|
BOOL
|
|
WalkAmd64Next(
|
|
HANDLE Process,
|
|
LPSTACKFRAME64 StackFrame,
|
|
PAMD64_CONTEXT Context,
|
|
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine,
|
|
PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine,
|
|
PGET_MODULE_BASE_ROUTINE64 GetModuleBase
|
|
);
|
|
|
|
BOOL
|
|
UnwindStackFrameAmd64(
|
|
HANDLE Process,
|
|
PULONG64 ReturnAddress,
|
|
PULONG64 StackPointer,
|
|
PULONG64 FramePointer,
|
|
PAMD64_CONTEXT Context,
|
|
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory,
|
|
PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccess,
|
|
PGET_MODULE_BASE_ROUTINE64 GetModuleBase
|
|
);
|
|
|
|
|
|
PAMD64_UNWIND_INFO
|
|
ReadUnwindInfoAmd64(ULONG64 ImageBase, ULONG Offset,
|
|
BOOL ReadCodes, HANDLE Process,
|
|
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory,
|
|
PVOID StaticBuffer, ULONG StaticBufferSize)
|
|
{
|
|
ULONG Done;
|
|
ULONG UnwindInfoSize;
|
|
PAMD64_UNWIND_INFO UnwindInfo;
|
|
PVOID SymInfo = NULL;
|
|
ULONG SymInfoSize;
|
|
ULONG64 MemOffset = ImageBase + Offset;
|
|
|
|
// Static buffer should at least be large enough to read the
|
|
// basic structure.
|
|
if (StaticBufferSize < sizeof(*UnwindInfo)) {
|
|
return NULL;
|
|
}
|
|
UnwindInfo = (PAMD64_UNWIND_INFO)StaticBuffer;
|
|
|
|
// First read just the basic structure since the information
|
|
// is needed to compute the complete size.
|
|
if (!ReadMemory(Process, MemOffset,
|
|
UnwindInfo, sizeof(*UnwindInfo), &Done) ||
|
|
Done != sizeof(*UnwindInfo)) {
|
|
WDB((1, "Unable to read unwind info at %I64X\n", MemOffset));
|
|
|
|
SymInfo = GetUnwindInfoFromSymbols(Process, ImageBase, Offset,
|
|
&SymInfoSize);
|
|
if (!SymInfo || SymInfoSize < sizeof(*UnwindInfo)) {
|
|
WDB((1, "Unable to get symbol unwind info at %I64X:%X\n",
|
|
ImageBase, Offset));
|
|
return NULL;
|
|
}
|
|
|
|
memcpy(UnwindInfo, SymInfo, sizeof(*UnwindInfo));
|
|
}
|
|
|
|
if (!ReadCodes) {
|
|
return UnwindInfo;
|
|
}
|
|
|
|
// Compute the size of all the data.
|
|
UnwindInfoSize = sizeof(*UnwindInfo) +
|
|
(UnwindInfo->CountOfCodes - 1) * sizeof(AMD64_UNWIND_CODE);
|
|
// An extra alignment code and pointer may be added on to handle
|
|
// the chained info case where the chain pointer 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(ULONG64);
|
|
}
|
|
|
|
if (UnwindInfoSize > 0xffff) {
|
|
// Too large to be valid data, assume it's garbage.
|
|
WDB((1, "Invalid unwind info at %I64X\n", MemOffset));
|
|
return NULL;
|
|
}
|
|
|
|
if (SymInfo && UnwindInfoSize > SymInfoSize) {
|
|
WDB((1, "Insufficient unwind info in symbols for %I64X:%X\n",
|
|
ImageBase, Offset));
|
|
return NULL;
|
|
}
|
|
|
|
if (UnwindInfoSize > StaticBufferSize) {
|
|
UnwindInfo = (PAMD64_UNWIND_INFO)MemAlloc(UnwindInfoSize);
|
|
if (UnwindInfo == NULL) {
|
|
WDB((1, "Unable to allocate memory for unwind info\n"));
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
// Now read all the data.
|
|
if (SymInfo) {
|
|
memcpy(UnwindInfo, SymInfo, UnwindInfoSize);
|
|
} else if (!ReadMemory(Process, MemOffset, UnwindInfo, UnwindInfoSize,
|
|
&Done) ||
|
|
Done != UnwindInfoSize) {
|
|
if ((PVOID)UnwindInfo != StaticBuffer) {
|
|
MemFree(UnwindInfo);
|
|
}
|
|
|
|
WDB((1, "Unable to read unwind info at %I64X\n", MemOffset));
|
|
return NULL;
|
|
}
|
|
|
|
return UnwindInfo;
|
|
}
|
|
|
|
//
|
|
// ****** 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 LEA_OP 0x8d
|
|
#define POP_OP 0x58
|
|
#define RET_OP 0xc3
|
|
|
|
BOOLEAN
|
|
RtlpUnwindPrologueAmd64 (
|
|
IN ULONG64 ImageBase,
|
|
IN ULONG64 ControlPc,
|
|
IN ULONG64 FrameBase,
|
|
IN _PIMAGE_RUNTIME_FUNCTION_ENTRY FunctionEntry,
|
|
IN OUT PAMD64_CONTEXT ContextRecord,
|
|
IN HANDLE Process,
|
|
IN PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory
|
|
)
|
|
|
|
/*++
|
|
|
|
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.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
ULONG64 FloatingAddress;
|
|
PAMD64_M128 FloatingRegister;
|
|
ULONG FrameOffset;
|
|
ULONG Index;
|
|
ULONG64 IntegerAddress;
|
|
PULONG64 IntegerRegister;
|
|
BOOLEAN MachineFrame;
|
|
ULONG OpInfo;
|
|
ULONG PrologOffset;
|
|
PULONG64 RegisterAddress;
|
|
ULONG64 ReturnAddress;
|
|
ULONG64 StackAddress;
|
|
PAMD64_UNWIND_CODE UnwindCode;
|
|
ULONG64 UnwindInfoBuffer[32];
|
|
PAMD64_UNWIND_INFO UnwindInfo;
|
|
ULONG Done;
|
|
ULONG UnwindOp;
|
|
|
|
//
|
|
// Process the unwind codes.
|
|
//
|
|
|
|
FloatingRegister = &ContextRecord->Xmm0;
|
|
IntegerRegister = &ContextRecord->Rax;
|
|
Index = 0;
|
|
MachineFrame = FALSE;
|
|
PrologOffset = (ULONG)(ControlPc - (FunctionEntry->BeginAddress + ImageBase));
|
|
|
|
WDB((1, "Prol: RIP %I64X, 0x%X bytes in function at %I64X\n",
|
|
ControlPc, PrologOffset, FunctionEntry->BeginAddress + ImageBase));
|
|
WDB((1, "Prol: Read unwind info at %I64X\n",
|
|
FunctionEntry->UnwindInfoAddress + ImageBase));
|
|
|
|
UnwindInfo =
|
|
ReadUnwindInfoAmd64(ImageBase, FunctionEntry->UnwindInfoAddress,
|
|
TRUE, Process, ReadMemory, UnwindInfoBuffer,
|
|
sizeof(UnwindInfoBuffer));
|
|
if (UnwindInfo == NULL) {
|
|
WDB((1, "Prol: Unable to read unwind info\n"));
|
|
return FALSE;
|
|
}
|
|
|
|
WDB((1, " Unwind info has 0x%X codes\n", UnwindInfo->CountOfCodes));
|
|
|
|
while (Index < UnwindInfo->CountOfCodes) {
|
|
|
|
WDB((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;
|
|
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 (!ReadMemory(Process, IntegerAddress,
|
|
&IntegerRegister[OpInfo], sizeof(ULONG64),
|
|
&Done) ||
|
|
Done != sizeof(ULONG64)) {
|
|
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 (!ReadMemory(Process, IntegerAddress,
|
|
&IntegerRegister[OpInfo], sizeof(ULONG64),
|
|
&Done) ||
|
|
Done != sizeof(ULONG64)) {
|
|
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 (!ReadMemory(Process, IntegerAddress,
|
|
&IntegerRegister[OpInfo], sizeof(ULONG64),
|
|
&Done) ||
|
|
Done != sizeof(ULONG64)) {
|
|
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 (!ReadMemory(Process, FloatingAddress,
|
|
&FloatingRegister[OpInfo].Low, sizeof(ULONG64),
|
|
&Done) ||
|
|
Done != sizeof(ULONG64)) {
|
|
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 (!ReadMemory(Process, FloatingAddress,
|
|
&FloatingRegister[OpInfo].Low, sizeof(ULONG64),
|
|
&Done) ||
|
|
Done != sizeof(ULONG64)) {
|
|
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 (!ReadMemory(Process, FloatingAddress,
|
|
&FloatingRegister[OpInfo], sizeof(AMD64_M128),
|
|
&Done) ||
|
|
Done != sizeof(AMD64_M128)) {
|
|
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 (!ReadMemory(Process, FloatingAddress,
|
|
&FloatingRegister[OpInfo], sizeof(AMD64_M128),
|
|
&Done) ||
|
|
Done != sizeof(AMD64_M128)) {
|
|
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;
|
|
}
|
|
|
|
if (!ReadMemory(Process, ReturnAddress,
|
|
&ContextRecord->Rip, sizeof(ULONG64),
|
|
&Done) ||
|
|
Done != sizeof(ULONG64)) {
|
|
goto Fail;
|
|
}
|
|
if (!ReadMemory(Process, StackAddress,
|
|
&ContextRecord->Rsp, sizeof(ULONG64),
|
|
&Done) ||
|
|
Done != sizeof(ULONG64)) {
|
|
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 += RtlpUnwindOpSlotTableAmd64[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) {
|
|
Index = UnwindInfo->CountOfCodes;
|
|
if ((Index & 1) != 0) {
|
|
Index += 1;
|
|
}
|
|
|
|
ULONG64 ChainEntryAddr =
|
|
*(PULONG64)(&UnwindInfo->UnwindCode[Index]) + ImageBase;
|
|
|
|
if (UnwindInfo != (PAMD64_UNWIND_INFO)UnwindInfoBuffer) {
|
|
MemFree(UnwindInfo);
|
|
}
|
|
|
|
_IMAGE_RUNTIME_FUNCTION_ENTRY ChainEntry;
|
|
|
|
WDB((1, " Chain to entry at %I64X\n", ChainEntryAddr));
|
|
|
|
if (!ReadMemory(Process, ChainEntryAddr,
|
|
&ChainEntry, sizeof(ChainEntry), &Done) ||
|
|
Done != sizeof(ChainEntry)) {
|
|
WDB((1, " Unable to read entry\n"));
|
|
return FALSE;
|
|
}
|
|
|
|
return RtlpUnwindPrologueAmd64(ImageBase,
|
|
ControlPc,
|
|
FrameBase,
|
|
&ChainEntry,
|
|
ContextRecord,
|
|
Process,
|
|
ReadMemory);
|
|
|
|
} else {
|
|
if (UnwindInfo != (PAMD64_UNWIND_INFO)UnwindInfoBuffer) {
|
|
MemFree(UnwindInfo);
|
|
}
|
|
|
|
if (MachineFrame == FALSE) {
|
|
if (!ReadMemory(Process, ContextRecord->Rsp,
|
|
&ContextRecord->Rip, sizeof(ULONG64),
|
|
&Done) ||
|
|
Done != sizeof(ULONG64)) {
|
|
return FALSE;
|
|
}
|
|
ContextRecord->Rsp += 8;
|
|
}
|
|
|
|
WDB((1, "Prol: Returning with RIP %I64X, RSP %I64X\n",
|
|
ContextRecord->Rip, ContextRecord->Rsp));
|
|
return TRUE;
|
|
}
|
|
|
|
Fail:
|
|
if (UnwindInfo != (PAMD64_UNWIND_INFO)UnwindInfoBuffer) {
|
|
MemFree(UnwindInfo);
|
|
}
|
|
WDB((1, "Prol: Unwind failed\n"));
|
|
return FALSE;
|
|
}
|
|
|
|
BOOLEAN
|
|
RtlVirtualUnwindAmd64 (
|
|
IN ULONG64 ImageBase,
|
|
IN ULONG64 ControlPc,
|
|
IN _PIMAGE_RUNTIME_FUNCTION_ENTRY FunctionEntry,
|
|
IN OUT PAMD64_CONTEXT ContextRecord,
|
|
OUT PULONG64 EstablisherFrame,
|
|
IN HANDLE Process,
|
|
IN PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory
|
|
)
|
|
|
|
/*++
|
|
|
|
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.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
ULONG64 BranchTarget;
|
|
LONG Displacement;
|
|
ULONG FrameRegister;
|
|
ULONG Index;
|
|
LOGICAL InEpilogue;
|
|
PULONG64 IntegerRegister;
|
|
PUCHAR NextByte;
|
|
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 atually 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.
|
|
//
|
|
|
|
UnwindInfo =
|
|
ReadUnwindInfoAmd64(ImageBase, FunctionEntry->UnwindInfoAddress,
|
|
FALSE, Process, ReadMemory, UnwindInfoBuffer,
|
|
sizeof(UnwindInfoBuffer));
|
|
if (UnwindInfo == NULL) {
|
|
return FALSE;
|
|
}
|
|
|
|
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.
|
|
UnwindInfo = ReadUnwindInfoAmd64(ImageBase,
|
|
FunctionEntry->UnwindInfoAddress,
|
|
TRUE, Process, ReadMemory,
|
|
UnwindInfoBuffer,
|
|
sizeof(UnwindInfoBuffer));
|
|
if (UnwindInfo == NULL) {
|
|
return FALSE;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if (UnwindInfo != (PAMD64_UNWIND_INFO)UnwindInfoBuffer) {
|
|
MemFree(UnwindInfo);
|
|
}
|
|
}
|
|
|
|
if (!ReadMemory(Process, ControlPc, InstrBuffer, sizeof(InstrBuffer),
|
|
&InstrBytes)) {
|
|
WDB((1, "Unable to read instruction stream at %I64X\n", ControlPc));
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Check for epilogue.
|
|
//
|
|
// 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 &&
|
|
((NextByte[0] & 0xf8) == SIZE64_PREFIX) &&
|
|
((NextByte[1] & 0xf8) == POP_OP)) {
|
|
|
|
NextByte += 2;
|
|
Bytes -= 2;
|
|
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the next instruction is a return, 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) {
|
|
|
|
//
|
|
// 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]);
|
|
}
|
|
|
|
//
|
|
// Now determine whether the branch target refers to code within this
|
|
// function. If not then it is an epilogue indicator.
|
|
//
|
|
|
|
if (BranchTarget <= FunctionEntry->BeginAddress ||
|
|
BranchTarget > FunctionEntry->EndAddress) {
|
|
|
|
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 >= 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 (!ReadMemory(Process, ContextRecord->Rsp,
|
|
&IntegerRegister[RegisterNumber],
|
|
sizeof(ULONG64), &Done) ||
|
|
Done != sizeof(ULONG64)) {
|
|
WDB((1, "Unable to read stack at %I64X\n",
|
|
ContextRecord->Rsp));
|
|
return FALSE;
|
|
}
|
|
ContextRecord->Rsp += 8;
|
|
NextByte += 1;
|
|
Bytes -= 1;
|
|
|
|
} else if (Bytes >= 2 &&
|
|
(NextByte[0] & 0xf8) == SIZE64_PREFIX &&
|
|
(NextByte[1] & 0xf8) == POP_OP) {
|
|
|
|
//
|
|
// pop nonvolatile-integer-register[8..15]
|
|
//
|
|
|
|
RegisterNumber = ((NextByte[0] & 1) << 3) | (NextByte[1] & 0x7);
|
|
if (!ReadMemory(Process, ContextRecord->Rsp,
|
|
&IntegerRegister[RegisterNumber],
|
|
sizeof(ULONG64), &Done) ||
|
|
Done != sizeof(ULONG64)) {
|
|
WDB((1, "Unable to read stack at %I64X\n",
|
|
ContextRecord->Rsp));
|
|
return FALSE;
|
|
}
|
|
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 (!ReadMemory(Process, ContextRecord->Rsp,
|
|
&ContextRecord->Rip, sizeof(ULONG64),
|
|
&Done) ||
|
|
Done != sizeof(ULONG64)) {
|
|
WDB((1, "Unable to read stack at %I64X\n",
|
|
ContextRecord->Rsp));
|
|
return FALSE;
|
|
}
|
|
ContextRecord->Rsp += 8;
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// Control left the specified function outside an epilogue. Unwind the
|
|
// subject function and any chained unwind information.
|
|
//
|
|
|
|
return RtlpUnwindPrologueAmd64(ImageBase,
|
|
ControlPc,
|
|
*EstablisherFrame,
|
|
FunctionEntry,
|
|
ContextRecord,
|
|
Process,
|
|
ReadMemory);
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
WalkAmd64(
|
|
HANDLE Process,
|
|
LPSTACKFRAME64 StackFrame,
|
|
PVOID ContextRecord,
|
|
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory,
|
|
PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccess,
|
|
PGET_MODULE_BASE_ROUTINE64 GetModuleBase
|
|
)
|
|
{
|
|
BOOL rval;
|
|
PAMD64_CONTEXT Context = (PAMD64_CONTEXT)ContextRecord;
|
|
|
|
WDB((2, "WalkAmd64 in: PC %I64X, SP %I64X, FP %I64X, RA %I64X\n",
|
|
StackFrame->AddrPC.Offset,
|
|
StackFrame->AddrStack.Offset,
|
|
StackFrame->AddrFrame.Offset,
|
|
StackFrame->AddrReturn.Offset));
|
|
|
|
if (StackFrame->Virtual) {
|
|
|
|
rval = WalkAmd64Next( Process,
|
|
StackFrame,
|
|
Context,
|
|
ReadMemory,
|
|
FunctionTableAccess,
|
|
GetModuleBase
|
|
);
|
|
|
|
} else {
|
|
|
|
rval = WalkAmd64Init( Process,
|
|
StackFrame,
|
|
Context,
|
|
ReadMemory,
|
|
FunctionTableAccess,
|
|
GetModuleBase
|
|
);
|
|
|
|
}
|
|
|
|
WDB((2, "WalkAmd64 out: succ %d, PC %I64X, SP %I64X, FP %I64X, RA %I64X\n",
|
|
rval,
|
|
StackFrame->AddrPC.Offset,
|
|
StackFrame->AddrStack.Offset,
|
|
StackFrame->AddrFrame.Offset,
|
|
StackFrame->AddrReturn.Offset));
|
|
|
|
return rval;
|
|
}
|
|
|
|
BOOL
|
|
UnwindStackFrameAmd64(
|
|
IN HANDLE Process,
|
|
IN OUT PULONG64 ReturnAddress,
|
|
IN OUT PULONG64 StackPointer,
|
|
IN OUT PULONG64 FramePointer,
|
|
IN PAMD64_CONTEXT Context, // Context members could be modified.
|
|
IN PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory,
|
|
IN PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccess,
|
|
IN PGET_MODULE_BASE_ROUTINE64 GetModuleBase
|
|
)
|
|
{
|
|
_PIMAGE_RUNTIME_FUNCTION_ENTRY FunctionEntry;
|
|
ULONG64 RetAddr;
|
|
BOOL Succ = TRUE;
|
|
|
|
FunctionEntry = (_PIMAGE_RUNTIME_FUNCTION_ENTRY)
|
|
FunctionTableAccess( Process, *ReturnAddress );
|
|
if (FunctionEntry != NULL) {
|
|
|
|
ULONG64 ImageBase;
|
|
// Initialized to quiet a PREfix warning.
|
|
ULONG64 EstablisherFrame = 0;
|
|
|
|
//
|
|
// 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.
|
|
//
|
|
|
|
ImageBase = GetModuleBase(Process, *ReturnAddress);
|
|
if (!RtlVirtualUnwindAmd64(ImageBase, *ReturnAddress, FunctionEntry,
|
|
Context, &EstablisherFrame,
|
|
Process, ReadMemory) ||
|
|
Context->Rip == 0 ||
|
|
(Context->Rip == *ReturnAddress &&
|
|
EstablisherFrame == *FramePointer)) {
|
|
Succ = FALSE;
|
|
}
|
|
|
|
*ReturnAddress = Context->Rip;
|
|
*StackPointer = Context->Rsp;
|
|
// 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.
|
|
*FramePointer = Context->Rsp - 2 * sizeof(ULONG64);
|
|
|
|
} else {
|
|
|
|
ULONG Done;
|
|
|
|
// If there's no function entry for a function
|
|
// we assume that it's a leaf and that ESP points
|
|
// directly to the return address. There's no
|
|
// stored frame pointer so we actually need to
|
|
// set a virtual frame pointer deeper in the stack
|
|
// so that arguments can correctly be read at
|
|
// two ULONG64's up from it.
|
|
*FramePointer = Context->Rsp - 8;
|
|
*StackPointer = Context->Rsp + 8;
|
|
Succ = ReadMemory(Process, Context->Rsp,
|
|
ReturnAddress, sizeof(*ReturnAddress), &Done) &&
|
|
Done == sizeof(*ReturnAddress);
|
|
|
|
// Update the context values to what they should be in
|
|
// the caller.
|
|
if (Succ) {
|
|
Context->Rsp += 8;
|
|
Context->Rip = *ReturnAddress;
|
|
}
|
|
}
|
|
|
|
if (Succ) {
|
|
ULONG64 CallOffset;
|
|
_PIMAGE_RUNTIME_FUNCTION_ENTRY CallFunc;
|
|
|
|
//
|
|
// Calls of __declspec(noreturn) functions may not have any
|
|
// code after them to return to since the compiler knows
|
|
// that the function will not return. This can confuse
|
|
// stack traces because the return address will lie outside
|
|
// of the function's address range and FPO data will not
|
|
// be looked up correctly. Check and see if the return
|
|
// address falls outside of the calling function and, if so,
|
|
// adjust the return address back by one byte. It'd be
|
|
// better to adjust it back to the call itself so that
|
|
// the return address points to valid code but
|
|
// backing up in X86 assembly is more or less impossible.
|
|
//
|
|
|
|
CallOffset = *ReturnAddress - 1;
|
|
CallFunc = (_PIMAGE_RUNTIME_FUNCTION_ENTRY)
|
|
FunctionTableAccess(Process, CallOffset);
|
|
if (CallFunc != NULL) {
|
|
_IMAGE_RUNTIME_FUNCTION_ENTRY SaveCallFunc = *CallFunc;
|
|
_PIMAGE_RUNTIME_FUNCTION_ENTRY RetFunc =
|
|
(_PIMAGE_RUNTIME_FUNCTION_ENTRY)
|
|
FunctionTableAccess(Process, *ReturnAddress);
|
|
if (RetFunc == NULL ||
|
|
memcmp(&SaveCallFunc, RetFunc, sizeof(SaveCallFunc))) {
|
|
*ReturnAddress = CallOffset;
|
|
}
|
|
}
|
|
}
|
|
|
|
return Succ;
|
|
}
|
|
|
|
BOOL
|
|
ReadFrameArgsAmd64(
|
|
LPADDRESS64 FrameOffset,
|
|
HANDLE Process,
|
|
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory,
|
|
PULONG64 Args
|
|
)
|
|
{
|
|
ULONG Done;
|
|
|
|
if (!ReadMemory(Process, FrameOffset->Offset + 2 * sizeof(ULONG64),
|
|
Args, 4 * sizeof(ULONG64), &Done)) {
|
|
Done = 0;
|
|
}
|
|
|
|
ZeroMemory((PUCHAR)Args + Done, 4 * sizeof(ULONG64) - Done);
|
|
|
|
return Done > 0;
|
|
}
|
|
|
|
BOOL
|
|
WalkAmd64Init(
|
|
HANDLE Process,
|
|
LPSTACKFRAME64 StackFrame,
|
|
PAMD64_CONTEXT Context,
|
|
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory,
|
|
PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccess,
|
|
PGET_MODULE_BASE_ROUTINE64 GetModuleBase
|
|
)
|
|
{
|
|
AMD64_CONTEXT ContextSave;
|
|
DWORD64 PcOffset;
|
|
DWORD64 StackOffset;
|
|
DWORD64 FrameOffset;
|
|
|
|
ZeroMemory( &StackFrame->AddrBStore, sizeof(StackFrame->AddrBStore) );
|
|
StackFrame->FuncTableEntry = NULL;
|
|
ZeroMemory( StackFrame->Params, sizeof(StackFrame->Params) );
|
|
StackFrame->Far = FALSE;
|
|
StackFrame->Virtual = TRUE;
|
|
ZeroMemory( StackFrame->Reserved, sizeof(StackFrame->Reserved) );
|
|
|
|
if (!StackFrame->AddrPC.Offset) {
|
|
StackFrame->AddrPC.Offset = Context->Rip;
|
|
StackFrame->AddrPC.Mode = AddrModeFlat;
|
|
}
|
|
|
|
if (!StackFrame->AddrStack.Offset) {
|
|
StackFrame->AddrStack.Offset = Context->Rsp;
|
|
StackFrame->AddrStack.Mode = AddrModeFlat;
|
|
}
|
|
|
|
if (!StackFrame->AddrFrame.Offset) {
|
|
StackFrame->AddrFrame.Offset = Context->Rbp;
|
|
StackFrame->AddrFrame.Mode = AddrModeFlat;
|
|
}
|
|
|
|
if ((StackFrame->AddrPC.Mode != AddrModeFlat) ||
|
|
(StackFrame->AddrStack.Mode != AddrModeFlat) ||
|
|
(StackFrame->AddrFrame.Mode != AddrModeFlat)) {
|
|
return FALSE;
|
|
}
|
|
|
|
PcOffset = StackFrame->AddrPC.Offset;
|
|
StackOffset = StackFrame->AddrStack.Offset;
|
|
FrameOffset = StackFrame->AddrFrame.Offset;
|
|
|
|
ContextSave = *Context;
|
|
ContextSave.Rip = PcOffset;
|
|
ContextSave.Rsp = StackOffset;
|
|
ContextSave.Rbp = FrameOffset;
|
|
|
|
if (!UnwindStackFrameAmd64( Process,
|
|
&PcOffset,
|
|
&StackOffset,
|
|
&FrameOffset,
|
|
&ContextSave,
|
|
ReadMemory,
|
|
FunctionTableAccess,
|
|
GetModuleBase)) {
|
|
return FALSE;
|
|
}
|
|
|
|
StackFrame->AddrReturn.Offset = PcOffset;
|
|
StackFrame->AddrReturn.Mode = AddrModeFlat;
|
|
|
|
StackFrame->AddrFrame.Offset = FrameOffset;
|
|
ReadFrameArgsAmd64(&StackFrame->AddrFrame, Process,
|
|
ReadMemory, StackFrame->Params);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOL
|
|
WalkAmd64Next(
|
|
HANDLE Process,
|
|
LPSTACKFRAME64 StackFrame,
|
|
PAMD64_CONTEXT Context,
|
|
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory,
|
|
PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccess,
|
|
PGET_MODULE_BASE_ROUTINE64 GetModuleBase
|
|
)
|
|
{
|
|
DWORD Done;
|
|
BOOL Succ = TRUE;
|
|
DWORD64 StackAddress;
|
|
_PIMAGE_RUNTIME_FUNCTION_ENTRY FunctionEntry;
|
|
|
|
if (!UnwindStackFrameAmd64( Process,
|
|
&StackFrame->AddrPC.Offset,
|
|
&StackFrame->AddrStack.Offset,
|
|
&StackFrame->AddrFrame.Offset,
|
|
Context,
|
|
ReadMemory,
|
|
FunctionTableAccess,
|
|
GetModuleBase)) {
|
|
Succ = FALSE;
|
|
|
|
//
|
|
// If the frame could not be unwound or is terminal, see if
|
|
// there is a callback frame:
|
|
//
|
|
|
|
if (g.AppVersion.Revision >= 4 && CALLBACK_STACK(StackFrame)) {
|
|
DWORD64 ImageBase;
|
|
|
|
if (CALLBACK_STACK(StackFrame) & 0x80000000) {
|
|
|
|
//
|
|
// it is the pointer to the stack frame that we want
|
|
//
|
|
|
|
StackAddress = CALLBACK_STACK(StackFrame);
|
|
|
|
} else {
|
|
|
|
//
|
|
// if it is a positive integer, it is the offset to
|
|
// the address in the thread.
|
|
// Look up the pointer:
|
|
//
|
|
|
|
Succ = ReadMemory(Process,
|
|
(CALLBACK_THREAD(StackFrame) +
|
|
CALLBACK_STACK(StackFrame)),
|
|
&StackAddress,
|
|
sizeof(StackAddress),
|
|
&Done);
|
|
if (!Succ || Done != sizeof(StackAddress) ||
|
|
StackAddress == 0) {
|
|
StackAddress = (DWORD64)-1;
|
|
CALLBACK_STACK(StackFrame) = (DWORD)-1;
|
|
}
|
|
}
|
|
|
|
if ((StackAddress == (DWORD64)-1) ||
|
|
(!(FunctionEntry = (_PIMAGE_RUNTIME_FUNCTION_ENTRY)
|
|
FunctionTableAccess(Process, CALLBACK_FUNC(StackFrame))) ||
|
|
!(ImageBase = GetModuleBase(Process,
|
|
CALLBACK_FUNC(StackFrame))))) {
|
|
|
|
Succ = FALSE;
|
|
|
|
} else {
|
|
|
|
if (!ReadMemory(Process,
|
|
(StackAddress + CALLBACK_NEXT(StackFrame)),
|
|
&CALLBACK_STACK(StackFrame),
|
|
sizeof(DWORD64),
|
|
&Done) ||
|
|
Done != sizeof(DWORD64)) {
|
|
Succ = FALSE;
|
|
} else {
|
|
StackFrame->AddrPC.Offset =
|
|
ImageBase + FunctionEntry->BeginAddress;
|
|
StackFrame->AddrStack.Offset = StackAddress;
|
|
Context->Rsp = StackAddress;
|
|
|
|
Succ = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Succ) {
|
|
AMD64_CONTEXT ContextSave;
|
|
ULONG64 StackOffset = 0;
|
|
ULONG64 FrameOffset = 0;
|
|
|
|
//
|
|
// Get the return address.
|
|
//
|
|
ContextSave = *Context;
|
|
StackFrame->AddrReturn.Offset = StackFrame->AddrPC.Offset;
|
|
|
|
if (!UnwindStackFrameAmd64( Process,
|
|
&StackFrame->AddrReturn.Offset,
|
|
&StackOffset,
|
|
&FrameOffset,
|
|
&ContextSave,
|
|
ReadMemory,
|
|
FunctionTableAccess,
|
|
GetModuleBase)) {
|
|
StackFrame->AddrReturn.Offset = 0;
|
|
}
|
|
|
|
StackFrame->AddrFrame.Offset = FrameOffset;
|
|
ReadFrameArgsAmd64(&StackFrame->AddrFrame, Process, ReadMemory,
|
|
StackFrame->Params);
|
|
}
|
|
|
|
return Succ;
|
|
}
|