Author : Kevin Lynx

当软件作为release模式被发布给用户时,当程序崩溃时我们很难去查找原因。常见的手法是输出LOG文件,根据LOG文件分析
程序崩溃时的运行情况。我们可以通过SEH来捕获程序错误,然后输出一些有用的信息作为我们分析错误的资料。一般我们需要
输出的信息包括:系统信息、CPU寄存器信息、堆栈信息、调用堆栈等。而调用堆栈则是最有用的部分,它可以直接帮我们定位
到程序崩溃时所处的位置(在何处崩溃)。(codeproject上关于这个专题的常见开场白 = =#)

要获取call stack(所谓的调用堆栈),就需要查看(unwind)stack的内容。We could conceivably attempt to unwind the
stack ourselves using inline assembly. But stack frames can be organized in different ways, depending on compiler
optimizations and calling conventions, so it could become complicated to do it that way.(摘自vld文档)要获取栈的
内容,我们可以自己使用内联汇编获取,但是考虑到兼容性,内联汇编并不是一个好的解决方案。我们可以使用微软的dbghelp
中的StackWalk64来获取栈的内容。

StackWalk64声明如下:
BOOL StackWalk64(
  DWORD MachineType,
  HANDLE hProcess,
  HANDLE hThread,
  LPSTACKFRAME64 StackFrame,
  PVOID ContextRecord,
  PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine,
  PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine,
  PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine,
  PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress
);

具体每个参数的含义可以参见MSDN。这里说下ContextRecord参数,该参数指定了CPU各个寄存器的内容。StackFrame指定了stack
frame的内容。stack frame是什么,我也不知道。(= =) StackWalk64函数需要用户指定当前frame的地址,以及当前程序的指令
地址。这两个信息都被填充进ContextRecord,然后传进StackWalk64函数。

那么如何获取当前的stack frame地址和当前程序指令地址呢?如前所说,你可以使用内联汇编。(对于程序指令地址,因为要获取
EIP寄存器的内容,而该寄存器不能被软件访问)也可以使用GetThreadContext一次性获取当前线程当前运行情况下的CPU各个寄存器
内容。补充下,当前frame地址被放在EBP寄存器里,当前程序指令地址放在EIP寄存器里。但是,如同MSDN对GetThreadContext函数
的说明一样,该函数可能获取到错误的寄存器内容(You cannot get a valid context for a running thread)。

另一种获取Context(包含EBP and EIP)的方法就是使用SEH(结构化异常处理),在__except中使用GetExceptionInformation获取。

GetExceptionInformation 传回一个LPEXCEPTION_POINTERS指针,该指针指向一个EXCEPTION_POINTERS结构,该结构里包含一个
Context的指针,即达到目标,可以使用StackWalk函数。

补充一下,你可以直接使用StackWalk函数,StackWalk被define为StackWalk64(windows平台相关)。

unwind栈后,可以进一步获取一个stack frame的内容,例如函数名。这里涉及到SymFromAddr函数,该函数可以根据一个地址返回
符号名(函数名)。还有一个有意思的函数:SymGetLineFromAddr,可以获取函数对应的源代码的文件名和行号。

当然,这一切都依赖于VC产生的程序数据库文件(pdb),以及提供以上API函数的dbghelp.dll。

参考一段简单的代码:

/**
///
///
#include <windows.h>
#include <stdio.h>
#include <dbghelp.h>

#pragma comment( lib, "dbghelp.lib" )

void dump_callstack( CONTEXT *context )
{
 STACKFRAME sf;
 memset( &sf, 0, sizeof( STACKFRAME ) );

sf.AddrPC.Offset = context->Eip;
 sf.AddrPC.Mode = AddrModeFlat;
 sf.AddrStack.Offset = context->Esp;
 sf.AddrStack.Mode = AddrModeFlat;
 sf.AddrFrame.Offset = context->Ebp;
 sf.AddrFrame.Mode = AddrModeFlat;

DWORD machineType = IMAGE_FILE_MACHINE_I386;

HANDLE hProcess = GetCurrentProcess();
 HANDLE hThread = GetCurrentThread();

for( ; ; )
 {
  if( !StackWalk(machineType, hProcess, hThread, &sf, context, 0, SymFunctionTableAccess, SymGetModuleBase, 0 ) )
  {
   break;
  }

if( sf.AddrFrame.Offset == 0 )
  {
   break;
  }
  BYTE symbolBuffer[ sizeof( SYMBOL_INFO ) + 1024 ];
  PSYMBOL_INFO pSymbol = ( PSYMBOL_INFO ) symbolBuffer;
 
  pSymbol->SizeOfStruct = sizeof( symbolBuffer );
  pSymbol->MaxNameLen = 1024;

DWORD64 symDisplacement = 0;
  if( SymFromAddr( hProcess, sf.AddrPC.Offset, 0, pSymbol ) )
  {
   printf( "Function : %s\n", pSymbol->Name );
  }
  else
  {
   printf( "SymFromAdd failed!\n" );
  }

IMAGEHLP_LINE lineInfo = { sizeof(IMAGEHLP_LINE) };
  DWORD dwLineDisplacement;

if( SymGetLineFromAddr( hProcess, sf.AddrPC.Offset, &dwLineDisplacement, &lineInfo ) )
  {
   printf( "[Source File : %s]\n", lineInfo.FileName );
   printf( "[Source Line : %u]\n", lineInfo.LineNumber );
  }
  else
  {
   printf( "SymGetLineFromAddr failed!\n" );
  }
 }
}

DWORD excep_filter( LPEXCEPTION_POINTERS lpEP )
{
 /** init dbghelp.dll
 if( SymInitialize( GetCurrentProcess(), NULL, TRUE ) )
 {
  printf( "Init dbghelp ok.\n" );
 }

dump_callstack( lpEP->ContextRecord );

if( SymCleanup( GetCurrentProcess() ) )
 {
  printf( "Cleanup dbghelp ok.\n" );
 }

return EXCEPTION_EXECUTE_HANDLER;
}

void func1( int i )
{
 int *p = 0;
 *p = i;
}

void func2( int i )
{
 func1( i - 1 );
}

void func3( int i )
{
 func2( i - 1 );
}

void test( int i )
{
 func3( i - 1 );
}

int main()
{
 __try
 {
  test( 10 );
 }
 __except( excep_filter( GetExceptionInformation() ) )
 {
  printf( "Some exception occures.\n" );
 }

return 0;
}

以上代码在release模式下需要关掉优化,否则调用堆栈显示不正确(某些函数被去掉了?),同时需要pdb文件。

参考资料:
http://www.codeproject.com/KB/threads/StackWalker.aspx
http://www.cnblogs.com/protalfox/articles/84723.html
http://www.codeproject.com/KB/debug/XCrashReportPt1.aspx
http://www.codeproject.com/KB/applications/visualleakdetector.aspx

ps,本文技术浅尝辄止,部分内容是否完全准确(正确)我个人都持保留态度,仅供参考。:

转载于:https://www.cnblogs.com/jxnclyk/archive/2010/09/25/1834340.html

使用dbghelp获取调用堆栈--release下的调试方法学相关推荐

  1. C/C++中手动获取调用堆栈【转】

    转自:http://blog.csdn.net/kevinlynx/article/details/39269507 版权声明:本文为博主原创文章,未经博主允许不得转载. 当我们的程序core掉之后, ...

  2. vs如何设置在Release下进行调试

    使用VS进行项目开发时,在debug环境下调试程序是没有问题,但是当release发布版时,会出现问题,很难排查,除了打log不能使用断点的方式进行调试,很难定位到问题所在,现在将vs环境设置rele ...

  3. 获取iOS任意线程调用堆栈(五)完整实现:BSBacktraceLogger

    转载自:https://toutiao.io/posts/aveig6/preview BSBacktraceLogger 是一个轻量级的框架,可以获取任意线程的调用栈,开源在我的 GitHub,建议 ...

  4. C#获取当前堆栈的各调用方法列表

    在使用.NET编写的代码在debug时很容易进行排查和定位问题,一旦项目上线并出现问题的话那么只能依靠系统日志来进行问题排查和定位,但当项目复杂时,即各种方法间相互调用将导致要获取具体的出错方法或调用 ...

  5. LibVLC+Qt调用libvlc_new在Debug下正确运行Release下崩溃(VS2008)

    在使用LibVLC+Qt包装食品播放器时遇到一个匪夷所思的问题,Debug下运行的很好,Release下初始化VLC便崩溃. 终于在Stack Overflow找到了问题的解决方案. 问题内容摘录如下 ...

  6. 高并发编程-捕获线程运行时的异常 + 获取调用链

    文章目录 概述 捕获线程运行时的异常 使用场景 UncaughtExceptionHandler 接口 示例 获取调用链 使用线程池的场景: 获取线程运行时异常 概述 捕获线程运行时的异常 我们看下T ...

  7. 谈谈iOS获取调用链

    本文由云+社区发表 iOS开发过程中难免会遇到卡顿等性能问题或者死锁之类的问题,此时如果有调用堆栈将对解决问题很有帮助.那么在应用中如何来实时获取函数的调用堆栈呢?本文参考了网上的一些博文,讲述了使用 ...

  8. 怎样重建一个损坏的调用堆栈(callstack)

    原文作者:Aaron Ballman 原文时间:2011年07月04日 原文地址:http://blog.aaronballman.com/2011/07/reconstructing-a-corru ...

  9. 生产服务器怎么dmp堆栈信息,如何根据程序崩溃时的DMP文件使用WinDbg查找调用堆栈...

    在 Windbg.exe,打开进程的.dmp 文件. 请确保您符号路径指向正确的位置. 有关如何执行此操作,请访问下面的 Microsoft Web 站点: 如何获得符号 http://www.mic ...

最新文章

  1. #Sora#OpenStack基础库oslo.config试用总结
  2. rsyslog日志管理+LogAnalyzer
  3. Mybatis源码解析之Mybatis初始化过程
  4. .NET 跨平台服务端资料
  5. numpy数组与矩阵运算
  6. SQL Server 2012安装错误案例:Error while enabling Windows feature: NetFx3, Error Code: -2146498298...
  7. python 连接hive_python pyhive通过SSH隧道连接hive报错,求解
  8. Python实战教程 | 轻松批量识别数百个快递单号
  9. Redis 集群模式(Cluster)原理详解
  10. 【硬件和驱动】如何查看linux的驱动有没有装好 ——声卡无声音,有驱动为例子,网卡wifi驱动相关
  11. 步步惊心插曲 - 歌词
  12. 如何在 React 中优雅的写 CSS?CSS作用域隔离
  13. 重装系统后计算机无法联网,韩博士重装系统后电脑无法上网怎么办?
  14. ARM架构Generic Interrupt Controller(GIC)之Distributor和CPU interface功能介绍
  15. 动态模态分解 DMD | 做高维时间序列数据短时预测
  16. autocad中的diesel语言详解
  17. 使用VLC(ActiveX)在网页中播放MP4
  18. CSDN博文字体设置 | 字体颜色、大小设置 | 简记 |
  19. 计算机在小学英语中的应用,计算机游戏在小学英语教学中的应用
  20. 调音台docker教程_Docker Hello World

热门文章

  1. ERROR: ld.so: object 'libgtk3-nocsd.so.0' from LD_PRELOAD cannot be preloaded
  2. colab加载google drive并且调试时跳过不运行
  3. Adaboost算法原理分析和实例+代码(转载)
  4. pycharm中的console退出问题以及console和pycharm各自运行结果不一样的问题
  5. dataframe数据标准化处理_数据预处理——标准化/归一化(实例)
  6. javacore分析工具_Javacore 与 WebSphere Commerce 性能问题之争?谁才是赢家
  7. python变量和运算符
  8. PLSQL Developer 运用Profiler 分析存储过程性能
  9. 迪普科技高端防火墙规模应用于海关总署 提供整网安全防护
  10. 写给找工作的朋友——最典的面试葵花宝典