Using information form CONTEXT
you can find function section and offset in PE image. For example, you can use this info to get function name from .map file generated by linker.
Get CONTEXT
struct. You are interested in program counter member. Since CONTEXT
is platform-dependent, you have to figure it out for yourself. You do it already when you initialize, for example STACKFRAME64.AddrPC.Offset = CONTEXT.Rip
for x64 Windows. Now we start stack walk and use STACKFRAME64.AddrPC.Offset
, filled by StaclkWalk64
as our starting point.
You need to translate it to Relative Virtual Address (RVA) using allocation base address: RVA = STACKFRAME64.AddrPC.Offset - AllocationBase
. You can get AllocationBase
using VirtualQuery
.
Once you have that, you need to find into which Section this RVA falls and subtract section start address from it to get SectionOffset: SectionOffset = RVA - SectionBase = STACKFRAME64.AddrPC.Offset - AllocationBase - SectionBase
. In order to do that you need to access PE image header structure (IMAGE_DOS_HEADER, IMAGE_NT_HEADER, IMAGE_SECTION_HEADER) to get number of sections in PE and their start/end addresses. It's pretty straightforward.
That's it. Now you have section number and offset in PE image. Function offset is the highest offset smaller than SectionOffset in .map file.
I can post code later, if you like.
EDIT: Code to print function address
(we assume x64 generic 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 );
}
Notice, our call stack is (inside out) GenerateReport()->Run()->main()
.
Program output (on my machine, path is absolute):
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
------------------
Now, call stack in terms of addresses is (inside out) 00002F8D->000031EB->00003253->00007947->0001552D->0002B521
.
Comparing first three offsets to .map
file content:
...
0001:00002f40 ?GenerateReport@@YAXXZ 0000000140003f40 f FMain.obj
0001:000031e0 ?Run@@YAXXZ 00000001400041e0 f FMain.obj
0001:00003220 main 0000000140004220 f FMain.obj
...
where 00002f40
is closest smaller offset to 00002F8D
and so on. Last three addresses refer to CRT/OS functions that call main
(_tmainCRTstartup
etc) - we should ignore them...
So, we can see that we are able to recover stack trace with help of .map
file. In order to generate stack trace for thrown exception, all you have to do is to place GenerateReport()
code into exception constructor (in fact, this GenerateReport()
was taken from my custom exception class constructor code (some part of it it) ) .