- android - RelativeLayout 背景可绘制重叠内容
- android - 如何链接 cpufeatures lib 以获取 native android 库?
- java - OnItemClickListener 不起作用,但 OnLongItemClickListener 在自定义 ListView 中起作用
- java - Android 文件转字符串
我正在研究一个类,我想用它来记录装有 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 文件中获取函数名称。
获取 CONTEXT
结构。您对节目柜台成员(member)感兴趣。由于 CONTEXT
是平台相关的,您必须自己弄清楚。您在初始化时就已经这样做了,例如用于 x64 Windows 的 STACKFRAME64.AddrPC.Offset = CONTEXT.Rip
。现在我们开始堆栈遍历并使用由 StaclkWalk64
填充的 STACKFRAME64.AddrPC.Offset
作为起点。
您需要使用分配基地址将其转换为相对虚拟地址 (RVA):RVA = STACKFRAME64.AddrPC.Offset - AllocationBase
。您可以使用 VirtualQuery
获取 AllocationBase
。
完成后,您需要找到此 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/
出于好奇 - 我知道有 LAMP - Linux、Apache、MySQL 和 PHP。但是还有哪些其他 Web 堆栈替代方案的缩写呢?像 LAMR - Linux、Apache、MySQL Ruby
我有以下代码。 var stackMapIn = []; var stackMapOut = []; var stackBack = []; stackMapOut.push("m1"); $scop
我遇到了导致我的堆栈无法恢复的情况,我别无选择,只能将其删除。使用完全相同的模板,我继续创建了另一个同名的堆栈。 The following resource(s) failed to create:
这是我第一次查看 Node 堆栈,自从我学习使用 Ruby on Rails 进行 Web 开发以来,我对一些基本的东西有点困惑。我了解 Rails 目录是什么样的。 demo/ ..../app .
本文实例讲述了C语言使用深度优先搜索算法解决迷宫问题。分享给大家供大家参考,具体如下: 深度优先搜索 伪代码 (Pseudocode)如下: ?
我正在按照指南 here ,它告诉我: The stack setup will download the compiler if necessary in an isolatedlocation (
同时 trying to debug a different question ,我安装了一个似乎与我安装的其他一些软件包冲突的软件包。 我跑了 $ stack install regex-pcre-
我花了几个小时创建了一个方法,该方法将从堆栈 s1 中获取 null 元素,并将它们放入 s2 中。然后该类应该打印堆栈。方法如下 import net.datastructures.ArraySta
我有一个类Floor,它有一个Stack block ,但我不知道如何初始化它。我曾尝试过这样的: public class Floor { private Stack stack;
我知道这个问题已经问过很多次了,但搜索一个小时后我仍然遇到问题。 我想使用一个 lifo 堆栈,它可以存储最大数量的元素。达到最大数量后,首先删除该元素并将其替换为新元素,这样在第一次弹出时我可以获取
我需要编写一个方法,压缩以执行以下操作; 目标compress方法是从栈s1中移除所有null元素。剩余(非空)元素应按其初始顺序保留在 s1 上。辅助堆栈 s2 应用作s1 中元素的临时存储。在该方
我正在尝试验证以下代码发生的顺序。 function square(n) { return n * n; } setTimeout(function(){ console.log("H
我需要一个字符数组,其中包含基于特定文件夹中文件数量的动态数量的字符数组。我能够通过初始化 char (*FullPathNames)[MAX_FILENAME_AND_PATHNAME_LENGTH
我正在编写一些日志逻辑并想要进行一些缩进。了解是否存在任何函数调用或某个函数是否已完成的最简单方法是查看堆栈/帧的当前地址。让我们假设堆栈颠倒增长。然后,如果 log() 调用中的堆栈地址小于前一次调
所以内存分段在x86-64中被放弃了,但是当我们使用汇编时,我们可以在代码中指定.code和.data段/段,并且还有堆栈指针寄存器。 还有堆栈段、数据段和代码段寄存器。 代码/数据/堆栈的划分是如何
void main() { int x = 5; // stack-allocated Console.WriteLine(x); } 我知道 x 是堆栈分配的。但是关于 x 的堆栈中
这是我关于 SO 的第一个问题。这可能是一个愚蠢的问题,但到目前为止我还没弄明白。 考虑下面的程序 Reader.java: public class Reader { public
java中有没有一种快速的方法来获取嵌套/递归级别? 我正在编写一个函数来创建组及其成员的列表。成员也可以是团体。我们最终可能会得到一组循环的组/成员。 我想在某个任意级别停止。 我知道我可以将变量保
考虑以下代码: struct A{...}; A a[100]; A* pa = new A[100]; delete[] pa; a/pa 元素的销毁顺序是由标准定义的还是实现定义的(对于第二种情况
我在下面有一些代码。此代码是一个基本的压入/弹出堆栈类,我将其创建为模板以允许某人压入/弹出堆栈。我有一个家庭作业,我现在要做的是创建一个具有多个值的堆栈。 所以我希望能够创建一个基本上可以发送三个整
我是一名优秀的程序员,十分优秀!