何时使用Windbg静态分析?何时使用Windbg动态调试?

目录

1、概述

2、使用Windbg静态分析dump文件

2.1、异常捕获模块自动生成dump文件

2.2、从Windows任务管理器中导出dump文件

2.3、从正在动态调试的Windbg中使用命令导出dump文件

2.4、使用Windbg静态分析dump文件的一般步骤

3、使用Windbg动态调试目标进程

3.1、程序发生异常,但异常捕获模块没有捕获到

3.2、异常捕获模块感知到了异常,但导出dump文件时产生了二次崩溃

3.3、程序并没有发生C++异常,只是发生卡死或死循环,异常捕获模块感知不到

3.4、程序运行过程中检测到不正常,调用abort函数强制结束进程,导致程序闪退

3.5、Visual Studio调试程序时产生异常,但看不到有效的函数调用堆栈,可以尝试使用Windbg进行动态调试

3.6、程序启动崩溃或失败时

3.7、程序弹出报错提示框时

3.8、使用Windbg动态调试目标进程的一般步骤

4、其他场景说明

5、在实际排查问题时,可能要尝试多种排查方法

6、最后


VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N6B9https://blog.csdn.net/chenlycly/article/details/124272585C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N6B9https://blog.csdn.net/chenlycly/article/details/125529931C++软件分析工具案例集锦(专栏文章正在更新中...)icon-default.png?t=N6B9https://blog.csdn.net/chenlycly/article/details/131405795C/C++基础与进阶(专栏文章,持续更新中...)icon-default.png?t=N6B9https://blog.csdn.net/chenlycly/category_11931267.html       使用Windbg分析C++软件问题时,可以静态分析,也可以动态调试。经常有朋友问,到底什么时候使用Windbg静态分析?什么时候Windbg进行动态调试?下面根据近几年来排查C++软件异常遇到的问题场景和项目实例,给大家做个详细的总结。

1、概述

       一般情况下,有dump文件时,使用Windbg静态分析dump文件即可;没有dump文件时,可能就需要使用Windbg去动态调试目标进程了。如果dump文件中的信息不足以分析出问题时,也可以尝试使用Windbg去动态调试。

       此外,如果使用Visual Studio调试代码时遇到异常,但在Visual Studio中分析不出来问题,比如刚启动调试时就产生异常或者报错,可以使用Windbg动态调试进行分析。 

2、使用Windbg静态分析dump文件

      Windbg静态分析的对象就是dump文件,必须要有dump文件。一般生成dump文件的方式主要有三种,分别是异常捕获模块生成、从Windows任务管理器中导出、从正在调试的Windbg中使用命令导出。

2.1、异常捕获模块自动生成dump文件

        为了让软件能自动捕获运行时的异常,一般会在软件中安装异常捕获模块。当软件发生异常时,异常捕获模块感知到异常,自动将包含异常上下文信息导出到dump文件中。有的软件甚至会弹出崩溃反馈提示框,比如如下所示的企业微信的提示框:

提示用户上传log日志和dump文件。为什么不自动上传呢?因为要上传用户机器上的文件,可能会涉及到用户隐私和用户信息安全,不能随意上传,必须经过用户授权后上传或者让用户上传。

       异常捕获库可以选用Google开源库CrashRpt,或者使用Chrome开源代码中的Breakpad或者Crashpad,对于QT程序则可以选用qBreakpad。程序发生异常时,异常捕获模块感知到,会将异常上下文信息导出到dump文件中。事后可以取来dump文件,使用Windbg进行静态分析了。

       异常捕获模块一般是调用系统API函数miniDumpWriteDump去生成dump文件,可以控制传给该API函数的参数去控制生成的dump文件的大小。此时一般生成的是比较小的mini dump文件,因为dump文件是保存在用户电脑上,不能生成全dump文件,因为全dump文件比较大(包含了所有内存信息,文件大小可能接近进程的总虚拟内存),会占用用户大量的磁盘空间。此外,dump文件可能要上传服务器,文件过大会给上传带来较大的网络负担,所以dump文件也不能太大。一般分析mini dump文件就能定位问题了。

