首页 > 其他分享 >使用dbghelp获取调用堆栈--release下的调试方法学

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

时间:2023-06-11 12:31:24浏览次数:42  
标签:函数 -- 寄存器 dbghelp stack 获取 printf release sf


当软件作为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.codeproject.com/KB/debug/XCrashReportPt1.aspx
http://www.codeproject.com/KB/applications/visualleakdetector.aspx

标签:函数,--,寄存器,dbghelp,stack,获取,printf,release,sf
From: https://blog.51cto.com/u_130277/6457452

相关文章

  • Spring Boot实现高质量的CRUD-1
    1、前言​ 在SpringBoot的SMM框架(SpringBoot+Mysql+Mybatis)的WEB项目中,CRUD(增删改查)大致占了50%-70%左右的工作量。提高CRUD的代码质量,提高CRUD的开发效率,是一件值得探讨的事项。​ 一般认为,CRUD是一件体力活。在SMM框架项目开发中,项目团队通常将这类CRUD的开发任务交由知道如......
  • 后渗透之权限维持
    内存马(不死马)最常见的内存马<?phpset_time_limit(0); #用来设置脚本最大的执行时间,如果为0的话,就会一直执行这个脚本,设置其他数字的话,则按照设置的秒数来进行执行,注意,执行时间和php.ini里的max_execution_time设置有很大的关系,真实时间的计算公式是max_execution_time+time......
  • vue后台管理系统实现全屏展示
    效果图展示直接上代码<!--全屏显示--><divclass="btn-fullscreen"@click="handleFullScreen"><el-tooltipeffect="dark":content="fullscreen?`取消全屏`:`全屏`"placement="bottom">......
  • 【Linux中断】中断下半部-软中断softirq的原理与使用
    软中断软中断是中断下半部的典型处理机制,是随着SMP的出现应运而生的,也是tasklet实现的基础,软中断的出现是为了满足中断上半部和下半部的区别,使得对时间不敏感的任务延后执行,而且可以在多个CPU上并行执行,使得总的系统效率可以更高。软中断有以下特性:产生后并不是马上可以执行,必......
  • CMU15445 (Fall 2020) 数据库系统 Project#2 - B+ Tree 详解(上篇)
    前言考虑到B+树较为复杂,CMU15-445将B+树实验拆成了两部分,这篇博客将介绍Checkpoint#1部分的实现过程,搭配教材《DataBaseSystemConcepts》食用更佳。B+树索引许多查询只涉及文件中的少量记录,例如“找出物理系所有教师”的查询就只涉及教师记录中的一小部分,如果数据库......
  • 比较新冠病毒与蝙蝠和穿山甲冠状病毒基因组/蛋白序列比对,更好地了解COVID-19病原体SAR
    比较新冠病毒与蝙蝠和穿山甲冠状病毒基因组/蛋白序列比对,更好地了解COVID-19病原体SARS-CoV-2的起源与进化作者:王怡然 石育 赵月馨 沈讯摘要:在新冠病毒COVID-19大流行的时代背景下,病毒的遗传信息对于其分类和可追溯性及其致病性至关重要。在全基因组水平上,SARS-CoV-2的序......
  • 611随笔QAQ
    1.古诗词里的中国十大名花  何须浅碧深红色,自是花中第一流。---------------------------桂花  只道花无十日红,此花无日不春风。----------------------------月季  遥知不是雪,为有暗香来。----------------------------------------梅花  孤兰生幽园,众草共无......
  • 更新完代理软件设置后无法访问网络
    1确定自己的本地的socks5等代理监听端口是否被更新修改 2如果没有修改确定自己的本地实际代理端口跟设置的代理端口是否一致  3如果还不行,重启下电脑,确定不用代理能访问的情况下,再打开代理,确认端口一致......
  • 小灰灰深度学习day8——线性回归(从零开始实现与简洁的实现)
    从零开始实现的代码如下:importmathimportrandom#随机梯度下降随机的权重importtimeimportnumpyasnpfromd2limporttorchasd2l#实现过的函数写在d2l包中'''加这两句是为了能画出散点图,不然会报错importosos.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"'......
  • 计组复习笔记
    老师说的整体要注意的...做题看清选对错课程知识点多,难点不一定在后边,可能开头就挺难。考试的时候经常说废话:比如主存的扩展,既告诉地址,还告诉总线的位数,总线的位数就是废话,实际的地址没那么多,要分清用不到的信息不知道的知识点跳过去。对缩写要知道,不要求每个字都对,要知道是......