首先,我使用RtlCaptureContext获取当前上下文记录,然后使用StackWalk64获取单个堆栈帧.现在,我意识到每当我关闭程序并重新启动时,STACKFRAME64.AddrPC中的程序计数器实际上会针对特定代码行进行更改.出于某种原因,我认为只要我不更改源代码并重新编译它,特定代码行的PC地址就会保持不变.
我需要PC-Address使用SymFromAddr和SymGetLineFromAddr64来获取有关被调用函数,代码文件和行号的信息.不幸的是,只有程序调试数据库(PDB-File)存在才能工作,但我不允许将其提供给客户端.
我的计划是记录调用堆栈的PC地址(只要需要),然后从客户端发送给我.这样我就可以使用我的PDB文件来找出调用了哪些函数,但这当然只有在PC-Addresses是唯一标识符时才有效.由于每次启动程序时它们都会改变,我不能使用这种方法.
您是否知道更好的方法来读取调用堆栈或克服更改程序计数器的问题?
我认为一种可能的解决方案可能是始终获取已知位置的PC地址并将其用作参考来仅确定不同PC地址之间的偏移量.这似乎有效,但我不确定这是否是一个有效的方法,并将始终有效.
非常感谢您的帮助!我将在codeproject.com上发布最终(封装的)解决方案,如果你喜欢,我会说你帮了我.
解决方法
>获取CONTEXT结构.您对程序计数器成员感兴趣.由于CONTEXT取决于平台,因此您必须自己解决.初始化时已经这样做了,例如对于x64 Windows,STACKFRAME64.AddrPC.Offset = CONTEXT.Rip.现在我们开始堆栈遍历并使用STACKFRAME64.AddrPC.Offset,由StaclkWalk64填充作为我们的起点.
>您需要使用分配基址将其转换为相对虚拟地址(RVA):RVA = STACKFRAME64.AddrPC.Offset – AllocationBase.您可以使用VirtualQuery获取AllocationBase.
>一旦你有了这个,你需要找到这个RVA落入哪个部分并从中减去部分起始地址以获得SectionOffset:SectionOffset = RVA – SectionBase = STACKFRAME64.AddrPC.Offset – AllocationBase – SectionBase.为此,您需要访问PE映像头结构(IMAGE_DOS_HEADER,IMAGE_NT_HEADER,IMAGE_SECTION_HEADER)以获取PE中的节数及其开始/结束地址.这很简单.
而已.现在您在PE图像中有节号和偏移量.函数偏移量是小于.map文件中SectionOffset的最高偏移量.
如果你愿意,我可以稍后发布代码.
#include <iostream> #include <windows.h> #include <dbghelp.h> void GenerateReport( void ) { ::CONTEXT lContext; ::ZeroMemory( &lContext,sizeof( ::CONTEXT ) ); ::RtlCaptureContext( &lContext ); ::STACKFRAME64 lFrameStack; ::ZeroMemory( &lFrameStack,sizeof( ::STACKFRAME64 ) ); lFrameStack.AddrPC.Offset = lContext.Rip; lFrameStack.AddrFrame.Offset = lContext.Rbp; lFrameStack.AddrStack.Offset = lContext.Rsp; lFrameStack.AddrPC.Mode = lFrameStack.AddrFrame.Mode = lFrameStack.AddrStack.Mode = AddrModeFlat; ::DWORD lTypeMachine = IMAGE_FILE_MACHINE_AMD64; for( auto i = ::DWORD(); i < 32; i++ ) { if( !::StackWalk64( lTypeMachine,::GetCurrentProcess(),::GetCurrentThread(),&lFrameStack,lTypeMachine == IMAGE_FILE_MACHINE_I386 ? 0 : &lContext,nullptr,&::SymFunctionTableAccess64,&::SymGetModuleBase64,nullptr ) ) { break; } if( lFrameStack.AddrPC.Offset != 0 ) { ::MEMORY_BASIC_INFORMATION lInfoMemory; ::VirtualQuery( ( ::PVOID )lFrameStack.AddrPC.Offset,&lInfoMemory,sizeof( lInfoMemory ) ); ::DWORD64 lBaseAllocation = reinterpret_cast< ::DWORD64 >( lInfoMemory.AllocationBase ); ::TCHAR lNameModule[ 1024 ]; ::GetModuleFileName( reinterpret_cast< ::HMODULE >( lBaseAllocation ),lNameModule,1024 ); PIMAGE_DOS_HEADER lHeaderDOS = reinterpret_cast< PIMAGE_DOS_HEADER >( lBaseAllocation ); PIMAGE_NT_HEADERS lHeaderNT = reinterpret_cast< PIMAGE_NT_HEADERS >( lBaseAllocation + lHeaderDOS->e_lfanew ); PIMAGE_SECTION_HEADER lHeaderSection = IMAGE_FIRST_SECTION( lHeaderNT ); ::DWORD64 lRVA = lFrameStack.AddrPC.Offset - lBaseAllocation; ::DWORD64 lNumberSection = ::DWORD64(); ::DWORD64 lOffsetSection = ::DWORD64(); for( auto lCnt = ::DWORD64(); lCnt < lHeaderNT->FileHeader.NumberOfSections; lCnt++,lHeaderSection++ ) { ::DWORD64 lSectionBase = lHeaderSection->VirtualAddress; ::DWORD64 lSectionEnd = lSectionBase + max( lHeaderSection->SizeOfRawData,lHeaderSection->Misc.VirtualSize ); if( ( lRVA >= lSectionBase ) && ( lRVA <= lSectionEnd ) ) { lNumberSection = lCnt + 1; lOffsetSection = lRVA - lSectionBase; break; } } std::cout << lNameModule << " : 000" << lNumberSection << " : " << reinterpret_cast< void * >( lOffsetSection ) << std::endl; } else { break; } } } void Run( void ); void Run( void ) { GenerateReport(); std::cout << "------------------" << std::endl; } int main( void ) { ::SymSetOptions( SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS ); ::SymInitialize( ::GetCurrentProcess(),1 ); try { Run(); } catch( ... ) { } ::SymCleanup( ::GetCurrentProcess() ); return ( 0 ); }
注意,我们的调用栈是(内向外)GenerateReport() – > Run() – > main().
程序输出(在我的机器上,路径是绝对的):
D:\Work\C++\Source\Application\Prototype.Console\Prototype.Console.exe : 0001 : 0000000000002F8D D:\Work\C++\Source\Application\Prototype.Console\Prototype.Console.exe : 0001 : 00000000000031EB D:\Work\C++\Source\Application\Prototype.Console\Prototype.Console.exe : 0001 : 0000000000003253 D:\Work\C++\Source\Application\Prototype.Console\Prototype.Console.exe : 0001 : 0000000000007947 C:\Windows\system32\kernel32.dll : 0001 : 000000000001552D C:\Windows\SYSTEM32\ntdll.dll : 0001 : 000000000002B521 ------------------
现在,就地址而言,呼叫栈是(内向外)00002F8D-> 000031EB-> 00003253-> 00007947-> 0001552D-> 0002B521.
将前三个偏移量与.map文件内容进行比较:
... 0001:00002f40 ?GenerateReport@@YAXXZ 0000000140003f40 f FMain.obj 0001:000031e0 ?Run@@YAXXZ 00000001400041e0 f FMain.obj 0001:00003220 main 0000000140004220 f FMain.obj ...
其中00002f40最接近00002F8D的小偏移,依此类推.最后三个地址是指调用main(_tmainCRTstartup等)的CRT / OS函数 – 我们应该忽略它们……
因此,我们可以看到我们能够借助.map文件恢复堆栈跟踪.为了生成抛出异常的堆栈跟踪,您所要做的就是将GenerateReport()代码放入异常构造函数中(实际上,这个GenerateReport()取自我的自定义异常类构造函数代码(它的某些部分)) .