2.2、从Windows任务管理器中导出dump文件

       如果程序中的线程出现卡死(可能是死循环或死锁引发的)或者程序运行弹出报错提示框,这些情况下程序的进程还在,可以直接到Windows任务管理器中手动导出dump文件。事后取来dump文件进行分析。

        从Windows任务管理器中导出dump文件的操作步骤是这样的。打开Windows任务管理器,找到出问题的目标进程,右键点击该进程,在弹出的右键菜单中点击“创建转储文件”菜单项:

即可导出包含进程上下文信息的到dump文件中。

       其实程序出现卡死或者运行报错时,还为时不晚的,可以直接将Windbg附加到进程上进行分析的。但有些客户比较敏感(比如安全级别比较高),不允许远程到机器上将Windbg附加上去,甚至都连Windbg都不让安装,这是就可以让客户手动从Windows任务管理器的进程列表中导出。这种方式导出的dump文件是全dump文件,文件会比较大,包含了进程的所有内存信息。

2.3、从正在动态调试的Windbg中使用命令导出dump文件

       可能正在使用Windbg调试目标进程,但一时半会又分析不出问题,不能长时间占用出问题的电脑,电脑主人还需要用。我们可以使用Windbg命令.dump导出当前正在调试的进程的dump文件,具体的命令样式可以这样:

.dump /ma D:\0606.dmp

事后取来dump文件,然后使用Windbg静态分析这个dump文件,继续分析问题。一般此时导出的也是全dump文件。

2.4、使用Windbg静态分析dump文件的一般步骤

       取来dump文件,使用Windbg打开,首先输入.excr命令切换到发生异常的上下文,然后使用kn命令查看发生异常时的函数调用堆栈,然后将函数调用堆栈与源码对应起来分析。一般此时需要根据函数调用堆栈中的模块,去找这些模块的pdb文件,然后再去查看函数调用堆栈,有了pdb文件中的函数及变量符号后,函数调用堆栈中可以看到具体的函数名称和代码行号:

甚至可以直接在Windbg中查看函数中局部变量的值或者函数所在类对象的成员变量的值。

       关于Windbg静态分析dump文件的详细步骤及相关要点,可以参见我之前写的文章:

使用Windbg静态分析dump文件的一般步骤及要点详解https://blog.csdn.net/chenlycly/article/details/130873143

3、使用Windbg动态调试目标进程

       如果没有生成dump文件,则没法使用Windbg进行静态分析,这时就需要使用Windbg进行动态调试了,即将Windbg动态附加到目标进程上进行调试。动态调试有两种方式:

1)直接使用Windbg启动目标程序;
2)先将目标程序启动起来,然后将Windbg附加到目标进程上。

当目标程序发生异常,Windbg就会感知到,会自动中断下来,这样使用Windbg命令就能查看当前发生的异常类型并查看此时的函数调用堆栈了。

       没有生成dump文件,有几种场景,这些场景需要使用Windbg的动态调试了,下面来详细介绍一下。

3.1、程序发生异常,但异常捕获模块没有捕获到

       程序中安装的异常捕获模块,并不能感知并捕获所有场景下的异常,即还是有少部分异常是捕获不到的。捕获不到的,自然就不会生成dump文件了。我们在项目中也时常会遇到,程序在使用过程中发生闪退或崩溃,异常捕获模块没捕获到,没有生成dump文件。一般遇到这类情况,都会让测试人员将Windbg附加到目标进程上,和目标进程一起跑,一旦目标进程运行过程中发生异常,Windbg会第一时间感知到并中断下来,此时就可以查看发生的异常类型和函数调用堆栈了。

3.2、异常捕获模块感知到了异常,但导出dump文件时产生了二次崩溃

       程序发生了异常,异常捕获模块感知到了,但在导出dump文件的过程中产生了二次崩溃,可能没生成dump文件,或者生成的dump文件大小为0,这种情况下也需要使用Windbg进行动态调试的。这个场景我们在项目中也遇到过多次。

