方法1:使用DbgHelp.dll中的StackWalk64
BOOL IMAGEAPI StackWalk64(
[in] DWORD MachineType,
[in] HANDLE hProcess,
[in] HANDLE hThread,
[in, out] LPSTACKFRAME64 StackFrame,
[in, out] PVOID ContextRecord,
[in, optional] PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine,
[in, optional] PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine,
[in, optional] PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine,
[in, optional] PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress
);
具体接口说明参考官方文档StackWalk64https://learn.microsoft.com/zh-cn/windows/win32/api/dbghelp/nf-dbghelp-stackwalk64
调用该接口前,需要获取到某一时刻的上下文信息 ,也就是参数中的
[in, out] PVOID ContextRecord,
该入参决定了需要打印的堆栈的开始位置,而我们需要打印当前线程的堆栈,所以直接调用
CONTEXT cr = { 0 };
RtlCaptureContext(&cr);
接口说明参考官方文档
RtlCaptureContext 函数 (winnt.h) - Win32 apps | Microsoft Learnhttps://learn.microsoft.com/zh-cn/windows/win32/api/winnt/nf-winnt-rtlcapturecontext现在关键参数有了,我们就需要准备StackWalk64函数的基本参数了
64位下,MachineType=IMAGE_FILE_MACHINE_AMD64
获取当前进程和线程句柄
HANDLE hProcess = GetCurrentProcess();
HANDLE hThread = GetCurrentThread();
初始化符号集并加载符号
SymInitialize(hProcess, 0, TRUE);
参数2是用户符号搜索路径,符号文件和可执行程序分开存放的可以设置该参数
参数3是否加载所有模块的符号,我们这边打印堆栈信息,需要选择TRUE,堆栈任何模块都有可能涉及到。不过加载全部模块,该接口会比较耗时。
设置初始栈帧信息
STACKFRAME sf = { 0 };
#ifdef _IMAGEHLP64
dwMachineType = IMAGE_FILE_MACHINE_AMD64;
sf.AddrPC.Offset = cr.Rip;
sf.AddrPC.Mode = AddrModeFlat;
sf.AddrFrame.Offset = cr.Rbp;
sf.AddrFrame.Mode = AddrModeFlat;
sf.AddrStack.Offset = cr.Rsp;
sf.AddrStack.Mode = AddrModeFlat;
#else
dwMachineType = IMAGE_FILE_MACHINE_I386;
sf.AddrPC.Offset = cr.Eip;
sf.AddrPC.Mode = AddrModeFlat;
sf.AddrFrame.Offset = cr.Ebp;
sf.AddrFrame.Mode = AddrModeFlat;
sf.AddrStack.Offset = cr.Esp;
sf.AddrStack.Mode = AddrModeFlat;
#endif
按照官方文档要求,64位填充Rip/Rbp/Rsp,32位进程填充Eip/Ebp/Esp;
调用StackWalk64
while (StackWalk(dwMachineType, hProcess, hThread, &sf, &cr, 0, SymFunctionTableAccess, SymGetModuleBase, nullptr))
{
//...
}
这里While的目的是遍历堆栈的所有栈帧,每次调用,sf和cr都会改变。一层层获取栈帧信息
栈帧信息
STACKFRAME 中存的是每次遍历的栈帧信息,你可以通过打印STACKFRAME中的AddrReturn来显示返回地址(其实就是上一个栈帧的地址),还可以通过打印STACKFRAME中的param来打印最多四个可能的入参。
typedef struct _tagSTACKFRAME64 {
ADDRESS64 AddrPC; // program counter
ADDRESS64 AddrReturn; // return address
ADDRESS64 AddrFrame; // frame pointer
ADDRESS64 AddrStack; // stack pointer
ADDRESS64 AddrBStore; // backing store pointer
PVOID FuncTableEntry; // pointer to pdata/fpo or NULL
DWORD64 Params[4]; // possible arguments to the function
BOOL Far; // WOW far call
BOOL Virtual; // is this a virtual frame?
DWORD64 Reserved[3];
KDHELP64 KdHelp;
} STACKFRAME64, *LPSTACKFRAME64;
打印每一层栈帧的地址符号信息
auto address = sf.AddrPC.Offset;
DWORD64 dwDisplacement = 0;
SYMBOL_INFO_PACKAGE symbol = { 0 };
symbol.si.SizeOfStruct = sizeof(symbol.si);
symbol.si.MaxNameLen = sizeof(symbol.name) / sizeof(TCHAR);
if (TRUE == SymFromAddr(hProcess, address, &dwDisplacement, &symbol.si))
{
_tprintf(_T("\t%IX %s + %Id\n"), address, symbol.si.Name, dwDisplacement);
}
打印每一层栈帧的行号信息
while (StackWalk(dwMachineType, hProcess, hThread, &sf, &cr, 0, SymFunctionTableAccess, pSymGetModuleBase, nullptr))
{
auto address = sf.AddrPC.Offset;
DWORD64 dwDisplacement = 0;
SYMBOL_INFO_PACKAGE symbol = { 0 };
symbol.si.SizeOfStruct = sizeof(symbol.si);
symbol.si.MaxNameLen = sizeof(symbol.name) / sizeof(TCHAR);
if (TRUE == SymFromAddr(hProcess, address, &dwDisplacement, &symbol.si))
{
DWORD dwLineDisplacement = 0;
IMAGEHLP_LINE line = { sizeof(line) };
if (TRUE == SymGetLineFromAddr(hProcess, address, &dwLineDisplacement, &line))
{
_tprintf(_T("\t%IX %s + %Id(%s:%u)\n"), address, symbol.si.Name, dwDisplacement, line.FileName, line.LineNumber);
}
else
{
_tprintf(_T("\t%IX %s + %Id\n"), address, symbol.si.Name, dwDisplacement);
}
}
else
{
_tprintf(_T("\t%IX Cannot find symbol!\n"), address);
}
}
最后记得Cleanup
SymCleanup(hProcess);
完整源码:
StackWalk64打印当前线程堆栈的示例资源-CSDN文库https://download.csdn.net/download/gaojunhuiwww/88601428
题外话:为什么我会想要打印当前线程堆栈
因为前面我使用远程注入DLL方式,动态库使用detours拦截了某个接口,进入拦截函数后,我想要看下调用链是什么。
Detours的使用方法_detours 使用_龙语者的博客-CSDN博客https://blog.csdn.net/gaojunhuiwww/article/details/132545312
方法2:使用 CaptureStackBackTrace接口
以下示例代码摘录自DLLInject的Misc.cpp文件
void CMisc::ShowTraceStarck()
{
if (!g_DbgHelper.Init())
{
DLL_TRACE(_T("Load DbgHelp.dll failed!"));
return ;
}
constexpr int MAX_STACK_FRAMES = 12;
constexpr int FRAME_INFO_LEN = 2048;
constexpr int STACK_INFO_LEN = FRAME_INFO_LEN * MAX_STACK_FRAMES;
void *pStack[MAX_STACK_FRAMES];
static TCHAR szStackInfo[STACK_INFO_LEN] = { 0 };
static TCHAR szFrameInfo[FRAME_INFO_LEN] = { 0 };
DWORD Options = g_DbgHelper.m_pSymGetOptions();
Options = Options | SYMOPT_LOAD_LINES;
g_DbgHelper.m_pSymSetOptions(Options);
TCHAR SymbolPath[256] = { 0 };
GetCurrentDirectory(sizeof(SymbolPath) / sizeof(TCHAR), SymbolPath);
_tcscat_s(SymbolPath, _T(";"));
_tcscat_s(SymbolPath, g_szPDBPath);
HANDLE hProcess = GetCurrentProcess();
BOOL bRet = g_DbgHelper.m_pSymInitialize(hProcess, SymbolPath, TRUE);
if (FALSE == bRet)
{
DLL_TRACE(_T("SymInitialize error ..."));
return;
}
WORD frames = CaptureStackBackTrace(0, MAX_STACK_FRAMES, pStack, NULL);
_sntprintf_s(szStackInfo, FRAME_INFO_LEN - 1, _T("stack traceback:%d\n"), frames);
for (WORD i = 0; i < frames; ++i) {
DWORD64 address = (DWORD64)(pStack[i]);
DWORD64 displacementSym = 0;
SYMBOL_INFO_PACKAGE stSymbolInfo = { 0 };
stSymbolInfo.si.SizeOfStruct = sizeof(SYMBOL_INFO);
stSymbolInfo.si.MaxNameLen = MAX_SYM_NAME;
DWORD displacementLine = 0;
IMAGEHLP_LINE line = { sizeof(line) };
if (TRUE == g_DbgHelper.m_pSymFromAddr(hProcess, address, &displacementSym, &stSymbolInfo.si))
{
if (TRUE == g_DbgHelper.m_pSymGetLineFromAddr(hProcess, address, &displacementLine, &line))
{
_sntprintf_s(szFrameInfo, FRAME_INFO_LEN - 1, _T("\t0x%IX %s() at %s:%d\n"),
stSymbolInfo.si.Address, stSymbolInfo.si.Name, line.FileName, line.LineNumber);
}
else
{
_sntprintf_s(szFrameInfo, FRAME_INFO_LEN - 1, _T("\t0x%IX %s()\n"), stSymbolInfo.si.Address, stSymbolInfo.si.Name);
}
}
else
{
_sntprintf_s(szFrameInfo, FRAME_INFO_LEN - 1, _T("\t0x%IX can not find symbol\n"), address);
}
_tcscat_s(szStackInfo, szFrameInfo);
}
g_DbgHelper.m_pSymCleanup(hProcess);
DLL_TRACE(_T("%s"), szStackInfo);
}