使用CONTEXT 的信息表格,您可以在PE 图像中找到函数部分和偏移量。例如,您可以使用此信息从链接器生成的 .map 文件中获取函数名称。
获取CONTEXT 结构。您对节目柜台会员感兴趣。由于CONTEXT 依赖于平台,因此您必须自己弄清楚。您在初始化时已经这样做了,例如 STACKFRAME64.AddrPC.Offset = CONTEXT.Rip 用于 x64 Windows。现在我们开始堆栈遍历,并使用STACKFRAME64.AddrPC.Offset,由StaclkWalk64 填充作为我们的起点。
您需要使用分配基地址将其转换为相对虚拟地址 (RVA):RVA = STACKFRAME64.AddrPC.Offset - AllocationBase。您可以使用VirtualQuery 获取AllocationBase。
1234563为此,您需要访问 PE 映像头结构(IMAGE_DOS_HEADER、IMAGE_NT_HEADER、IMAGE_SECTION_HEADER)以获取 PE 中的节数及其开始/结束地址。这很简单。
就是这样。现在你在 PE 图像中有节号和偏移量。函数偏移量是 .map 文件中小于 SectionOffset 的最大偏移量。
如果你愿意,我可以稍后发布代码。
编辑:打印 function address 的代码(我们假设 x64 通用 CPU):
#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(), 0, 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() 是从我的自定义异常类构造函数代码中获取的(它的一部分)) .