3.3、程序并没有发生C++异常,只是发生卡死或死循环,异常捕获模块感知不到

       程序并没有发生C++异常,只是业务逻辑上产生异常(比如不能正常处理业务了),比如发生多线程死锁或死循环。因为并没有产生C++异常,异常捕获模块是感知不到的,所以也就无法生成dump文件。这个也需要将Windbg附加到进程上进行动态调试,进行详细分析的。

3.4、程序运行过程中检测到不正常,调用abort函数强制结束进程,导致程序闪退

       如果程序在运行过程中监测到不正常的情况,直接调用abort函数强制将进程终止了,这样给人一种程序发生崩溃闪退的感觉。这种情况也是没有发生C++异常,异常捕获模块也是感知不到的,所以也不会产生dump文件。比如在开源jsoncpp库中,如果解析的json节点的类型不匹配,会被jsoncpp内部检测到,会触发abort函数的调用,相关代码截图如下:

        再比如WebRTC开源库中在监测到malloc动态申请内存失败后,也会触发abort函数的调用,强行终止进程。估计是因为动态申请内存失败了,相关业务无法正常展开了,程序活着也没意思了,所以就强行终止进程了。相关代码截图如下:

       上述两类问题,我们在实际项目中都遇到过。遇到这类情况,也可以将Widnbg附加到目标进程上进行动态调试,尝试着去复现问题。如果程序调用abort函数,就会触发Windbg中断下来,然后查看函数调用堆栈,通过函数调用堆栈就知道是什么函数调用触发的abort函数的调用的。那为什么调用abort函数会让正在调试的调试器Windbg中断下来呢?因为abort函数内部会raise(产生)一个SIGABRT信号终止异常,如果当前正在调试状态,会让调试器中断下来。abort函数的内部实现源码如下所示:

/***
*void abort() - abort the current program by raising SIGABRT
*
*Purpose:
*   print out an abort message and raise the SIGABRT signal.  If the user
*   hasn't defined an abort handler routine, terminate the program
*   with exit status of 3 without cleaning up.
*
*   Multi-thread version does not raise SIGABRT -- this isn't supported
*   under multi-thread.
*******************************************************************************/
void __cdecl abort (void)
{_PHNDLR sigabrt_act = SIG_DFL;#ifdef _DEBUGif (__abort_behavior & _WRITE_ABORT_MSG){/* write the abort message */_NMSG_WRITE(_RT_ABORT);}
#endif  /* _DEBUG *//* Check if the user installed a handler for SIGABRT.* We need to read the user handler atomically in the case* another thread is aborting while we change the signal* handler.*/sigabrt_act = __get_sigabrt();if (sigabrt_act != SIG_DFL){raise(SIGABRT);}/* If there is no user handler for SIGABRT or if the user* handler returns, then exit from the program anyway*/if (__abort_behavior & _CALL_REPORTFAULT){_call_reportfault(_CRT_DEBUGGER_ABORT, STATUS_FATAL_APP_EXIT, EXCEPTION_NONCONTINUABLE);}/* If we don't want to call ReportFault, then we call _exit(3), which is the* same as invoking the default handler for SIGABRT*/_exit(3);
}

        关于调用abort函数强行终止进程场景的详细说明,可以参见我之前写的文章:

C++程序中执行abort等操作导致没有生成dump文件的问题案例分析https://blog.csdn.net/chenlycly/article/details/129003869

3.5、Visual Studio调试程序时产生异常,但看不到有效的函数调用堆栈,可以尝试使用Windbg进行动态调试

       如果使用Visual Studio调试代码的过程中产生了异常,但在Visual Studio中看不到完整的或者有效的函数调用堆栈,分析不出来问题,比如刚启动调试时就产生异常或者报错,可以尝试着使用Windbg动态调试进行分析。

3.6、程序启动崩溃或失败时

       当程序启动崩溃闪退,会因为某些原因启动失败时,可以尝试使用Windbg启动程序进行动态调试。可能Debug下不好复现,也可能即使复现用Visual Studio调试也很难排查。有可能问题只在客户机器上才会出现,没法到客户机器上使用Visual Studio调试代码。这些场景下的问题,使用Windbg去动态调试分析很方便。

