我正在使用Windows DbgHelp库来转储我的C ++应用程序的callstack。 PDB位于正确的位置,我成功报告了堆栈帧。我有一个涉及的类型遍历系统打印出所有本地人,这主要是工作。但是,在某些情况下,我收到错误的地址并导致访问冲突。一种情况是函数按值传递一个对象:
struct Object { float x,y,z; }
void foo( Object objectByValue)
{
// dump callstack
}
在这种情况下,为objectByValue计算的地址是错误的。它靠近堆栈上的正确位置,但不完全正确。我在找到正确地址的信息时遇到了一些困难。我正在做以下事情:
对于堆栈上的变量,此地址是正确的,因为对于大多数其他情况。但是,它不属于某些情况,包括这种情况。
我在下面输入了一个工作示例,其中包含以下输出:
main-> Address of on stack: 000000000020B0D0 = { 1.000000, 2.000000,
3.000000 }
foo-> Address of parameters: 000000000020D6F8 = { 1.000000, 2.000000, 3.000000 }
Print stack from bottom up:
Frame: foo Variable: objByValue offset=0xe0 address=0x20b090 size=8204
Frame: main Variable: objOnStack offset=0x10 address=0x20b0d0 size=8204
您可以在示例中看到从堆栈上的变量计算的地址是正确的,但是对于pass by value,它是错误的。
有没有人对如何正确计算这个值有任何见解?
#include "stdafx.h"
#include <windows.h>
#include <stdint.h>
#pragma comment(lib, "dbghelp.lib")
#pragma pack( push, before_imagehlp, 8 )
#include <imagehlp.h>
#pragma pack( pop, before_imagehlp )
// Normally it should be enough to use 'CONTEXT_FULL' (better would be 'CONTEXT_ALL')
#define USED_CONTEXT_FLAGS CONTEXT_FULL
#if defined(_M_AMD64)
const int ImageFileMachine = IMAGE_FILE_MACHINE_AMD64;
#else
const int ImageFileMachine = IMAGE_FILE_MACHINE_I386;
#endif
struct BaseAddresses
{
uint64_t Rsp;
uint64_t Rbp;
};
const int C_X64_REGISTER_RBP = 334; // Frame Base Pointer register
const int C_X64_REGISTER_RSP = 335; // Stack Pointer register (common in release builds with frame pointer removal)
BOOL EnumSymbolsCallback(PSYMBOL_INFO pSymInfo, ULONG SymbolSize, PVOID UserContext)
{
BaseAddresses * pBaseAddresses = (BaseAddresses*)UserContext;
ULONG64 base = 0;
if ((pSymInfo->Flags & SYMFLAG_REGREL) != 0)
{
switch (pSymInfo->Register)
{
case C_X64_REGISTER_RBP:
base = (ULONG64)pBaseAddresses->Rbp;
break;
case C_X64_REGISTER_RSP:
base = (ULONG64)pBaseAddresses->Rsp;
break;
default:
exit(0);
}
}
ULONG64 address = base + pSymInfo->Address;
printf("Variable: %s offset=0x%llx address=0x%llx size=%lu\n", pSymInfo->Name, pSymInfo->Address, address, pSymInfo->Size);
return TRUE;
}
DWORD DumpStackTrace()
{
HANDLE mProcess = GetCurrentProcess();
HANDLE mThread = GetCurrentThread();
if (!SymInitialize(mProcess, NULL, TRUE)) // load symbols, invasive
return 0;
CONTEXT c;
memset(&c, 0, sizeof(CONTEXT));
c.ContextFlags = USED_CONTEXT_FLAGS;
RtlCaptureContext(&c);
// SYMBOL_INFO & buffer storage
char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];
PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer;
STACKFRAME64 frame;
memset(&frame, 0, sizeof(STACKFRAME64));
DWORD64 displacement_from_symbol = 0;
printf("Print stack from bottom up:\n");
int framesToSkip = 1; // skip reporting this frame
do
{
// Get next stack frame
if (!StackWalk64(ImageFileMachine, mProcess, mThread, &frame, &c, nullptr, SymFunctionTableAccess64, SymGetModuleBase64, nullptr))
{
break;
}
// Lookup symbol name using the address
pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
pSymbol->MaxNameLen = MAX_SYM_NAME;
if (!SymFromAddr(mProcess, (ULONG64)frame.AddrPC.Offset, &displacement_from_symbol, pSymbol))
return false;
if (framesToSkip > 0)
{
framesToSkip--;
continue;
}
printf("Frame: %s\n", pSymbol->Name);
// Setup the context to get to the parameters
IMAGEHLP_STACK_FRAME imSFrame = { 0 };
imSFrame.InstructionOffset = frame.AddrPC.Offset;
if (!SymSetContext(mProcess, &imSFrame, NULL))
return false;
BaseAddresses addresses;
addresses.Rbp = c.Rbp;
addresses.Rsp = c.Rsp;
if (!SymEnumSymbols(mProcess, 0, 0, EnumSymbolsCallback, &addresses))
{
return false;
}
if (strcmp(pSymbol->Name, "main") == 0)
break;
} while (frame.AddrReturn.Offset != 0);
SymCleanup(mProcess);
return 0;
}
struct Structure
{
float x, y, z;
};
void foo(Structure objByValue)
{
printf("foo-> Address of parameters: %p = { %f, %f, %f }\n", &objByValue, objByValue.x, objByValue.y, objByValue.z);
DumpStackTrace();
}
int main()
{
Structure objOnStack = { 1, 2, 3 };
printf("main-> Address of on stack: %p = { %f, %f, %f }\n", &objOnStack, objOnStack.x, objOnStack.y, objOnStack.z);
foo(objOnStack);
return 0;
}
在阅读有关X64调用约定的文档后,我发现了以下句子:
任何不适合8个字节或不是1,2,4或8个字节的参数必须通过引用传递1
这就解释了有趣的地址 - 调试符号给我的是存储完整数据引用的内存地址。所以我的代码流基本上说:
if(符号是参数且大小> 8)address = *(uint64_t)地址; //取消引用
然后通过我的类型系统传递该地址可以正确解析。
我认为其他人可能会觉得这很有用,因为它没有在DbgHelp库中的任何地方记录,虽然有些人可能理解调用约定,但我没有想到传回的符号数据不包含有助于指示的东西这个。