C++获取当前线程堆栈信息

方法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
);

具体接口说明参考官方文档StackWalk64icon-default.png?t=N7T8https://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 Learnicon-default.png?t=N7T8https://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文库icon-default.png?t=N7T8https://download.csdn.net/download/gaojunhuiwww/88601428

题外话:为什么我会想要打印当前线程堆栈

 因为前面我使用远程注入DLL方式,动态库使用detours拦截了某个接口,进入拦截函数后,我想要看下调用链是什么。

Detours的使用方法_detours 使用_龙语者的博客-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/gaojunhuiwww/article/details/132545312

方法2:使用 CaptureStackBackTrace接口

CaptureStackBackTrace 函数 - Win32 apps | Microsoft Learnicon-default.png?t=N7T8https://learn.microsoft.com/zh-cn/windows/win32/debug/capturestackbacktrace

以下示例代码摘录自DLLInject的Misc.cpp文件

GitHub - Prophecy2015/DLLInjectContribute to Prophecy2015/DLLInject development by creating an account on GitHub.icon-default.png?t=N7T8https://github.com/Prophecy2015/DLLInject.git

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);
}

  • 23
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
获取当前所有线程信息,你可以使用C++标准库中的`<thread>`头文件中的函数,结合操作系统提供的相关API来实现。以下是一个基本的示例代码: ```cpp #include <iostream> #include <thread> #include <vector> #include <Windows.h> // 适用于Windows操作系统的API头文件 // 线程信息结构体 struct ThreadInfo { DWORD threadId; // 线程ID HANDLE threadHandle; // 线程句柄 }; // 获取当前所有线程信息的函数 std::vector<ThreadInfo> GetAllThreadsInfo() { std::vector<ThreadInfo> threads; // 获取系统中所有线程的快照 HANDLE threadSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); if (threadSnapshot == INVALID_HANDLE_VALUE) { std::cerr << "Failed to create thread snapshot." << std::endl; return threads; } // 遍历快照中的所有线程 THREADENTRY32 threadEntry; threadEntry.dwSize = sizeof(threadEntry); if (Thread32First(threadSnapshot, &threadEntry)) { do { if (threadEntry.th32OwnerProcessID == GetCurrentProcessId()) { // 只获取当前进程的线程 HANDLE threadHandle = OpenThread(THREAD_ALL_ACCESS, FALSE, threadEntry.th32ThreadID); if (threadHandle != NULL) { ThreadInfo threadInfo; threadInfo.threadId = threadEntry.th32ThreadID; threadInfo.threadHandle = threadHandle; threads.push_back(threadInfo); } } } while (Thread32Next(threadSnapshot, &threadEntry)); } // 关闭快照句柄 CloseHandle(threadSnapshot); return threads; } int main() { // 获取当前所有线程信息 std::vector<ThreadInfo> threads = GetAllThreadsInfo(); // 打印线程信息 for (const ThreadInfo& thread : threads) { std::cout << "Thread ID: " << thread.threadId << std::endl; // 可以通过调用GetThreadTimes等API获取更多线程信息 // ... // 关闭线程句柄 CloseHandle(thread.threadHandle); } return 0; } ``` 这个示例代码使用了Windows操作系统的API来获取线程信息,如果你使用的是其他操作系统,你可能需要使用不同的API来实现相同的功能。请注意,这只是一个基本示例,你可以根据自己的需求进行扩展和修改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙语者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值