gpt4 book ai didi

C++ 堆栈跟踪问题

转载 作者:可可西里 更新时间:2023-11-01 13:19:12 25 4
gpt4 key购买 nike

我正在研究一个类,我想用它来记录装有 Windows Vista/7 的计算机上的当前调用堆栈。 (与“遍历调用堆栈”http://www.codeproject.com/Articles/11132/Walking-the-callstack 非常相似)。

首先,我使用 RtlCaptureContext 获取当前上下文记录,然后使用 StackWalk64 获取各个堆栈帧。现在,我意识到每当我关闭我的程序并再次启动它时,STACKFRAME64.AddrPC 中的程序计数器实际上会针对特定代码行发生变化。出于某种原因,我认为只要不更改源代码并再次重新编译,特定代码行的 PC-Address 就会保持不变。

我需要 PC-Address 来使用 SymFromAddr 和 SymGetLineFromAddr64 来获取有关被调用函数、代码文件和行号的信息。不幸的是,只有在 Program-Debug-Database (PDB-File) 存在时它才有效,但我不允许将其提供给客户。

我的计划是记录调用堆栈的 PC 地址(无论何时需要),然后将其从客户端发送给我。这样我就可以使用我的 PDB 文件找出调用了哪些函数,但这当然只有在 PC 地址是唯一标识符时才有效。因为每次我启动程序时它们都会改变,所以我不能使用这种方法。

您知道读取调用堆栈或克服程序计数器变化问题的更好方法吗?

我认为一种可能的解决方案是始终获取已知位置的 PC-Address 并将其用作引用来确定不同 PC-Address 之间的偏移量。这似乎可行,但我不确定这是否是一种有效的方法并且是否会始终有效。

非常感谢您的帮助!我将在 codeproject.com 中发布最终(封装的)解决方案,如果您愿意,我会说您帮助了我。

最佳答案

使用 CONTEXT 信息形式,您可以在 PE 图像中找到功能部分和偏移量。例如,您可以使用此信息从链接器生成的 .map 文件中获取函数名称。

  1. 获取 CONTEXT 结构。您对节目柜台成员(member)感兴趣。由于 CONTEXT 是平台相关的,您必须自己弄清楚。您在初始化时就已经这样做了,例如用于 x64 Windows 的 STACKFRAME64.AddrPC.Offset = CONTEXT.Rip。现在我们开始堆栈遍历并使用由 StaclkWalk64 填充的 STACKFRAME64.AddrPC.Offset 作为起点。

  2. 您需要使用分配基地址将其转换为相对虚拟地址 (RVA):RVA = STACKFRAME64.AddrPC.Offset - AllocationBase。您可以使用 VirtualQuery 获取 AllocationBase

  3. 完成后,您需要找到此 RVA 属于哪个部分,并从中减去部分起始地址以获得 SectionOffset:SectionOffset = RVA - SectionBase = STACKFRAME64.AddrPC.Offset - AllocationBase - SectionBase 。为此,您需要访问 PE 图像头结构(IMAGE_DOS_HEADER、IMAGE_NT_HEADER、IMAGE_SECTION_HEADER)以获取 PE 中的节数及其开始/结束地址。这非常简单。

就是这样。现在您在 PE 图像中有了节号和偏移量。 Function offset是.map文件中小于SectionOffset的最高偏移量。

如果你愿意,我可以稍后发布代码。

编辑:打印 函数地址 的代码(我们假设 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() 取 self 的自定义异常类构造函数代码(它的一部分)。

关于C++ 堆栈跟踪问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9424568/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com