DbgHelp:函数参数地址错误,当它是 x64 上的按值传递符号时
DbgHelp: Wrong address of function parameter when it is a pass-by-value symbol on x64
我正在使用 Windows DbgHelp 库转储我的 C++ 应用程序的调用堆栈。 PDB 位于正确的位置,我正在成功报告堆栈帧。我有一个复杂的类型遍历系统,它打印出所有的本地人,这是 mostly 工作。但是,在某些情况下,我得到了错误的地址并导致访问冲突。一种情况是当函数按值传递对象时:
struct Object { float x,y,z; }
void foo( Object objectByValue)
{
// dump callstack
}
在这种情况下,为objectByValue计算的地址是错误的。它是 near 在堆栈中的正确位置,但不完全是。我在查找有关正确地址的信息时遇到了一些困难。我正在执行以下操作:
- 使用 SymSetContext(...) 设置正确的上下文
- 通过回调调用 SymEnumSymbols
- 在回调中,检查是否设置了 SYMFLAG_REGREL
- 分配地址 = SymInfo->地址 + context.rbp 或 context.rsp 取决于
SymInfo.Register 值(CV_AMD64_RBP 或 CV_AMD64_RSP 仅
永远存在 )
- 然后我使用该地址访问变量。
对于堆栈上的变量,这个地址是正确的,对于大多数其他情况也是如此。但是,在某些情况下并非如此,包括这种情况。
我在下面包含了一个工作示例,输出如下:
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
从例子中可以看出,从栈上的变量计算出的地址是正确的,但是传值是错误的。
有人知道我如何正确计算这个值吗?
#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 调用约定的文档后,我发现了以下句子:
Any argument that doesn’t fit in 8 bytes, or isn't 1, 2, 4, or 8
bytes, must be passed by reference 1
这就解释了这个有趣的地址——调试符号给我的是存储完整数据引用的内存地址。所以我的代码流程基本上说:
if ( symbol 为参数且大小 > 8 )
地址 = *(uint64_t)地址; // 解引用
然后通过我的类型系统传递该地址正确解析。
我认为其他人可能会觉得这很有用,因为它在 DbgHelp 库中的任何地方都没有记录,虽然有些人可能理解调用约定,但我没有想到传回的符号数据不会包含有助于表明这一点的内容。
我正在使用 Windows DbgHelp 库转储我的 C++ 应用程序的调用堆栈。 PDB 位于正确的位置,我正在成功报告堆栈帧。我有一个复杂的类型遍历系统,它打印出所有的本地人,这是 mostly 工作。但是,在某些情况下,我得到了错误的地址并导致访问冲突。一种情况是当函数按值传递对象时:
struct Object { float x,y,z; }
void foo( Object objectByValue)
{
// dump callstack
}
在这种情况下,为objectByValue计算的地址是错误的。它是 near 在堆栈中的正确位置,但不完全是。我在查找有关正确地址的信息时遇到了一些困难。我正在执行以下操作:
- 使用 SymSetContext(...) 设置正确的上下文
- 通过回调调用 SymEnumSymbols
- 在回调中,检查是否设置了 SYMFLAG_REGREL
- 分配地址 = SymInfo->地址 + context.rbp 或 context.rsp 取决于 SymInfo.Register 值(CV_AMD64_RBP 或 CV_AMD64_RSP 仅 永远存在 )
- 然后我使用该地址访问变量。
对于堆栈上的变量,这个地址是正确的,对于大多数其他情况也是如此。但是,在某些情况下并非如此,包括这种情况。
我在下面包含了一个工作示例,输出如下:
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
从例子中可以看出,从栈上的变量计算出的地址是正确的,但是传值是错误的。
有人知道我如何正确计算这个值吗?
#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 调用约定的文档后,我发现了以下句子:
Any argument that doesn’t fit in 8 bytes, or isn't 1, 2, 4, or 8 bytes, must be passed by reference 1
这就解释了这个有趣的地址——调试符号给我的是存储完整数据引用的内存地址。所以我的代码流程基本上说:
if ( symbol 为参数且大小 > 8 ) 地址 = *(uint64_t)地址; // 解引用
然后通过我的类型系统传递该地址正确解析。
我认为其他人可能会觉得这很有用,因为它在 DbgHelp 库中的任何地方都没有记录,虽然有些人可能理解调用约定,但我没有想到传回的符号数据不会包含有助于表明这一点的内容。