3.7、程序弹出报错提示框时

       程序在运行过程中出现异常,弹出报错提示框,此时进程还在,此刻将Windbg附加到进程上还来的及,可以直接分析。可能问题很难复现,错过了就很难复现了,所以要及时地将Windbg附加到进程上分析。即使一时半会分析不出原因,也可以将进程上下文导出到dump文件中,事后再去分析。

3.8、使用Windbg动态调试目标进程的一般步骤

       将Windbg附加到目标进程上,一旦目标进程产生异常,Windbg就会感知到并中断下来,此时使用kn命令查看发生异常时的函数调用堆栈,然后将函数调用堆栈与源码对应起来分析。一般此时需要根据函数调用堆栈中的模块,去找这些模块的pdb文件,然后再去查看函数调用堆栈,有了pdb文件中的函数及变量符号后,函数调用堆栈中可以看到具体的函数名称和代码行号,甚至可以直接在Windbg中查看函数中局部变量的值或者函数所在类对象的成员变量的值。

       关于Windbg动态调试目标进程的详细步骤及相关要点,可以参见我之前写的文章:

使用Windbg动态调试目标进程的一般步骤及要点详解https://blog.csdn.net/chenlycly/article/details/131029795

4、其他场景说明

       产品发布后,问题是出在Release版本下的。如果使用Windbg静态分析dump文件分析不出来问题时,可以尝试使用Windbg动态调试的。如果使用Windbg分析不出来问题,并且问题是好复现的,则可以尝试使用Visual Studio去调试代码,进行Release下调试。如果问题出在底层的dll模块,则可以使用Visual Studio附加到进程调试的方式去单独调试这个dll模块,附加到进程调试还是很方便的。

       此外,我们有时甚至需要在Visual Studio中查看并单步调试汇编代码。CPU中执行的是一句一句汇编代码(或者叫二进制机器码,两者是等价的),看汇编代码才能看到代码的执行细节。一行C++源码可能对应多行汇编代码,有时我们要知道C++源码为何有问题,可以尝试在Visual Studio中转到汇编代码页面,去单步调试汇编代码,通过调试汇编代码去寻找真相

5、在实际排查问题时,可能要尝试多种排查方法

       在排查问题的过程中,可能需要使用多种排查方法,一个方法排查不出来,就换另一种方法;或者将两个或两个以上的方法结合起来使用,直到定位问题为止!此外,除了使用调试器,还可以考虑同时使用其他的软件分析工具去辅助定位,比如Process Explorer或Process Monitor等。

        之前使用多种方法去排查一个线程栈溢出的问题,相关问题排查实例记录文章如下:

线程栈溢出异常,程序崩溃在汇编代码test dword ptr [eax],eax上的问题排查https://blog.csdn.net/chenlycly/article/details/131743305在上述问题中先后使用了多种排查方法,从Windbg静态分析dump文件,到Windbg动态调试目标进程,再到使用Visual Studio调试源码,最后去单行调试汇编代码。最终调试汇编代码,才找出问题的原因。

6、最后

       本文根据近几年来排查C++软件异常遇到的问题场景和实例,给大家做个详细的总结,希望能给大家提供一个详细的借鉴或参考。此外,分析问题的方法是多样的,不是一成不变的,要根据具体情况去灵活选用一种或多种方法去排查。需要通过大量的实践去积累经验的,经验多了,处理问题的思路就多了,处理问题就更加得心应手了!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/29158.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OpenCV的HSV颜色空间在无人车中颜色识别的应用

RGB属于三基色空间,是大家最为熟悉的,看到的任何一种颜色都可以由三基色进行混合而成。然而一般对颜色空间的图像进行有效处理都是在HSV空间进行的,HSV(色调Hue,饱和度Saturation,亮度Value)是根据颜色的直观特性创建的一种颜色空间, 也称六角…

