使用信息表格CONTEXT
您可以在PE圖像中找到功能部分和偏移量。例如,您可以使用此信息從鏈接器生成的.map文件獲取函數名稱。
獲取CONTEXT
結構。您對程序櫃檯成員感興趣。由於CONTEXT
是平臺相關的,所以你必須自己弄明白。你在初始化的時候就已經做好了,例如對於x64 Windows,STACKFRAME64.AddrPC.Offset = CONTEXT.Rip
。現在我們開始棧走,並使用STACKFRAME64.AddrPC.Offset
,填寫StaclkWalk64
作爲我們的出發點。
您需要使用分配基地址RVA = STACKFRAME64.AddrPC.Offset - AllocationBase
將其轉換爲相對虛擬地址(RVA)。您可以使用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的最高偏移量。
如果您願意,我可以稍後發佈代碼。
編輯:代碼打印function address
(我們假設64位通用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 [email protected]@YAXXZ 0000000140003f40 f FMain.obj
0001:000031e0 [email protected]@YAXXZ 00000001400041e0 f FMain.obj
0001:00003220 main 0000000140004220 f FMain.obj
...
其中00002f40
最接近較小的偏移00002F8D
等。最後三個地址指的是調用main
(_tmainCRTstartup
等),CRT/OS的功能 - 我們應該忽略他們...
所以,我們可以看到,我們能夠恢復堆棧跟蹤與.map
文件的幫助。爲了生成拋出異常的堆棧跟蹤,你所要做的就是將GenerateReport()
代碼放入異常構造函數(實際上,這個GenerateReport()
取自我自定義的異常類構造函數代碼(它的某些部分))。
查看我的實施:http://www.dima.to/blog/?p=13 – Alexandru 2014-06-02 13:34:48
根據您在博客中的意見,您的實施需要pdb-s。 – 2016-02-23 16:49:27