Leaked source code of windows server 2003
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.
 
 
 
 
 
 

3003 lines
92 KiB

/*++
Copyright (c) 1993-2002 Microsoft Corporation
Module Name:
walkx86.c
Abstract:
This file implements the Intel x86 stack walking api. This api allows for
the presence of "real mode" stack frames. This means that you can trace
into WOW code.
Author:
Wesley Witt (wesw) 1-Oct-1993
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 <objbase.h>
#include <wx86dll.h>
#include <symbols.h>
#include <globals.h>
#include "dia2.h"
#define WDB(Args) SdbOut Args
#define SAVE_EBP(f) (f->Reserved[0])
#define IS_EBP_SAVED(f) (f->Reserved[0] && ((f->Reserved[0] >> 32) != 0xEB))
#define TRAP_TSS(f) (f->Reserved[1])
#define TRAP_EDITED(f) (f->Reserved[1])
#define SAVE_TRAP(f) (f->Reserved[2])
#define CALLBACK_STACK(f) (f->KdHelp.ThCallbackStack)
#define CALLBACK_NEXT(f) (f->KdHelp.NextCallback)
#define CALLBACK_FUNC(f) (f->KdHelp.KiCallUserMode)
#define CALLBACK_THREAD(f) (f->KdHelp.Thread)
#define CALLBACK_FP(f) (f->KdHelp.FramePointer)
#define CALLBACK_DISPATCHER(f) (f->KdHelp.KeUserCallbackDispatcher)
#define SYSTEM_RANGE_START(f) (f->KdHelp.SystemRangeStart)
#define STACK_SIZE (sizeof(DWORD))
#define FRAME_SIZE (STACK_SIZE * 2)
#define STACK_SIZE16 (sizeof(WORD))
#define FRAME_SIZE16 (STACK_SIZE16 * 2)
#define FRAME_SIZE1632 (STACK_SIZE16 * 3)
#define MAX_STACK_SEARCH 64 // in STACK_SIZE units
#define MAX_JMP_CHAIN 64 // in STACK_SIZE units
#define MAX_CALL 7 // in bytes
#define MIN_CALL 2 // in bytes
#define MAX_FUNC_PROLOGUE 64 // in bytes
#define PUSHBP 0x55
#define MOVBPSP 0xEC8B
ULONG g_vc7fpo = 1;
#define DoMemoryRead(addr,buf,sz,br) \
ReadMemoryInternal( Process, Thread, addr, buf, sz, \
br, ReadMemory, TranslateAddress, FALSE )
#define DoMemoryReadAll(addr,buf,sz) \
ReadMemoryInternal( Process, Thread, addr, buf, sz, \
NULL, ReadMemory, TranslateAddress, TRUE )
BOOL
WalkX86Init(
HANDLE Process,
HANDLE Thread,
LPSTACKFRAME64 StackFrame,
PX86_CONTEXT ContextRecord,
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory,
PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccess,
PGET_MODULE_BASE_ROUTINE64 GetModuleBase,
PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress
);
BOOL
WalkX86Next(
HANDLE Process,
HANDLE Thread,
LPSTACKFRAME64 StackFrame,
PX86_CONTEXT ContextRecord,
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory,
PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccess,
PGET_MODULE_BASE_ROUTINE64 GetModuleBase,
PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress
);
BOOL
ReadMemoryInternal(
HANDLE Process,
HANDLE Thread,
LPADDRESS64 lpBaseAddress,
LPVOID lpBuffer,
DWORD nSize,
LPDWORD lpNumberOfBytesRead,
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory,
PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress,
BOOL MustReadAll
);
BOOL
IsFarCall(
HANDLE Process,
HANDLE Thread,
LPSTACKFRAME64 StackFrame,
BOOL *Ok,
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory,
PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress
);
BOOL
ReadTrapFrame(
HANDLE Process,
DWORD64 TrapFrameAddress,
PX86_KTRAP_FRAME TrapFrame,
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory
);
BOOL
TaskGate2TrapFrame(
HANDLE Process,
USHORT TaskRegister,
PX86_KTRAP_FRAME TrapFrame,
PULONG64 off,
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory
);
DWORD64
SearchForReturnAddress(
HANDLE Process,
DWORD64 uoffStack,
DWORD64 funcAddr,
DWORD funcSize,
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory,
PGET_MODULE_BASE_ROUTINE64 GetModuleBase,
BOOL AcceptUnreadableCallSite
);
//----------------------------------------------------------------------------
//
// DIA IDiaStackWalkFrame implementation.
//
//----------------------------------------------------------------------------
class X86WalkFrame : public IDiaStackWalkFrame
{
public:
X86WalkFrame(HANDLE Process,
X86_CONTEXT* Context,
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory,
PGET_MODULE_BASE_ROUTINE64 GetModuleBase,
PFPO_DATA PreviousFpo)
{
m_Process = Process;
m_Context = Context;
m_ReadMemory = ReadMemory;
m_GetModuleBase = GetModuleBase;
m_Locals = 0;
m_Params = 0;
m_VirtFrame = Context->Ebp;
m_PreviousFpo = PreviousFpo;
m_EbpSet = FALSE;
}
// IUnknown.
STDMETHOD(QueryInterface)(
THIS_
IN REFIID InterfaceId,
OUT PVOID* Interface
);
STDMETHOD_(ULONG, AddRef)(
THIS
);
STDMETHOD_(ULONG, Release)(
THIS
);
// IDiaStackWalkFrame.
STDMETHOD(get_registerValue)(DWORD reg, ULONGLONG* pValue);
STDMETHOD(put_registerValue)(DWORD reg, ULONGLONG value);
STDMETHOD(readMemory)(ULONGLONG va, DWORD cbData,
DWORD* pcbData, BYTE* data);
STDMETHOD(searchForReturnAddress)(IDiaFrameData* frame,
ULONGLONG* pResult);
STDMETHOD(searchForReturnAddressStart)(IDiaFrameData* frame,
ULONGLONG startAddress,
ULONGLONG* pResult);
BOOL WasEbpSet(void)
{
return m_EbpSet;
}
private:
HANDLE m_Process;
X86_CONTEXT* m_Context;
PREAD_PROCESS_MEMORY_ROUTINE64 m_ReadMemory;
PGET_MODULE_BASE_ROUTINE64 m_GetModuleBase;
ULONGLONG m_Locals;
ULONGLONG m_Params;
ULONGLONG m_VirtFrame;
PFPO_DATA m_PreviousFpo;
BOOL m_EbpSet;
};
STDMETHODIMP
X86WalkFrame::QueryInterface(
THIS_
IN REFIID InterfaceId,
OUT PVOID* Interface
)
{
HRESULT Status;
*Interface = NULL;
Status = E_NOINTERFACE;
if (IsEqualIID(InterfaceId, IID_IDiaStackWalkFrame)) {
*Interface = (IDiaStackWalkFrame*)this;
Status = S_OK;
}
return Status;
}
STDMETHODIMP_(ULONG)
X86WalkFrame::AddRef(
THIS
)
{
// Stack allocated, no refcount.
return 1;
}
STDMETHODIMP_(ULONG)
X86WalkFrame::Release(
THIS
)
{
// Stack allocated, no refcount.
return 0;
}
STDMETHODIMP
X86WalkFrame::get_registerValue( DWORD reg, ULONGLONG* pVal )
{
switch( reg ) {
// debug registers
case CV_REG_DR0:
*pVal = m_Context->Dr0;
break;
case CV_REG_DR1:
*pVal = m_Context->Dr1;
break;
case CV_REG_DR2:
*pVal = m_Context->Dr2;
break;
case CV_REG_DR3:
*pVal = m_Context->Dr3;
break;
case CV_REG_DR6:
*pVal = m_Context->Dr6;
break;
case CV_REG_DR7:
*pVal = m_Context->Dr7;
break;
// segment registers
case CV_REG_GS:
*pVal = m_Context->SegGs;
break;
case CV_REG_FS:
*pVal = m_Context->SegFs;
break;
case CV_REG_ES:
*pVal = m_Context->SegEs;
break;
case CV_REG_DS:
*pVal = m_Context->SegDs;
break;
// integer registers
case CV_REG_EDI:
*pVal = m_Context->Edi;
break;
case CV_REG_ESI:
*pVal = m_Context->Esi;
break;
case CV_REG_EBX:
*pVal = m_Context->Ebx;
break;
case CV_REG_EDX:
*pVal = m_Context->Edx;
break;
case CV_REG_ECX:
*pVal = m_Context->Ecx;
break;
case CV_REG_EAX:
*pVal = m_Context->Eax;
break;
// control registers
case CV_REG_EBP:
*pVal = m_Context->Ebp;
break;
case CV_REG_EIP:
*pVal = m_Context->Eip;
break;
case CV_REG_CS:
*pVal = m_Context->SegCs;
break;
case CV_REG_EFLAGS:
*pVal = m_Context->EFlags;
break;
case CV_REG_ESP:
*pVal = m_Context->Esp;
break;
case CV_REG_SS:
*pVal = m_Context->SegSs;
break;
case CV_ALLREG_LOCALS:
*pVal = m_Locals;
break;
case CV_ALLREG_PARAMS:
*pVal = m_Params;
break;
case CV_ALLREG_VFRAME:
*pVal = m_VirtFrame;
break;
default:
*pVal = 0;
return E_FAIL;
}
return S_OK;
}
STDMETHODIMP
X86WalkFrame::put_registerValue( DWORD reg, ULONGLONG LongVal )
{
ULONG val = (ULONG)LongVal;
switch( reg ) {
// debug registers
case CV_REG_DR0:
m_Context->Dr0 = val;
break;
case CV_REG_DR1:
m_Context->Dr1 = val;
break;
case CV_REG_DR2:
m_Context->Dr2 = val;
break;
case CV_REG_DR3:
m_Context->Dr3 = val;
break;
case CV_REG_DR6:
m_Context->Dr6 = val;
break;
case CV_REG_DR7:
m_Context->Dr7 = val;
break;
// segment registers
case CV_REG_GS:
m_Context->SegGs = val;
break;
case CV_REG_FS:
m_Context->SegFs = val;
break;
case CV_REG_ES:
m_Context->SegEs = val;
break;
case CV_REG_DS:
m_Context->SegDs = val;
break;
// integer registers
case CV_REG_EDI:
m_Context->Edi = val;
break;
case CV_REG_ESI:
m_Context->Esi = val;
break;
case CV_REG_EBX:
m_Context->Ebx = val;
break;
case CV_REG_EDX:
m_Context->Edx = val;
break;
case CV_REG_ECX:
m_Context->Ecx = val;
break;
case CV_REG_EAX:
m_Context->Eax = val;
break;
// control registers
case CV_REG_EBP:
m_Context->Ebp = val;
m_EbpSet = TRUE;
break;
case CV_REG_EIP:
m_Context->Eip = val;
break;
case CV_REG_CS:
m_Context->SegCs = val;
break;
case CV_REG_EFLAGS:
m_Context->EFlags = val;
break;
case CV_REG_ESP:
m_Context->Esp = val;
break;
case CV_REG_SS:
m_Context->SegSs = val;
break;
case CV_ALLREG_LOCALS:
m_Locals = val;
break;
case CV_ALLREG_PARAMS:
m_Params = val;
break;
case CV_ALLREG_VFRAME:
m_VirtFrame = val;
break;
default:
return E_FAIL;
}
return S_OK;
}
STDMETHODIMP
X86WalkFrame::readMemory(ULONGLONG va, DWORD cbData,
DWORD* pcbData, BYTE* data)
{
return m_ReadMemory( m_Process, va, data, cbData, pcbData ) != 0 ?
S_OK : E_FAIL;
}
STDMETHODIMP
X86WalkFrame::searchForReturnAddress(IDiaFrameData* frame,
ULONGLONG* pResult)
{
HRESULT Status;
DWORD LenLocals, LenRegs;
if ((Status = frame->get_lengthLocals(&LenLocals)) != S_OK ||
(Status = frame->get_lengthSavedRegisters(&LenRegs)) != S_OK) {
return Status;
}
return searchForReturnAddressStart(frame,
EXTEND64(m_Context->Esp +
LenLocals + LenRegs),
pResult);
}
STDMETHODIMP
X86WalkFrame::searchForReturnAddressStart(IDiaFrameData* DiaFrame,
ULONGLONG StartAddress,
ULONGLONG* Result)
{
HRESULT Status;
BOOL HasSeh, IsFuncStart;
IDiaFrameData* OrigFrame = DiaFrame;
IDiaFrameData* NextFrame;
DWORD LenLocals, LenRegs, LenParams = 0;
if (m_PreviousFpo &&
m_PreviousFpo->cbFrame != FRAME_TRAP &&
m_PreviousFpo->cbFrame != FRAME_TSS) {
//
// if the previous frame had an fpo record, we can account
// for its parameters
//
LenParams = m_PreviousFpo->cdwParams * STACK_SIZE;
}
if ((Status = DiaFrame->get_lengthLocals(&LenLocals)) != S_OK ||
(Status = DiaFrame->get_lengthSavedRegisters(&LenRegs)) != S_OK ||
(Status = DiaFrame->get_systemExceptionHandling(&HasSeh)) != S_OK ||
(Status = DiaFrame->get_functionStart(&IsFuncStart)) != S_OK) {
return Status;
}
if ((!HasSeh || IsFuncStart) &&
m_Context->Esp + LenLocals + LenRegs + LenParams >
(ULONG) StartAddress) {
StartAddress =
EXTEND64(m_Context->Esp + LenLocals + LenRegs + LenParams);
}
//
// This frame data may be a subsidiary descriptor. Move up
// the parent chain to the true function start.
//
while (DiaFrame->get_functionParent(&NextFrame) == S_OK) {
if (DiaFrame != OrigFrame) {
DiaFrame->Release();
}
DiaFrame = NextFrame;
}
ULONGLONG FuncStart;
DWORD LenFunc;
if ((Status = DiaFrame->get_virtualAddress(&FuncStart)) == S_OK) {
Status = DiaFrame->get_lengthBlock(&LenFunc);
}
if (DiaFrame != OrigFrame) {
DiaFrame->Release();
}
if (Status != S_OK) {
return Status;
}
*Result = SearchForReturnAddress(m_Process,
StartAddress,
FuncStart,
LenFunc,
m_ReadMemory,
m_GetModuleBase,
TRUE);
return *Result != 0 ? S_OK : E_FAIL;
}
//----------------------------------------------------------------------------
//
// Walk functions.
//
//----------------------------------------------------------------------------
BOOL
WalkX86(
HANDLE Process,
HANDLE Thread,
LPSTACKFRAME64 StackFrame,
PVOID ContextRecord,
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory,
PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccess,
PGET_MODULE_BASE_ROUTINE64 GetModuleBase,
PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress,
DWORD flags
)
{
BOOL rval;
WDB((2, "WalkX86 in: PC %X, SP %X, FP %X, RA %X\n",
(ULONG)StackFrame->AddrPC.Offset,
(ULONG)StackFrame->AddrStack.Offset,
(ULONG)StackFrame->AddrFrame.Offset,
(ULONG)StackFrame->AddrReturn.Offset));
if (StackFrame->Virtual) {
rval = WalkX86Next( Process,
Thread,
StackFrame,
(PX86_CONTEXT)ContextRecord,
ReadMemory,
FunctionTableAccess,
GetModuleBase,
TranslateAddress
);
} else {
rval = WalkX86Init( Process,
Thread,
StackFrame,
(PX86_CONTEXT)ContextRecord,
ReadMemory,
FunctionTableAccess,
GetModuleBase,
TranslateAddress
);
}
WDB((2, "WalkX86 out: PC %X, SP %X, FP %X, RA %X\n",
(ULONG)StackFrame->AddrPC.Offset,
(ULONG)StackFrame->AddrStack.Offset,
(ULONG)StackFrame->AddrFrame.Offset,
(ULONG)StackFrame->AddrReturn.Offset));
// This hack fixes the fpo stack when ebp wasn't used.
// Don't put this fix into StackWalk() or it will break MSDEV.
#if 0
if (rval && (flags & WALK_FIX_FPO_EBP)) {
PFPO_DATA pFpo = (PFPO_DATA)StackFrame->FuncTableEntry;
if (pFpo && !pFpo->fUseBP) {
StackFrame->AddrFrame.Offset += 4;
}
}
#endif
return rval;
}
BOOL
ReadMemoryInternal(
HANDLE Process,
HANDLE Thread,
LPADDRESS64 lpBaseAddress,
LPVOID lpBuffer,
DWORD nSize,
LPDWORD lpNumberOfBytesRead,
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory,
PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress,
BOOL MustReadAll
)
{
ADDRESS64 addr;
DWORD LocalBytesRead = 0;
BOOL Succ;
addr = *lpBaseAddress;
if (addr.Mode != AddrModeFlat) {
TranslateAddress( Process, Thread, &addr );
}
Succ = ReadMemory( Process,
addr.Offset,
lpBuffer,
nSize,
&LocalBytesRead
);
if (lpNumberOfBytesRead) {
*lpNumberOfBytesRead = LocalBytesRead;
}
return (Succ && MustReadAll) ? (LocalBytesRead == nSize) : Succ;
}
DWORD64
SearchForReturnAddress(
HANDLE Process,
DWORD64 uoffStack,
DWORD64 funcAddr,
DWORD funcSize,
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory,
PGET_MODULE_BASE_ROUTINE64 GetModuleBase,
BOOL AcceptUnreadableCallSite
)
{
DWORD64 uoffRet;
DWORD64 uoffBestGuess = 0;
DWORD cdwIndex;
DWORD cdwIndexMax;
INT cbIndex;
INT cbLimit;
DWORD cBytes;
DWORD cJmpChain = 0;
DWORD64 uoffT;
DWORD cb;
BYTE jmpBuffer[ sizeof(WORD) + sizeof(DWORD) ];
LPWORD lpwJmp = (LPWORD)&jmpBuffer[0];
BYTE code[MAX_CALL];
DWORD stack [ MAX_STACK_SEARCH ];
BOPINSTR BopInstr;
WDB((1, " SearchForReturnAddress: start %X\n", (ULONG)uoffStack));
//
// this function is necessary for 4 reasons:
//
// 1) random compiler bugs where regs are saved on the
// stack but the fpo data does not account for them
//
// 2) inline asm code that does a push
//
// 3) any random code that does a push and it isn't
// accounted for in the fpo data
//
// 4) non-void non-fpo functions
// *** This case is not neccessary when the compiler
// emits FPO records for non-FPO funtions. Unfortunately
// only the NT group uses this feature.
//
if (!ReadMemory(Process,
uoffStack,
stack,
sizeof(stack),
&cb)) {
WDB((1, " can't read stack\n"));
return 0;
}
cdwIndexMax = cb / STACK_SIZE;
if ( !cdwIndexMax ) {
WDB((1, " can't read stack\n"));
return 0;
}
for ( cdwIndex=0; cdwIndex<cdwIndexMax; cdwIndex++,uoffStack+=STACK_SIZE ) {
uoffRet = (DWORD64)(LONG64)(LONG)stack[cdwIndex];
//
// Don't try looking for Code in the first 64K of an NT app.
//
if ( uoffRet < 0x00010000 ) {
continue;
}
//
// if it isn't part of any known address space it must be bogus
//
if (GetModuleBase( Process, uoffRet ) == 0) {
continue;
}
//
// Check for a BOP instruction.
//
if (ReadMemory(Process,
uoffRet - sizeof(BOPINSTR),
&BopInstr,
sizeof(BOPINSTR),
&cb)) {
if (cb == sizeof(BOPINSTR) &&
BopInstr.Instr1 == 0xc4 && BopInstr.Instr2 == 0xc4) {
WDB((1, " BOP, use %X\n", (ULONG)uoffStack));
return uoffStack;
}
}
//
// Read the maximum number of bytes a call could be from the istream
//
cBytes = MAX_CALL;
if (!ReadMemory(Process,
uoffRet - cBytes,
code,
cBytes,
&cb)) {
//
// if page is not present, we will ALWAYS mess up by
// continuing to search. If alloca was used also, we
// are toast. Too Bad.
//
if (cdwIndex == 0 && AcceptUnreadableCallSite) {
WDB((1, " unreadable call site, use %X\n",
(ULONG)uoffStack));
return uoffStack;
} else {
continue;
}
}
//
// With 32bit code that isn't FAR:32 we don't have to worry about
// intersegment calls. Check here to see if we had a call within
// segment. If it is we can later check it's full diplacement if
// necessary and see if it calls the FPO function. We will also have
// to check for thunks and see if maybe it called a JMP indirect which
// called the FPO function. We will fail to find the caller if it was
// a case of tail recursion where one function doesn't actually call
// another but rather jumps to it. This will only happen when a
// function who's parameter list is void calls another function who's
// parameter list is void and the call is made as the last statement
// in the first function. If the call to the first function was an
// 0xE8 call we will fail to find it here because it didn't call the
// FPO function but rather the FPO functions caller. If we don't get
// specific about our 0xE8 checks we will potentially see things that
// look like return addresses but aren't.
//
if (( cBytes >= 5 ) && ( code[ 2 ] == 0xE8 )) {
// We do math on 32 bit so we can ignore carry, and then sign extended
uoffT = EXTEND64((DWORD)uoffRet + *( (UNALIGNED DWORD *) &code[3] ));
//
// See if it calls the function directly, or into the function
//
if (( uoffT >= funcAddr) && ( uoffT < (funcAddr + funcSize) ) ) {
WDB((1, " found function, use %X\n", (ULONG)uoffStack));
return uoffStack;
}
while ( cJmpChain < MAX_JMP_CHAIN ) {
if (!ReadMemory(Process,
uoffT,
jmpBuffer,
sizeof(jmpBuffer),
&cb)) {
break;
}
if (cb != sizeof(jmpBuffer)) {
break;
}
//
// Now we are going to check if it is a call to a JMP, that may
// jump to the function
//
// If it is a relative JMP then calculate the destination
// and save it in uoffT. If it is an indirect JMP then read
// the destination from where the JMP is inderecting through.
//
if ( *(LPBYTE)lpwJmp == 0xE9 ) {
// We do math on 32 bit so we can ignore carry, and then
// sign extended
uoffT = EXTEND64 ((ULONG)uoffT +
*(UNALIGNED DWORD *)( jmpBuffer + sizeof(BYTE) ) + 5);
} else if ( *lpwJmp == 0x25FF ) {
if ((!ReadMemory(Process,
EXTEND64 (
*(UNALIGNED DWORD *)
((LPBYTE)lpwJmp+sizeof(WORD))),
&uoffT,
sizeof(DWORD),
&cb)) || (cb != sizeof(DWORD))) {
uoffT = 0;
break;
}
uoffT = EXTEND64(uoffT);
} else {
break;
}
//
// If the destination is to the FPO function then we have
// found the return address and thus the vEBP
//
if ( uoffT == funcAddr ) {
WDB((1, " exact function, use %X\n",
(ULONG)uoffStack));
return uoffStack;
}
cJmpChain++;
}
//
// We cache away the first 0xE8 call or 0xE9 jmp that we find in
// the event we cant find anything else that looks like a return
// address. This is meant to protect us in the tail recursion case.
//
if ( !uoffBestGuess ) {
uoffBestGuess = uoffStack;
}
}
//
// Now loop backward through the bytes read checking for a multi
// byte call type from Grp5. If we find an 0xFF then we need to
// check the byte after that to make sure that the nnn bits of
// the mod/rm byte tell us that it is a call. It it is a call
// then we will assume that this one called us because we can
// no longer accurately determine for sure whether this did
// in fact call the FPO function. Since 0xFF calls are a guess
// as well we will not check them if we already have an earlier guess.
// It is more likely that the first 0xE8 called the function than
// something higher up the stack that might be an 0xFF call.
//
if ( !uoffBestGuess && cBytes >= MIN_CALL ) {
cbLimit = MAX_CALL - (INT)cBytes;
for (cbIndex = MAX_CALL - MIN_CALL;
cbIndex >= cbLimit; //MAX_CALL - (INT)cBytes;
cbIndex--) {
if ( ( code [ cbIndex ] == 0xFF ) &&
( ( code [ cbIndex + 1 ] & 0x30 ) == 0x10 )){
WDB((1, " found call, use %X\n", (ULONG)uoffStack));
return uoffStack;
}
}
}
}
//
// we found nothing that was 100% definite so we'll return the best guess
//
WDB((1, " best guess is %X\n", (ULONG)uoffBestGuess));
return uoffBestGuess;
}
#define MRM_MOD(Mrm) (((Mrm) >> 6) & 3)
#define MRM_REGOP(Mrm) (((Mrm) >> 3) & 7)
#define MRM_RM(Mrm) (((Mrm) >> 0) & 7)
#define SIB_SCALE(Sib) (((Sib) >> 6) & 3)
#define SIB_INDEX(Sib) (((Sib) >> 3) & 7)
#define SIB_BASE(Sib) (((Sib) >> 0) & 7)
DWORD
ModRmLen(BYTE ModRm)
{
BYTE Mod, Rm;
Mod = MRM_MOD(ModRm);
Rm = MRM_RM(ModRm);
switch(Mod)
{
case 0:
if (Rm == 4)
{
return 1;
}
else if (Rm == 5)
{
return 4;
}
break;
case 1:
return 1 + (Rm == 4 ? 1 : 0);
case 2:
return 4 + (Rm == 4 ? 1 : 0);
}
// No extra bytes.
return 0;
}
BOOL
GetEspRelModRm(BYTE* CodeMrm, ULONG Esp, PULONG EspRel)
{
BYTE Mrm, Sib;
Mrm = CodeMrm[0];
if (MRM_MOD(Mrm) == 3)
{
// Register-only form. Only handle
// the case of an ESP reference.
if (MRM_RM(Mrm) == 4)
{
*EspRel = Esp;
return TRUE;
}
else
{
return FALSE;
}
}
// Look for any ESP-relative R/M.
if (MRM_RM(Mrm) != 4)
{
return FALSE;
}
Sib = CodeMrm[1];
// Only simple displacements from ESP are supported.
if (SIB_INDEX(Sib) != 4 ||
SIB_BASE(Sib) != 4)
{
return FALSE;
}
switch(MRM_MOD(Mrm))
{
case 0:
// [esp]
*EspRel = Esp;
break;
case 1:
// disp8[esp]
*EspRel = Esp + (signed char)CodeMrm[2];
break;
case 2:
// disp32[esp]
*EspRel = Esp + *(ULONG UNALIGNED *)&CodeMrm[2];
break;
default:
// Should never get here, MOD == 3 is handled above.
return FALSE;
}
return TRUE;
}
DWORD64
SearchForFramePointer(
HANDLE Process,
DWORD64 RegSaveAddr,
DWORD64 RetEspAddr,
DWORD NumRegs,
DWORD64 FuncAddr,
DWORD FuncSize,
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory
)
{
BYTE Code[MAX_FUNC_PROLOGUE];
DWORD CodeLen;
DWORD i;
DWORD Depth;
DWORD64 DefAddr;
DWORD Esp = (ULONG)RetEspAddr;
BOOL EspValid = TRUE;
BYTE Mrm;
WDB((1, " SearchForFramePointer: regs %X, ret ESP %X, numregs %d\n",
(ULONG)RegSaveAddr, (ULONG)RetEspAddr, NumRegs));
// RetEspAddr is the first address beyond the end of
// the frame, so it hopefully is the address of the return
// address for the call. We don't really care about that
// and are more interested in what the first push slot might
// be, which is directly beneath the return address.
RetEspAddr -= STACK_SIZE;
//
// The compiler does not push registers in a consistent
// order and FPO information only indicates the total
// number of registers pushed, not their order. This
// function searches the stack locations where registers
// are stored and tries to find which one is EBP.
// It searches the function code for pushes and
// tries to use that information to help the stack
// analysis.
//
// If this routine fails it just returns the base
// of the register save area. If the routine pushes
// no registers, return the first possible push slot.
//
DefAddr = NumRegs ? RegSaveAddr : RetEspAddr;
// Read the beginning of the function for code analysis.
if (sizeof(Code) < FuncSize)
{
CodeLen = sizeof(Code);
}
else
{
CodeLen = FuncSize;
}
if (!ReadMemory(Process, FuncAddr, Code, CodeLen, &CodeLen))
{
WDB((1, " unable to read code, use %X\n", (ULONG)DefAddr));
return DefAddr;
}
// Scan the code for normal prologue operations like
// sub esp, push reg and mov reg. This code only
// handles a very limited set of instructions.
Depth = 0;
for (i = 0; i < CodeLen; i++)
{
WDB((4, " %08X: Opcode %02X - ",
(ULONG)FuncAddr + i, Code[i]));
if (Code[i] == 0x83 && i + 3 <= CodeLen && Code[i + 1] == 0xec)
{
// sub esp, signed imm8
Esp -= (signed char)Code[i + 2];
WDB((4 | SDB_NO_PREFIX, "sub esp,0x%x, ESP %X (%s)\n",
(signed char)Code[i + 2], Esp,
EspValid ? "valid" : "invalid"));
// Loop increment adds one.
i += 2;
}
else if (Code[i] == 0x81 && i + 6 <= CodeLen && Code[i + 1] == 0xec)
{
// sub esp, imm32
Esp -= *(ULONG UNALIGNED *)&Code[i + 2];
WDB((4 | SDB_NO_PREFIX, "sub esp,0x%x, ESP %X (%s)\n",
*(ULONG UNALIGNED *)&Code[i + 2], Esp,
EspValid ? "valid" : "invalid"));
// Loop increment adds one.
i += 5;
}
else if (Code[i] == 0x89 && i + 2 <= CodeLen)
{
// mov r/m32, reg32
Mrm = Code[i + 1];
switch(MRM_REGOP(Mrm))
{
case 5:
if (GetEspRelModRm(Code + 1, Esp, &Esp))
{
// mov [esp+offs], ebp
WDB((4 | SDB_NO_PREFIX, "mov [%X],ebp\n", Esp));
WDB((1, " moved ebp to stack at %X\n", Esp));
return EXTEND64(Esp);
}
break;
}
WDB((4 | SDB_NO_PREFIX, "mov r/m32,reg32, skipped\n"));
i += ModRmLen(Mrm) + 1;
}
else if (Code[i] == 0x8b && i + 2 <= CodeLen)
{
// mov reg32, r/m32
Mrm = Code[i + 1];
if (MRM_REGOP(Mrm) == 4)
{
// ESP was modified in a way we can't emulate.
WDB((4 | SDB_NO_PREFIX, "ESP lost\n"));
EspValid = FALSE;
}
else
{
WDB((4 | SDB_NO_PREFIX, "mov reg32,r/m32, skipped\n"));
}
i += ModRmLen(Mrm) + 1;
}
else if (Code[i] == 0x8d && i + 2 <= CodeLen)
{
// lea reg32, r/m32
Mrm = Code[i + 1];
switch(MRM_REGOP(Mrm))
{
case 4:
if (GetEspRelModRm(Code + 1, Esp, &Esp))
{
WDB((4 | SDB_NO_PREFIX, "lea esp,[%X]\n", Esp));
}
else
{
// ESP was modified in a way we can't emulate.
WDB((4 | SDB_NO_PREFIX, "ESP lost\n"));
EspValid = FALSE;
}
break;
default:
WDB((4 | SDB_NO_PREFIX, "lea reg32,r/m32, skipped\n"));
break;
}
i += ModRmLen(Mrm) + 1;
}
else if (Code[i] >= 0x50 && Code[i] <= 0x57)
{
// push rd
Esp -= STACK_SIZE;
WDB((4 | SDB_NO_PREFIX, "push <reg>, ESP %X (%s)\n", Esp,
EspValid ? "valid" : "invalid"));
if (Code[i] == 0x55)
{
// push ebp
// Found it. If we trust the ESP we've
// been tracking just return it.
// Otherwise, if it's the first instruction
// of the routine then we should return the
// frame address, otherwise return the
// proper location in the register store area.
// If there is no register store area then
// just return the default address.
if (EspValid)
{
WDB((1, " push ebp at esp %X\n", Esp));
return EXTEND64(Esp);
}
else if (!NumRegs)
{
WDB((1, " found ebp but no regarea, return %X\n",
(ULONG)DefAddr));
return DefAddr;
}
else
{
RegSaveAddr += (NumRegs - Depth - 1) * STACK_SIZE;
WDB((1, " guess ebp at %X\n", (ULONG)RegSaveAddr));
return RegSaveAddr;
}
}
Depth++;
}
else
{
// Unhandled code, fail.
WDB((4 | SDB_NO_PREFIX, "unknown\n"));
WDB((1, " unknown code sequence %02X at %X\n",
Code[i], (ULONG)FuncAddr + i));
return DefAddr;
}
}
// Didn't find a push ebp, fail.
WDB((1, " no ebp, use %X\n", (ULONG)DefAddr));
return DefAddr;
}
BOOL
GetFpoFrameBase(
HANDLE Process,
LPSTACKFRAME64 StackFrame,
PFPO_DATA pFpoData,
PFPO_DATA PreviousFpoData,
BOOL fFirstFrame,
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory,
PGET_MODULE_BASE_ROUTINE64 GetModuleBase
)
{
DWORD Addr32;
X86_KTRAP_FRAME TrapFrame;
DWORD64 OldFrameAddr;
DWORD64 FrameAddr;
DWORD64 StackAddr;
DWORD64 ModuleBase;
DWORD64 FuncAddr;
DWORD cb;
DWORD64 StoredEbp;
//
// calculate the address of the beginning of the function
//
ModuleBase = GetModuleBase( Process, StackFrame->AddrPC.Offset );
if (!ModuleBase) {
return FALSE;
}
FuncAddr = ModuleBase+pFpoData->ulOffStart;
WDB((1, " GetFpoFrameBase: PC %X, Func %X, first %d, FPO %p [%d,%d,%d]\n",
(ULONG)StackFrame->AddrPC.Offset, (ULONG)FuncAddr,
fFirstFrame, pFpoData, pFpoData->cdwParams, pFpoData->cdwLocals,
pFpoData->cbRegs));
//
// If this isn't the first/current frame then we can add back the count
// bytes of locals and register pushed before beginning to search for
// EBP. If we are beyond prolog we can add back the count bytes of locals
// and registers pushed as well. If it is the first frame and EIP is
// greater than the address of the function then the SUB for locals has
// been done so we can add them back before beginning the search. If we
// are right on the function then we will need to start our search at ESP.
//
if ( !fFirstFrame ) {
OldFrameAddr = StackFrame->AddrFrame.Offset;
FrameAddr = 0;
//
// if this is a non-fpo or trap frame, get the frame base now:
//
if (pFpoData->cbFrame != FRAME_FPO) {
if (!PreviousFpoData || PreviousFpoData->cbFrame == FRAME_NONFPO) {
//
// previous frame base is ebp and points to this frame's ebp
//
if (!ReadMemory(Process,
OldFrameAddr,
&Addr32,
sizeof(DWORD),
&cb) ||
cb != sizeof(DWORD)) {
FrameAddr = 0;
} else {
FrameAddr = (DWORD64)(LONG64)(LONG)Addr32;
}
}
//
// if that didn't work, try for a saved ebp
//
if (!FrameAddr && IS_EBP_SAVED(StackFrame) &&
(OldFrameAddr <= SAVE_EBP(StackFrame))) {
FrameAddr = SAVE_EBP(StackFrame);
WDB((1, " non-FPO using %X\n", (ULONG)FrameAddr));
}
//
// this is not an FPO frame, so the saved EBP can only have come
// from this or a lower frame.
//
SAVE_EBP(StackFrame) = 0;
}
//
// still no frame base - either this frame is fpo, or we couldn't
// follow the ebp chain.
//
if (FrameAddr == 0) {
FrameAddr = OldFrameAddr;
//
// skip over return address from prev frame
//
FrameAddr += FRAME_SIZE;
//
// skip over this frame's locals and saved regs
//
FrameAddr += ( pFpoData->cdwLocals * STACK_SIZE );
FrameAddr += ( pFpoData->cbRegs * STACK_SIZE );
if (PreviousFpoData) {
//
// if the previous frame had an fpo record, we can account
// for its parameters
//
FrameAddr += PreviousFpoData->cdwParams * STACK_SIZE;
}
}
//
// if this is an FPO frame
// and the previous frame was non-fpo,
// and this frame passed the inherited ebp to the previous frame,
// save its ebp
//
// (if this frame used ebp, SAVE_EBP will be set after verifying
// the frame base)
//
if (pFpoData->cbFrame == FRAME_FPO &&
(!PreviousFpoData || PreviousFpoData->cbFrame == FRAME_NONFPO) &&
!pFpoData->fUseBP) {
SAVE_EBP(StackFrame) = 0;
if (ReadMemory(Process,
OldFrameAddr,
&Addr32,
sizeof(DWORD),
&cb) &&
cb == sizeof(DWORD)) {
SAVE_EBP(StackFrame) = (DWORD64)(LONG64)(LONG)Addr32;
WDB((1, " pass-through FP %X\n", Addr32));
} else {
WDB((1, " clear ebp\n"));
}
}
} else {
OldFrameAddr = StackFrame->AddrFrame.Offset;
if (pFpoData->cbFrame == FRAME_FPO && !pFpoData->fUseBP) {
//
// this frame didn't use EBP, so it actually belongs
// to a non-FPO frame further up the stack. Stash
// it in the save area for the next frame.
//
SAVE_EBP(StackFrame) = StackFrame->AddrFrame.Offset;
WDB((1, " first non-ebp save %X\n", (ULONG)SAVE_EBP(StackFrame)));
}
if (pFpoData->cbFrame == FRAME_TRAP ||
pFpoData->cbFrame == FRAME_TSS) {
FrameAddr = StackFrame->AddrFrame.Offset;
} else if (StackFrame->AddrPC.Offset == FuncAddr) {
FrameAddr = StackFrame->AddrStack.Offset;
} else if (StackFrame->AddrPC.Offset >= FuncAddr+pFpoData->cbProlog) {
FrameAddr = StackFrame->AddrStack.Offset +
( pFpoData->cdwLocals * STACK_SIZE ) +
( pFpoData->cbRegs * STACK_SIZE );
} else {
FrameAddr = StackFrame->AddrStack.Offset +
( pFpoData->cdwLocals * STACK_SIZE );
}
}
if (pFpoData->cbFrame == FRAME_TRAP) {
//
// read a kernel mode trap frame from the stack
//
if (!ReadTrapFrame( Process,
FrameAddr,
&TrapFrame,
ReadMemory )) {
return FALSE;
}
SAVE_TRAP(StackFrame) = FrameAddr;
TRAP_EDITED(StackFrame) = TrapFrame.SegCs & X86_FRAME_EDITED;
StackFrame->AddrReturn.Offset = (DWORD64)(LONG64)(LONG)(TrapFrame.Eip);
StackFrame->AddrReturn.Mode = AddrModeFlat;
StackFrame->AddrReturn.Segment = 0;
return TRUE;
}
if (pFpoData->cbFrame == FRAME_TSS) {
//
// translate a tss to a kernel mode trap frame
//
StackAddr = FrameAddr;
if (!TaskGate2TrapFrame( Process, X86_KGDT_TSS, &TrapFrame,
&StackAddr, ReadMemory )) {
return FALSE;
}
TRAP_TSS(StackFrame) = X86_KGDT_TSS;
SAVE_TRAP(StackFrame) = StackAddr;
StackFrame->AddrReturn.Offset = (DWORD64)(LONG64)(LONG)(TrapFrame.Eip);
StackFrame->AddrReturn.Mode = AddrModeFlat;
StackFrame->AddrReturn.Segment = 0;
return TRUE;
}
if ((pFpoData->cbFrame != FRAME_FPO) &&
(pFpoData->cbFrame != FRAME_NONFPO) ) {
//
// we either have a compiler or linker problem, or possibly
// just simple data corruption.
//
return FALSE;
}
//
// go look for a return address. this is done because, even though
// we have subtracted all that we can from the frame pointer it is
// possible that there is other unknown data on the stack. by
// searching for the return address we are able to find the base of
// the fpo frame.
//
FrameAddr = SearchForReturnAddress( Process,
FrameAddr,
FuncAddr,
pFpoData->cbProcSize,
ReadMemory,
GetModuleBase,
PreviousFpoData != NULL
);
if (!FrameAddr) {
return FALSE;
}
if (pFpoData->fUseBP && pFpoData->cbFrame == FRAME_FPO) {
//
// this function used ebp as a general purpose register, but
// before doing so it saved ebp on the stack.
//
// we must retrieve this ebp and save it for possible later
// use if we encounter a non-fpo frame
//
if (fFirstFrame && StackFrame->AddrPC.Offset < FuncAddr+pFpoData->cbProlog) {
SAVE_EBP(StackFrame) = OldFrameAddr;
WDB((1, " first use save FP %X\n", (ULONG)OldFrameAddr));
} else {
SAVE_EBP(StackFrame) = 0;
// FPO information doesn't indicate which of the saved
// registers is EBP and the compiler doesn't push in a
// consistent way. Scan the register slots of the
// stack for something that looks OK.
StackAddr = FrameAddr -
( ( pFpoData->cbRegs + pFpoData->cdwLocals ) * STACK_SIZE );
StackAddr = SearchForFramePointer( Process,
StackAddr,
FrameAddr,
pFpoData->cbRegs,
FuncAddr,
pFpoData->cbProcSize,
ReadMemory
);
if (StackAddr &&
ReadMemory(Process,
StackAddr,
&Addr32,
sizeof(DWORD),
&cb) &&
cb == sizeof(DWORD)) {
SAVE_EBP(StackFrame) = (DWORD64)(LONG64)(LONG)Addr32;
WDB((1, " use search save %X from %X\n", Addr32,
(ULONG)StackAddr));
} else {
WDB((1, " use clear ebp\n"));
}
}
}
//
// subtract the size for an ebp register if one had
// been pushed. this is done because the frames that
// are virtualized need to appear as close to a real frame
// as possible.
//
StackFrame->AddrFrame.Offset = FrameAddr - STACK_SIZE;
return TRUE;
}
BOOL
ReadTrapFrame(
HANDLE Process,
DWORD64 TrapFrameAddress,
PX86_KTRAP_FRAME TrapFrame,
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory
)
{
DWORD cb;
if (!ReadMemory(Process,
TrapFrameAddress,
TrapFrame,
sizeof(*TrapFrame),
&cb)) {
return FALSE;
}
if (cb < sizeof(*TrapFrame)) {
if (cb < sizeof(*TrapFrame) - 20) {
//
// shorter then the smallest possible frame type
//
return FALSE;
}
if ((TrapFrame->SegCs & 1) && cb < sizeof(*TrapFrame) - 16 ) {
//
// too small for inter-ring frame
//
return FALSE;
}
if (TrapFrame->EFlags & X86_EFLAGS_V86_MASK) {
//
// too small for V86 frame
//
return FALSE;
}
}
return TRUE;
}
BOOL
GetSelector(
HANDLE Process,
USHORT Processor,
PX86_DESCRIPTOR_TABLE_ENTRY pDescriptorTableEntry,
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory
)
{
ULONG_PTR Address;
PVOID TableBase;
USHORT TableLimit;
ULONG Index;
X86_LDT_ENTRY Descriptor;
ULONG bytesread;
//
// Fetch the address and limit of the GDT
//
Address = (ULONG_PTR)&(((PX86_KSPECIAL_REGISTERS)0)->Gdtr.Base);
ReadMemory( Process, Address, &TableBase, sizeof(TableBase), (LPDWORD)-1 );
Address = (ULONG_PTR)&(((PX86_KSPECIAL_REGISTERS)0)->Gdtr.Limit);
ReadMemory( Process, Address, &TableLimit, sizeof(TableLimit), (LPDWORD)-1 );
//
// Find out whether this is a GDT or LDT selector
//
if (pDescriptorTableEntry->Selector & 0x4) {
//
// This is an LDT selector, so we reload the TableBase and TableLimit
// with the LDT's Base & Limit by loading the descriptor for the
// LDT selector.
//
if (!ReadMemory(Process,
(ULONG64)TableBase+X86_KGDT_LDT,
&Descriptor,
sizeof(Descriptor),
&bytesread)) {
return FALSE;
}
TableBase = (PVOID)(DWORD_PTR)((ULONG)Descriptor.BaseLow + // Sundown: zero-extension from ULONG to PVOID.
((ULONG)Descriptor.HighWord.Bits.BaseMid << 16) +
((ULONG)Descriptor.HighWord.Bytes.BaseHi << 24));
TableLimit = Descriptor.LimitLow; // LDT can't be > 64k
if(Descriptor.HighWord.Bits.Granularity) {
//
// I suppose it's possible, to have an
// LDT with page granularity.
//
TableLimit <<= X86_PAGE_SHIFT;
}
}
Index = (USHORT)(pDescriptorTableEntry->Selector) & ~0x7;
// Irrelevant bits
//
// Check to make sure that the selector is within the table bounds
//
if (Index >= TableLimit) {
//
// Selector is out of table's bounds
//
return FALSE;
}
if (!ReadMemory(Process,
(ULONG64)TableBase+Index,
&(pDescriptorTableEntry->Descriptor),
sizeof(pDescriptorTableEntry->Descriptor),
&bytesread)) {
return FALSE;
}
return TRUE;
}
BOOL
TaskGate2TrapFrame(
HANDLE Process,
USHORT TaskRegister,
PX86_KTRAP_FRAME TrapFrame,
PULONG64 off,
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory
)
{
X86_DESCRIPTOR_TABLE_ENTRY desc;
ULONG bytesread;
struct {
ULONG r1[8];
ULONG Eip;
ULONG EFlags;
ULONG Eax;
ULONG Ecx;
ULONG Edx;
ULONG Ebx;
ULONG Esp;
ULONG Ebp;
ULONG Esi;
ULONG Edi;
ULONG Es;
ULONG Cs;
ULONG Ss;
ULONG Ds;
ULONG Fs;
ULONG Gs;
} TaskState;
//
// Get the task register
//
desc.Selector = TaskRegister;
if (!GetSelector(Process, 0, &desc, ReadMemory)) {
return FALSE;
}
if (desc.Descriptor.HighWord.Bits.Type != 9 &&
desc.Descriptor.HighWord.Bits.Type != 0xb) {
//
// not a 32bit task descriptor
//
return FALSE;
}
//
// Read in Task State Segment
//
*off = ((ULONG)desc.Descriptor.BaseLow +
((ULONG)desc.Descriptor.HighWord.Bytes.BaseMid << 16) +
((ULONG)desc.Descriptor.HighWord.Bytes.BaseHi << 24) );
if (!ReadMemory(Process,
EXTEND64(*off),
&TaskState,
sizeof(TaskState),
&bytesread)) {
return FALSE;
}
//
// Move fields from Task State Segment to TrapFrame
//
ZeroMemory( TrapFrame, sizeof(*TrapFrame) );
TrapFrame->Eip = TaskState.Eip;
TrapFrame->EFlags = TaskState.EFlags;
TrapFrame->Eax = TaskState.Eax;
TrapFrame->Ecx = TaskState.Ecx;
TrapFrame->Edx = TaskState.Edx;
TrapFrame->Ebx = TaskState.Ebx;
TrapFrame->Ebp = TaskState.Ebp;
TrapFrame->Esi = TaskState.Esi;
TrapFrame->Edi = TaskState.Edi;
TrapFrame->SegEs = TaskState.Es;
TrapFrame->SegCs = TaskState.Cs;
TrapFrame->SegDs = TaskState.Ds;
TrapFrame->SegFs = TaskState.Fs;
TrapFrame->SegGs = TaskState.Gs;
TrapFrame->HardwareEsp = TaskState.Esp;
TrapFrame->HardwareSegSs = TaskState.Ss;
return TRUE;
}
BOOL
ProcessTrapFrame(
HANDLE Process,
LPSTACKFRAME64 StackFrame,
PFPO_DATA pFpoData,
PFPO_DATA PreviousFpoData,
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory,
PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccess
)
{
X86_KTRAP_FRAME TrapFrame;
DWORD64 StackAddr;
if (PreviousFpoData->cbFrame == FRAME_TSS) {
StackAddr = SAVE_TRAP(StackFrame);
TaskGate2TrapFrame( Process, X86_KGDT_TSS, &TrapFrame, &StackAddr, ReadMemory );
} else {
if (!ReadTrapFrame( Process,
SAVE_TRAP(StackFrame),
&TrapFrame,
ReadMemory)) {
SAVE_TRAP(StackFrame) = 0;
return FALSE;
}
}
pFpoData = (PFPO_DATA)
FunctionTableAccess(Process,
(DWORD64)(LONG64)(LONG)TrapFrame.Eip);
#if 0
// Remove this check since we are not using pFpoData anyway
if (!pFpoData) {
StackFrame->AddrFrame.Offset = (DWORD64)(LONG64)(LONG)TrapFrame.Ebp;
SAVE_EBP(StackFrame) = 0;
} else
#endif //0
{
if ((TrapFrame.SegCs & X86_MODE_MASK) ||
(TrapFrame.EFlags & X86_EFLAGS_V86_MASK)) {
//
// User-mode frame, real value of Esp is in HardwareEsp
//
StackFrame->AddrFrame.Offset = (DWORD64)(LONG64)(LONG)(TrapFrame.HardwareEsp - STACK_SIZE);
StackFrame->AddrStack.Offset = (DWORD64)(LONG64)(LONG)TrapFrame.HardwareEsp;
} else {
//
// We ignore if Esp has been edited for now, and we will print a
// separate line indicating this later.
//
// Calculate kernel Esp
//
if (PreviousFpoData->cbFrame == FRAME_TRAP) {
//
// plain trap frame
//
if ((TrapFrame.SegCs & X86_FRAME_EDITED) == 0) {
StackFrame->AddrStack.Offset = EXTEND64(TrapFrame.TempEsp);
} else {
StackFrame->AddrStack.Offset = EXTEND64(SAVE_TRAP(StackFrame))+
FIELD_OFFSET(X86_KTRAP_FRAME, HardwareEsp);
}
} else {
//
// tss converted to trap frame
//
StackFrame->AddrStack.Offset = EXTEND64(TrapFrame.HardwareEsp);
}
}
}
StackFrame->AddrFrame.Offset = EXTEND64(TrapFrame.Ebp);
StackFrame->AddrPC.Offset = EXTEND64(TrapFrame.Eip);
SAVE_TRAP(StackFrame) = 0;
StackFrame->FuncTableEntry = pFpoData;
return TRUE;
}
BOOL
IsFarCall(
HANDLE Process,
HANDLE Thread,
LPSTACKFRAME64 StackFrame,
BOOL *Ok,
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory,
PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress
)
{
BOOL fFar = FALSE;
ULONG cb;
ADDRESS64 Addr;
*Ok = TRUE;
if (StackFrame->AddrFrame.Mode == AddrModeFlat) {
DWORD dwStk[ 3 ];
//
// If we are working with 32 bit offset stack pointers, we
// will say that the return address if far if the address
// treated as a FAR pointer makes any sense, if not then
// it must be a near return
//
if (StackFrame->AddrFrame.Offset &&
DoMemoryReadAll( &StackFrame->AddrFrame, dwStk, sizeof(dwStk) )) {
//
// See if segment makes sense
//
Addr.Offset = (DWORD64)(LONG64)(LONG)(dwStk[1]);
Addr.Segment = (WORD)dwStk[2];
Addr.Mode = AddrModeFlat;
if (TranslateAddress( Process, Thread, &Addr ) && Addr.Offset) {
fFar = TRUE;
}
} else {
*Ok = FALSE;
}
} else {
WORD wStk[ 3 ];
//
// For 16 bit (i.e. windows WOW code) we do the following tests
// to check to see if an address is a far return value.
//
// 1. if the saved BP register is odd then it is a far
// return values
// 2. if the address treated as a far return value makes sense
// then it is a far return value
// 3. else it is a near return value
//
if (StackFrame->AddrFrame.Offset &&
DoMemoryReadAll( &StackFrame->AddrFrame, wStk, 6 )) {
if ( wStk[0] & 0x0001 ) {
fFar = TRUE;
} else {
//
// See if segment makes sense
//
Addr.Offset = wStk[1];
Addr.Segment = wStk[2];
Addr.Mode = AddrModeFlat;
if (TranslateAddress( Process, Thread, &Addr ) && Addr.Offset) {
fFar = TRUE;
}
}
} else {
*Ok = FALSE;
}
}
return fFar;
}
BOOL
SetNonOff32FrameAddress(
HANDLE Process,
HANDLE Thread,
LPSTACKFRAME64 StackFrame,
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory,
PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccess,
PGET_MODULE_BASE_ROUTINE64 GetModuleBase,
PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress
)
{
BOOL fFar;
WORD Stk[ 3 ];
ULONG cb;
BOOL Ok;
fFar = IsFarCall( Process, Thread, StackFrame, &Ok, ReadMemory, TranslateAddress );
if (!Ok) {
return FALSE;
}
if (!DoMemoryReadAll( &StackFrame->AddrFrame, Stk, (DWORD)(fFar ? FRAME_SIZE1632 : FRAME_SIZE16) )) {
return FALSE;
}
if (IS_EBP_SAVED(StackFrame) && (SAVE_EBP(StackFrame) > 0)) {
StackFrame->AddrFrame.Offset = SAVE_EBP(StackFrame) & 0xffff;
StackFrame->AddrPC.Offset = Stk[1];
if (fFar) {
StackFrame->AddrPC.Segment = Stk[2];
}
SAVE_EBP(StackFrame) = 0;
} else {
if (Stk[1] == 0) {
return FALSE;
} else {
StackFrame->AddrFrame.Offset = Stk[0];
StackFrame->AddrFrame.Offset &= 0xFFFFFFFE;
StackFrame->AddrPC.Offset = Stk[1];
if (fFar) {
StackFrame->AddrPC.Segment = Stk[2];
}
}
}
return TRUE;
}
VOID
X86ReadFunctionParameters(
HANDLE Process,
ULONG64 Offset,
LPSTACKFRAME64 Frame,
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory
)
{
DWORD Params[4];
DWORD Done;
if (!ReadMemory(Process, Offset, Params, sizeof(Params), &Done)) {
Done = 0;
}
if (Done < sizeof(Params)) {
ZeroMemory((PUCHAR)Params + Done, sizeof(Params) - Done);
}
Frame->Params[0] = (DWORD64)(LONG64)(LONG)(Params[0]);
Frame->Params[1] = (DWORD64)(LONG64)(LONG)(Params[1]);
Frame->Params[2] = (DWORD64)(LONG64)(LONG)(Params[2]);
Frame->Params[3] = (DWORD64)(LONG64)(LONG)(Params[3]);
}
VOID
GetFunctionParameters(
HANDLE Process,
HANDLE Thread,
LPSTACKFRAME64 StackFrame,
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory,
PGET_MODULE_BASE_ROUTINE64 GetModuleBase,
PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress
)
{
BOOL Ok;
ADDRESS64 ParmsAddr;
ParmsAddr = StackFrame->AddrFrame;
//
// calculate the frame size
//
if (StackFrame->AddrPC.Mode == AddrModeFlat) {
ParmsAddr.Offset += FRAME_SIZE;
} else
if ( IsFarCall( Process, Thread, StackFrame, &Ok,
ReadMemory, TranslateAddress ) ) {
StackFrame->Far = TRUE;
ParmsAddr.Offset += FRAME_SIZE1632;
} else {
StackFrame->Far = FALSE;
ParmsAddr.Offset += STACK_SIZE;
}
//
// read the memory
//
if (ParmsAddr.Mode != AddrModeFlat) {
TranslateAddress( Process, Thread, &ParmsAddr );
}
X86ReadFunctionParameters(Process, ParmsAddr.Offset, StackFrame,
ReadMemory);
}
VOID
GetReturnAddress(
HANDLE Process,
HANDLE Thread,
LPSTACKFRAME64 StackFrame,
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory,
PGET_MODULE_BASE_ROUTINE64 GetModuleBase,
PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress,
PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccess
)
{
ULONG cb;
DWORD stack[1];
if (SAVE_TRAP(StackFrame)) {
//
// if a trap frame was encountered then
// the return address was already calculated
//
return;
}
WDB((1, " GetReturnAddress: SP %X, FP %X\n",
(ULONG)StackFrame->AddrStack.Offset,
(ULONG)StackFrame->AddrFrame.Offset));
if (StackFrame->AddrPC.Mode == AddrModeFlat) {
ULONG64 CallOffset;
PFPO_DATA CallFpo;
ADDRESS64 FrameRet;
FPO_DATA SaveCallFpo;
PFPO_DATA RetFpo;
//
// read the frame from the process's memory
//
FrameRet = StackFrame->AddrFrame;
FrameRet.Offset += STACK_SIZE;
FrameRet.Offset = EXTEND64(FrameRet.Offset);
if (!DoMemoryRead( &FrameRet, stack, STACK_SIZE, &cb ) ||
cb < STACK_SIZE) {
//
// if we could not read the memory then set
// the return address to zero so that the stack trace
// will terminate
//
stack[0] = 0;
}
StackFrame->AddrReturn.Offset = (DWORD64)(LONG64)(LONG)(stack[0]);
WDB((1, " read %X\n", stack[0]));
//
// 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 = StackFrame->AddrReturn.Offset - 1;
CallFpo = (PFPO_DATA)FunctionTableAccess(Process, CallOffset);
if (CallFpo != NULL) {
SaveCallFpo = *CallFpo;
}
RetFpo = (PFPO_DATA)
FunctionTableAccess(Process, StackFrame->AddrReturn.Offset);
if (CallFpo != NULL) {
if (RetFpo == NULL ||
memcmp(&SaveCallFpo, RetFpo, sizeof(SaveCallFpo))) {
StackFrame->AddrReturn.Offset = CallOffset;
}
} else if (RetFpo != NULL) {
StackFrame->AddrReturn.Offset = CallOffset;
}
} else {
StackFrame->AddrReturn.Offset = StackFrame->AddrPC.Offset;
StackFrame->AddrReturn.Segment = StackFrame->AddrPC.Segment;
}
}
BOOL
WalkX86_Fpo_Fpo(
HANDLE Process,
HANDLE Thread,
PFPO_DATA pFpoData,
PFPO_DATA PreviousFpoData,
LPSTACKFRAME64 StackFrame,
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory,
PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccess,
PGET_MODULE_BASE_ROUTINE64 GetModuleBase,
PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress
)
{
BOOL rval;
WDB((1, " WalkFF:\n"));
rval = GetFpoFrameBase( Process,
StackFrame,
pFpoData,
PreviousFpoData,
FALSE,
ReadMemory,
GetModuleBase );
StackFrame->FuncTableEntry = pFpoData;
return rval;
}
BOOL
WalkX86_Fpo_NonFpo(
HANDLE Process,
HANDLE Thread,
PFPO_DATA pFpoData,
PFPO_DATA PreviousFpoData,
LPSTACKFRAME64 StackFrame,
PX86_CONTEXT ContextRecord,
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory,
PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccess,
PGET_MODULE_BASE_ROUTINE64 GetModuleBase,
PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress
)
{
DWORD stack[FRAME_SIZE+STACK_SIZE];
DWORD cb;
DWORD64 FrameAddr;
DWORD64 FuncAddr;
DWORD FuncSize;
BOOL AcceptUnreadableCallsite = FALSE;
WDB((1, " WalkFN:\n"));
//
// if the previous frame was an SEH frame then we must
// retrieve the "real" frame pointer for this frame.
// the SEH function pushed the frame pointer last.
//
if (PreviousFpoData->fHasSEH) {
if (DoMemoryReadAll( &StackFrame->AddrFrame, stack, FRAME_SIZE+STACK_SIZE )) {
StackFrame->AddrFrame.Offset = (DWORD64)(LONG64)(LONG)(stack[2]);
StackFrame->AddrStack.Offset = (DWORD64)(LONG64)(LONG)(stack[2]);
WalkX86Init(Process,
Thread,
StackFrame,
ContextRecord,
ReadMemory,
FunctionTableAccess,
GetModuleBase,
TranslateAddress);
return TRUE;
}
}
//
// If a prior frame has stored this frame's EBP, just use it.
// Do sanity check if the saved ebp looks like a valid ebp for current stack
//
if (IS_EBP_SAVED(StackFrame) &&
(StackFrame->AddrFrame.Offset <= SAVE_EBP(StackFrame)) &&
(StackFrame->AddrFrame.Offset + 0x4000 >= SAVE_EBP(StackFrame))) {
StackFrame->AddrFrame.Offset = SAVE_EBP(StackFrame);
FrameAddr = StackFrame->AddrFrame.Offset + 4;
AcceptUnreadableCallsite = TRUE;
WDB((1, " use %X\n", (ULONG)FrameAddr));
} else {
//
// Skip past the FPO frame base and parameters.
//
StackFrame->AddrFrame.Offset +=
(FRAME_SIZE + (PreviousFpoData->cdwParams * 4));
//
// Now this is pointing to the bottom of the non-FPO frame.
// If the frame has an fpo record, use it:
//
if (pFpoData) {
FrameAddr = StackFrame->AddrFrame.Offset +
4* (pFpoData->cbRegs + pFpoData->cdwLocals);
AcceptUnreadableCallsite = TRUE;
} else {
//
// We don't know if the non-fpo frame has any locals, but
// skip past the EBP anyway.
//
FrameAddr = StackFrame->AddrFrame.Offset + 4;
}
WDB((1, " compute %X\n", (ULONG)FrameAddr));
}
//
// at this point we may not be sitting at the base of the frame
// so we now search for the return address and then subtract the
// size of the frame pointer and use that address as the new base.
//
if (pFpoData) {
FuncAddr = GetModuleBase(Process,StackFrame->AddrPC.Offset) + pFpoData->ulOffStart;
FuncSize = pFpoData->cbProcSize;
} else {
FuncAddr = StackFrame->AddrPC.Offset - MAX_CALL;
FuncSize = MAX_CALL;
}
FrameAddr = SearchForReturnAddress( Process,
FrameAddr,
FuncAddr,
FuncSize,
ReadMemory,
GetModuleBase,
AcceptUnreadableCallsite
);
if (FrameAddr) {
StackFrame->AddrFrame.Offset = FrameAddr - STACK_SIZE;
}
if (!DoMemoryReadAll( &StackFrame->AddrFrame, stack, FRAME_SIZE )) {
//
// a failure means that we likely have a bad address.
// returning zero will terminate that stack trace.
//
stack[0] = 0;
}
SAVE_EBP(StackFrame) = (DWORD64)(LONG64)(LONG)(stack[0]);
WDB((1, " save %X\n", stack[0]));
StackFrame->FuncTableEntry = pFpoData;
return TRUE;
}
BOOL
WalkX86_NonFpo_Fpo(
HANDLE Process,
HANDLE Thread,
PFPO_DATA pFpoData,
PFPO_DATA PreviousFpoData,
LPSTACKFRAME64 StackFrame,
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory,
PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccess,
PGET_MODULE_BASE_ROUTINE64 GetModuleBase,
PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress
)
{
BOOL rval;
WDB((1, " WalkNF:\n"));
rval = GetFpoFrameBase( Process,
StackFrame,
pFpoData,
PreviousFpoData,
FALSE,
ReadMemory,
GetModuleBase );
StackFrame->FuncTableEntry = pFpoData;
return rval;
}
BOOL
WalkX86_NonFpo_NonFpo(
HANDLE Process,
HANDLE Thread,
PFPO_DATA pFpoData,
PFPO_DATA PreviousFpoData,
LPSTACKFRAME64 StackFrame,
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory,
PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccess,
PGET_MODULE_BASE_ROUTINE64 GetModuleBase,
PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress
)
{
DWORD stack[FRAME_SIZE*4];
DWORD cb;
WDB((1, " WalkNN:\n"));
//
// a previous function in the call stack was a fpo function that used ebp as
// a general purpose register. ul contains the ebp value that was good before
// that function executed. it is that ebp that we want, not what was just read
// from the stack. what was just read from the stack is totally bogus.
//
if (IS_EBP_SAVED(StackFrame) &&
(StackFrame->AddrFrame.Offset <= SAVE_EBP(StackFrame))) {
StackFrame->AddrFrame.Offset = SAVE_EBP(StackFrame);
SAVE_EBP(StackFrame) = 0;
} else {
//
// read the first dword off the stack
//
if (!DoMemoryReadAll( &StackFrame->AddrFrame, stack, STACK_SIZE )) {
return FALSE;
}
StackFrame->AddrFrame.Offset = (DWORD64)(LONG64)(LONG)(stack[0]);
}
StackFrame->FuncTableEntry = pFpoData;
return TRUE;
}
BOOL
X86ApplyFrameData(
HANDLE Process,
LPSTACKFRAME64 StackFrame,
PX86_CONTEXT ContextRecord,
PFPO_DATA PreviousFpoData,
BOOL FirstFrame,
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory,
PGET_MODULE_BASE_ROUTINE64 GetModuleBase
)
{
IDiaFrameData* DiaFrame;
BOOL Succ = FALSE;
// If we can get VC7-style frame data just execute
// the frame data program to unwind the stack.
// If weren't given a context record we cannot use
// the new VC7 unwind information as we have nowhere
// to save intermediate context values.
if (StackFrame->AddrPC.Mode != AddrModeFlat ||
!g_vc7fpo ||
!ContextRecord ||
!diaGetFrameData(Process, StackFrame->AddrPC.Offset, &DiaFrame))
{
return FALSE;
}
if (FirstFrame)
{
ContextRecord->Ebp = (ULONG)StackFrame->AddrFrame.Offset;
ContextRecord->Esp = (ULONG)StackFrame->AddrStack.Offset;
ContextRecord->Eip = (ULONG)StackFrame->AddrPC.Offset;
}
WDB((1, " Applying frame data program for PC %X SP %X FP %X\n",
ContextRecord->Eip, ContextRecord->Esp, ContextRecord->Ebp));
//
// execute() does not currently work when the PC is
// within the function prologue. This should only
// happen on calls from WalkX86Init, in which case the
// normal failure path here where the non-frame-data
// code will be executed is correct as that will handle
// normal prologue code.
//
X86WalkFrame WalkFrame(Process, ContextRecord,
ReadMemory, GetModuleBase,
PreviousFpoData);
Succ = DiaFrame->execute(&WalkFrame) == S_OK;
if (Succ) {
WDB((1, " Result PC %X SP %X FP %X\n",
ContextRecord->Eip, ContextRecord->Esp, ContextRecord->Ebp));
StackFrame->AddrStack.Mode = AddrModeFlat;
StackFrame->AddrStack.Offset = EXTEND64(ContextRecord->Esp);
StackFrame->AddrFrame.Mode = AddrModeFlat;
// The frame value we want to return is the frame value
// used for the function that was just unwound, not
// the current value of EBP. After the unwind the current
// value of EBP is the caller's EBP, not the callee's
// frame. Instead we always set the callee's frame to
// the offset beyond where the return address would be
// as that's where the frame will be in a normal non-FPO
// function and where we fake it as being for FPO functions.
//
// Separately, we save the true EBP away for future frame use.
// According to VinitD there's a compiler case where it
// doesn't generate proper unwind instructions for restoring
// EBP, so there are some times where EBP is not restored
// to a good value after the execute and we have to fall
// back on searching. If EBP wasn't set during the execute
// we do not save its value.
StackFrame->AddrFrame.Offset =
StackFrame->AddrStack.Offset - FRAME_SIZE;
StackFrame->AddrReturn.Offset = EXTEND64(ContextRecord->Eip);
// XXX drewb - This is causing some failures in the regression
// tests so don't enable it until we fully understand it.
#if 0
if (WalkFrame.WasEbpSet()) {
SAVE_EBP(StackFrame) = EXTEND64(ContextRecord->Ebp);
} else {
WDB((1, " * EBP not recovered\n"));
}
#else
SAVE_EBP(StackFrame) = EXTEND64(ContextRecord->Ebp);
#endif
// Caller may need to allocate this to allow alternate stackwalk with dbghelp code
StackFrame->FuncTableEntry = NULL;
X86ReadFunctionParameters(Process, StackFrame->AddrStack.Offset,
StackFrame, ReadMemory);
} else {
WDB((1, " Apply failed\n"));
}
DiaFrame->Release();
return Succ;
}
VOID
X86UpdateContextFromFrame(
HANDLE Process,
LPSTACKFRAME64 StackFrame,
PX86_CONTEXT ContextRecord,
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory
)
{
ULONG Ebp;
ULONG Done;
if (StackFrame->AddrPC.Mode != AddrModeFlat ||
!ContextRecord) {
return;
}
ContextRecord->Esp = (ULONG)StackFrame->AddrFrame.Offset + FRAME_SIZE;
ContextRecord->Eip = (ULONG)StackFrame->AddrReturn.Offset;
if (IS_EBP_SAVED(StackFrame)) {
ContextRecord->Ebp = (ULONG)SAVE_EBP(StackFrame);
} else {
if (ReadMemory(Process, StackFrame->AddrFrame.Offset,
&Ebp, sizeof(Ebp), &Done) &&
Done == sizeof(Ebp)) {
ContextRecord->Ebp = Ebp;
}
}
if (StackFrame->FuncTableEntry) {
if (!IS_EBP_SAVED(StackFrame)) {
// Don't change Ebp
SAVE_EBP(StackFrame) = ((ULONG) StackFrame->AddrFrame.Offset + STACK_SIZE) +
0xEB00000000; // Add this tag to top 32 bits for marking this as a frame value
// rather than FPO saved EBP
}
}
}
BOOL
WalkX86Next(
HANDLE Process,
HANDLE Thread,
LPSTACKFRAME64 StackFrame,
PX86_CONTEXT ContextRecord,
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory,
PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccess,
PGET_MODULE_BASE_ROUTINE64 GetModuleBase,
PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress
)
{
PFPO_DATA pFpoData = NULL;
PFPO_DATA PrevFpoData = NULL;
FPO_DATA PrevFpoBuffer;
BOOL rVal = TRUE;
DWORD64 Address;
DWORD cb;
DWORD64 ThisPC;
DWORD64 ModuleBase;
DWORD64 SystemRangeStart;
WDB((1, "WalkNext: PC %X, SP %X, FP %X\n",
(ULONG)StackFrame->AddrReturn.Offset,
(ULONG)StackFrame->AddrStack.Offset,
(ULONG)StackFrame->AddrFrame.Offset));
StackFrame->AddrStack.Offset = EXTEND64(StackFrame->AddrStack.Offset);
StackFrame->AddrFrame.Offset = EXTEND64(StackFrame->AddrFrame.Offset);
// FunctionTableAccess often returns pointers to static
// data that gets overwritten on every call. Preserve
// the data of any previous FPO record for the duration
// of this routine so that FunctionTableAccess calls can
// be made without destroying previous FPO data.
if (StackFrame->FuncTableEntry) {
PrevFpoBuffer = *(PFPO_DATA)StackFrame->FuncTableEntry;
PrevFpoData = &PrevFpoBuffer;
}
if (g.AppVersion.Revision >= 6) {
SystemRangeStart = EXTEND64(SYSTEM_RANGE_START(StackFrame));
} else {
//
// This might not really work right with old debuggers, but it keeps
// us from looking off the end of the structure anyway.
//
SystemRangeStart = 0xFFFFFFFF80000000;
}
ThisPC = StackFrame->AddrPC.Offset;
//
// the previous frame's return address is this frame's pc
//
StackFrame->AddrPC = StackFrame->AddrReturn;
if (StackFrame->AddrPC.Mode != AddrModeFlat) {
//
// the call stack is from either WOW or a DOS app
//
SetNonOff32FrameAddress( Process,
Thread,
StackFrame,
ReadMemory,
FunctionTableAccess,
GetModuleBase,
TranslateAddress
);
goto exit;
}
//
// if the last frame was the usermode callback dispatcher,
// switch over to the kernel stack:
//
ModuleBase = GetModuleBase(Process, ThisPC);
if ((g.AppVersion.Revision >= 4) &&
(CALLBACK_STACK(StackFrame) != 0) &&
(pFpoData = PrevFpoData) &&
(CALLBACK_DISPATCHER(StackFrame) ==
ModuleBase + PrevFpoData->ulOffStart) ) {
NextCallback:
rVal = FALSE;
//
// find callout frame
//
if (EXTEND64(CALLBACK_STACK(StackFrame)) >= SystemRangeStart) {
//
// it is the pointer to the stack frame that we want,
// or -1.
Address = EXTEND64(CALLBACK_STACK(StackFrame));
} else {
//
// if it is below SystemRangeStart, it is the offset to
// the address in the thread.
// Look up the pointer:
//
rVal = ReadMemory(Process,
(CALLBACK_THREAD(StackFrame) +
CALLBACK_STACK(StackFrame)),
&Address,
sizeof(DWORD),
&cb);
Address = EXTEND64(Address);
if (!rVal || cb != sizeof(DWORD) || Address == 0) {
Address = 0xffffffff;
CALLBACK_STACK(StackFrame) = 0xffffffff;
}
}
if ((Address == 0xffffffff) ||
!(pFpoData = (PFPO_DATA)
FunctionTableAccess( Process, CALLBACK_FUNC(StackFrame))) ) {
rVal = FALSE;
} else {
StackFrame->FuncTableEntry = pFpoData;
StackFrame->AddrPC.Offset = CALLBACK_FUNC(StackFrame) +
pFpoData->cbProlog;
StackFrame->AddrStack.Offset = Address;
if (!ReadMemory(Process,
Address + CALLBACK_FP(StackFrame),
&StackFrame->AddrFrame.Offset,
sizeof(DWORD),
&cb) ||
cb != sizeof(DWORD)) {
return FALSE;
}
StackFrame->AddrFrame.Offset =
EXTEND64(StackFrame->AddrFrame.Offset);
if (!ReadMemory(Process,
Address + CALLBACK_NEXT(StackFrame),
&CALLBACK_STACK(StackFrame),
sizeof(DWORD),
&cb) ||
cb != sizeof(DWORD))
{
return FALSE;
}
SAVE_TRAP(StackFrame) = 0;
rVal = WalkX86Init(
Process,
Thread,
StackFrame,
ContextRecord,
ReadMemory,
FunctionTableAccess,
GetModuleBase,
TranslateAddress
);
}
return rVal;
}
//
// if there is a trap frame then handle it
//
if (SAVE_TRAP(StackFrame)) {
rVal = ProcessTrapFrame(
Process,
StackFrame,
pFpoData,
PrevFpoData,
ReadMemory,
FunctionTableAccess
);
if (!rVal) {
return rVal;
}
rVal = WalkX86Init(
Process,
Thread,
StackFrame,
ContextRecord,
ReadMemory,
FunctionTableAccess,
GetModuleBase,
TranslateAddress
);
return rVal;
}
//
// if the PC address is zero then we're at the end of the stack
//
//if (GetModuleBase(Process, StackFrame->AddrPC.Offset) == 0)
if (StackFrame->AddrPC.Offset < 65536) {
//
// if we ran out of stack, check to see if there is
// a callback stack chain
//
if (g.AppVersion.Revision >= 4 && CALLBACK_STACK(StackFrame) != 0) {
goto NextCallback;
}
return FALSE;
}
//
// If the frame, pc and return address are all identical, then we are
// at the top of the idle loop
//
if ((StackFrame->AddrPC.Offset == StackFrame->AddrReturn.Offset) &&
(StackFrame->AddrPC.Offset == StackFrame->AddrFrame.Offset))
{
return FALSE;
}
if (X86ApplyFrameData(Process, StackFrame, ContextRecord,
PrevFpoData, FALSE,
ReadMemory, GetModuleBase)) {
// copy FPO_DATA to allow alternating between dbghelp and DIA stackwalk
StackFrame->FuncTableEntry = FunctionTableAccess(Process, StackFrame->AddrPC.Offset);
return TRUE;
}
//
// check to see if the current frame is an fpo frame
//
pFpoData = (PFPO_DATA) FunctionTableAccess(Process, StackFrame->AddrPC.Offset);
if (pFpoData && pFpoData->cbFrame != FRAME_NONFPO) {
if (PrevFpoData && PrevFpoData->cbFrame != FRAME_NONFPO) {
rVal = WalkX86_Fpo_Fpo( Process,
Thread,
pFpoData,
PrevFpoData,
StackFrame,
ReadMemory,
FunctionTableAccess,
GetModuleBase,
TranslateAddress
);
} else {
rVal = WalkX86_NonFpo_Fpo( Process,
Thread,
pFpoData,
PrevFpoData,
StackFrame,
ReadMemory,
FunctionTableAccess,
GetModuleBase,
TranslateAddress
);
}
} else {
if (PrevFpoData && PrevFpoData->cbFrame != FRAME_NONFPO) {
rVal = WalkX86_Fpo_NonFpo( Process,
Thread,
pFpoData,
PrevFpoData,
StackFrame,
ContextRecord,
ReadMemory,
FunctionTableAccess,
GetModuleBase,
TranslateAddress
);
} else {
rVal = WalkX86_NonFpo_NonFpo( Process,
Thread,
pFpoData,
PrevFpoData,
StackFrame,
ReadMemory,
FunctionTableAccess,
GetModuleBase,
TranslateAddress
);
}
}
exit:
StackFrame->AddrFrame.Mode = StackFrame->AddrPC.Mode;
StackFrame->AddrReturn.Mode = StackFrame->AddrPC.Mode;
GetFunctionParameters( Process, Thread, StackFrame,
ReadMemory, GetModuleBase, TranslateAddress );
GetReturnAddress( Process, Thread, StackFrame,
ReadMemory, GetModuleBase, TranslateAddress,
FunctionTableAccess );
X86UpdateContextFromFrame(Process, StackFrame, ContextRecord, ReadMemory);
return rVal;
}
BOOL
WalkX86Init(
HANDLE Process,
HANDLE Thread,
LPSTACKFRAME64 StackFrame,
PX86_CONTEXT ContextRecord,
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemory,
PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccess,
PGET_MODULE_BASE_ROUTINE64 GetModuleBase,
PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress
)
{
UCHAR code[3];
DWORD stack[FRAME_SIZE*4];
PFPO_DATA pFpoData = NULL;
ULONG cb;
ULONG64 ModBase;
StackFrame->Virtual = TRUE;
StackFrame->Reserved[0] =
StackFrame->Reserved[1] =
StackFrame->Reserved[2] = 0;
StackFrame->AddrReturn = StackFrame->AddrPC;
if (StackFrame->AddrPC.Mode != AddrModeFlat) {
goto exit;
}
WDB((1, "WalkInit: PC %X, SP %X, FP %X\n",
(ULONG)StackFrame->AddrPC.Offset, (ULONG)StackFrame->AddrStack.Offset,
(ULONG)StackFrame->AddrFrame.Offset));
if (X86ApplyFrameData(Process, StackFrame, ContextRecord, NULL, TRUE,
ReadMemory, GetModuleBase)) {
// copy FPO_DATA to allow alternating between dbghelp and DIA stackwalk
StackFrame->FuncTableEntry = FunctionTableAccess(Process, StackFrame->AddrPC.Offset);
return TRUE;
}
StackFrame->FuncTableEntry = pFpoData = (PFPO_DATA)
FunctionTableAccess(Process, StackFrame->AddrPC.Offset);
if (pFpoData && pFpoData->cbFrame != FRAME_NONFPO) {
GetFpoFrameBase( Process,
StackFrame,
pFpoData,
pFpoData,
TRUE,
ReadMemory,
GetModuleBase );
goto exit;
} else if (!pFpoData &&
((ModBase = GetModuleBase(Process, StackFrame->AddrPC.Offset)) == 0 ||
ModBase == MM_SHARED_USER_DATA_VA)) {
//
// We have no FPO data and the current IP isn't in
// any known module or the module is debuggers' madeup
// "shareduserdata" module. We'll assume this is a call
// to a bad address, so we expect that the return
// address should be the first DWORD on the stack.
//
if (DoMemoryReadAll( &StackFrame->AddrStack, stack, STACK_SIZE ) &&
GetModuleBase(Process, EXTEND64(stack[0]))) {
// The first DWORD is a code address. We probably
// found a call to a bad location.
SAVE_EBP(StackFrame) = StackFrame->AddrFrame.Offset;
StackFrame->AddrFrame.Offset =
StackFrame->AddrStack.Offset - STACK_SIZE;
goto exit;
}
}
//
// We couldn't figure out anything about the code at
// the current IP so we just assume it's a traditional
// EBP-framed routine.
//
// First check whether eip is in the function prolog
//
memset(code, 0xcc, sizeof(code));
if (!DoMemoryRead( &StackFrame->AddrPC, code, 3, &cb )) {
//
// Assume a call to a bad address if the memory read fails.
//
code[0] = PUSHBP;
}
if ((code[0] == PUSHBP) || (*(LPWORD)&code[0] == MOVBPSP)) {
SAVE_EBP(StackFrame) = StackFrame->AddrFrame.Offset;
StackFrame->AddrFrame.Offset = StackFrame->AddrStack.Offset;
if (StackFrame->AddrPC.Mode != AddrModeFlat) {
StackFrame->AddrFrame.Offset &= 0xffff;
}
if (code[0] == PUSHBP) {
if (StackFrame->AddrPC.Mode == AddrModeFlat) {
StackFrame->AddrFrame.Offset -= STACK_SIZE;
} else {
StackFrame->AddrFrame.Offset -= STACK_SIZE16;
}
}
} else {
//
// We're not in a prologue so assume we're in the middle
// of an EBP-framed function. Read the first dword off
// the stack at EBP and assume that it's the pushed EBP.
//
if (DoMemoryReadAll( &StackFrame->AddrFrame, stack, STACK_SIZE )) {
SAVE_EBP(StackFrame) = EXTEND64(stack[0]);
}
if (StackFrame->AddrPC.Mode != AddrModeFlat) {
StackFrame->AddrFrame.Offset &= 0x0000FFFF;
}
}
exit:
StackFrame->AddrFrame.Mode = StackFrame->AddrPC.Mode;
GetFunctionParameters( Process, Thread, StackFrame,
ReadMemory, GetModuleBase, TranslateAddress );
GetReturnAddress( Process, Thread, StackFrame,
ReadMemory, GetModuleBase, TranslateAddress,
FunctionTableAccess );
X86UpdateContextFromFrame(Process, StackFrame, ContextRecord, ReadMemory);
return TRUE;
}