【技巧】Maven重复依赖分析查找

【技巧】Maven重复依赖分析查找 遇到奇葩的错误可以考虑是不是依赖冲突了 比如同一段代码 再这个项目中好好的 另一个项目中不能用等 idea安装插件 maven helper 打开pom文件 输入要查找的依赖 将不用的排除掉 右键排除即可

Pandas Groupby:在Python中汇总、聚合和分组数据

GroupBy是一个非常简单的概念。我们可以创建一个类别分组,并对这些类别应用一个函数。这是一个简单的概念,但它是一种在数据科学中广泛使用的非常有价值的技术。在真实的的数据科学项目中,您将处理大量数据并一遍又一遍地尝试,因此…

旅游卡小程序软件招商加盟代理

旅游卡小程序软件招商加盟代理 我国人民生活水平的提高,旅游业成为了人们生活中必不可少的一部分。旅游卡小程序软件作为旅游行业的重要组成部分,也日益受到人们的关注。如今,旅游卡小程序软件招商加盟代理已经成为了一个热门的投资创业项…

⛳ Java数组

Java数组的目录 ⛳ Java数组🎨 一,一维数组👣 1.1,概念📢 1.2,基本用法1,语法格式2,代码 💻 1.3,内存结构📝 1.4,练习 🎁 …

芯片验证板卡设计方案:基于VU440T的多核处理器多输入芯片验证板卡

基于VU440T的多核处理器多输入芯片验证板卡 一、板卡概述 基于XCVU440-FLGA2892的多核处理器多输入芯片验证板卡为实现网络交换芯片的验证,包括四个FMC接口、DDR、GPIO等,板卡用于完成甲方的芯片验证任务,多任务功能验证。 …

GO语言Metex

Mutex互斥锁 type Mutex struct{state int32 // 0表示未加锁 1表示加锁,原子操作sema uint32 // 信号量,用作等待队列 } Mutex正常模式: 尝试加锁的G会先自旋几次,若获不到锁,则加入等待队列. 正常模式下,自选和等待队列的一起竞争 因为G频繁的挂起…

后端(五):JVM

目录 JVM 中的内存区域划分 JVM 的类加载机制 1. 加载 2. 验证 3. 准备 4. 解析 5. 初始化 JVM 中的垃圾回收策略 找,确认垃圾 1. 引用计数 2. 可达行分析 释放”垃圾“对象 1. 标记清除 2. 复制算法 3. 标记整理 分代算法 JVM也就是我们俗称的八股…

WEB阶段_CSSJS篇(附代码笔记)

&#xff08;一&#xff09;、使用DIVCSS布局首页 1、HTML的块标记 <!DOCTYPE html> <html><head><meta charset"UTF-8"><title></title></head><body><div>div1</div><div>div2</div><…

查看IP地址方法(电脑IP地址方法)

查看IP地址方法 如何识别win7还是win10系统&#xff1f; &#xff08;一&#xff09;Win7系统电脑导航栏如下&#xff1a; &#xff08;二&#xff09;Win10系统电脑导航栏如下&#xff1a; 一、win7系统查看IP地址 方法一&#xff1a;查看网络设置 点击电脑导航栏最右下…

ASEMI快恢复二极管MUR20100CTR在电子工程中的应用

编辑-Z 随着电子技术的日益发展&#xff0c;各种电子元件的使用场景与需求也在逐步扩大。今天&#xff0c;我们将聚焦于一款广泛应用于各类电路的二极管——MUR20100CTR&#xff0c;来详细解读其性能特征及应用。 一、MUR20100CTR二极管的主要特性 MUR20100CTR是一款极高性能的…

Redis远程字典服务

目录 前言 1.NoSQL 1.1NOSQL和关系型数据库比较 1.2非关系型数据库的优势 1.3关系型数据库的优势 ​编辑 2.主流的NOSQL产品 键值(Key-Value)存储数据库 列存储数据库 文档型数据库 图形(Graph)数据库 3.Redis简介 redis的应用场景 4.命令操作 4.1字符串类型 s…