Используя информационную форму CONTEXT
, вы можете найти раздел функций и смещение в изображении PE.Например, вы можете использовать эту информацию для получения имени функции из файла .map, сгенерированного компоновщиком.
Get CONTEXT
struct.Вы заинтересованы в участнике программы.Поскольку CONTEXT
зависит от платформы, вы должны сами это выяснить.Вы делаете это уже при инициализации, например STACKFRAME64.AddrPC.Offset = CONTEXT.Rip
для x64 Windows.Теперь мы начинаем обход стека и используем STACKFRAME64.AddrPC.Offset
, заполненный StaclkWalk64
в качестве нашей отправной точки.
Вам необходимо преобразовать его в относительный виртуальный адрес (RVA), используя базовый адрес выделения:RVA = STACKFRAME64.AddrPC.Offset - AllocationBase
.Вы можете получить AllocationBase
, используя VirtualQuery
.
Когда у вас есть это, вам нужно выяснить, в какой раздел попадает этот RVA, и вычесть из него начальный адрес раздела, чтобы получить SectionOffset: SectionOffset = RVA - SectionBase = STACKFRAME64.AddrPC.Offset - AllocationBase - SectionBase
.Для этого вам нужно получить доступ к структуре заголовка образа PE (IMAGE_DOS_HEADER, IMAGE_NT_HEADER, IMAGE_SECTION_HEADER), чтобы получить количество разделов в PE и их начальный / конечный адреса.Это довольно просто.
Вот и все.Теперь у вас есть номер раздела и смещение в изображении PE.Смещение функции - это наибольшее смещение, меньшее, чем SectionOffset в файле .map.
Я могу опубликовать код позже, если хотите.
РЕДАКТИРОВАТЬ: Код для печати function address
(мы предполагаем x64 универсальный ЦП):
#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
и так далее.Последние три адреса относятся к функциям CRT / OS, которые вызывают main
(_tmainCRTstartup
и т. Д.) - мы должны их игнорировать ...
Итак, мы видим, что мы можем восстановить трассировку стека с помощью.map
файл.Чтобы сгенерировать трассировку стека для сгенерированного исключения, все, что вам нужно сделать, это поместить код GenerateReport()
в конструктор исключений (на самом деле этот GenerateReport()
был взят из моего кода конструктора пользовательских классов исключений (некоторая его часть)).