admin管理员组

文章数量:1648621

目录

1、概述

2、软件异常的分类与排查

3、常用的C++异常排查思路与方法

3.1、IDE调试

3.1.1、Debug和Release下的调试

3.1.2、VS附加到进程调试

3.1.3、Windbg附加到进程调试

3.2、添加日志打印

3.3、分块注释代码

3.4、数据断点

3.5、历史版本比对法

3.6、Windbg静态分析与动态调试

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

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

3.7、使用IDA反汇编工具查看汇编代码

3.8、使用常用的分析工具去辅助分析

3.9、尝试找到复现问题的方法和步骤,确定代码的排查范围和方向

4、排查问题时如何使用上述方法?

5、学习软件调试技术的心得分享

6、最后


VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn/chenlycly/article/details/124272585C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn/chenlycly/article/details/125529931C++软件分析工具从入门到精通案例集锦(专栏文章正在更新中...)https://blog.csdn/chenlycly/article/details/131405795C/C++基础与进阶(专栏文章,持续更新中...)https://blog.csdn/chenlycly/category_11931267.html       在C++软件开发和维护的过程中,会遇到各式各样的软件异常,这些问题的分析和排查可能会消耗大量的时间和精力,会给软件研发流程的顺利推进带来不利的影响。上一篇我们详细讲述了引发C++软件异常的常见原因,本篇我们将详细介绍排查C++软件异常的常用思路与方法

1、概述

       作为C++软件开发人员,我们很有必要去系统了解一下引发C++软件异常的常见原因,这样我们在分析和排查问题时会更加敏锐,思路会更加开阔,可以根据现有的信息和线索进行更有针对性的分析。

       上篇文章在多年排查C++异常实践的基础上,系统地总结了引发C++软件异常的常见原因!引发异常的常见原因有变量未初始化内存越界内存访问违例stack overflow线程栈溢出空指针与野指针死循环多线程死锁多线程未加锁内存泄露堆内存被破坏程序抛出异常GDI对象泄露函数调用约定不一致导致的栈不平衡调用abort强行退出进程格式化符与参数不匹配库与库之间版本不匹配第三方库注入等,如下所示:

这些引发C++软件异常的常见原因,我们在上一篇文章中有详细的讲解与阐述,文章链接如下:

引发C++软件异常的常见原因分析与总结(实战经验分享)https://blog.csdn/chenlycly/article/details/124996473       在了解引发C++软件异常的常见原因之后,我们还需要系统地掌握排查C++软件异常的常用手段和方法,这样我们基本就能有效的快速分析和排查项目开发与维护过程中遇到的各种软件问题了。

一般地,要分析软件发生异常的具体原因,找到解决办法,我们可能需要想办法获取到发生异常时的函数调用堆栈,通过函数调用堆栈,我们可以知道都调用了哪些函数。我们可能还需要知道发生异常时异常上下文中相关变量的值,确定相关变量是否出现了非法的异常值。此外,我们可能还需要软件运行过程中输出的运行日志,通过查看日志搞清楚代码的执行轨迹和业务走向。我们需要使用一些工具和手段,去获取去搜集尽可能多的这些信息,去分析和解决问题。

       这些内容都属于C++软件调试技术领域的知识和技能。考察一个软件开发人员的水平,一是要看编码与架构设计能力,二是要看软件调试能力,所以调试能力非常重要。 

      至于为什么要学习C++软件调试技术以及学习C++软件调试技术有哪些好处,可以查看我的文章:

为什么要学习C++软件调试技术?掌握调试技术都有哪些好处?https://blog.csdn/chenlycly/article/details/130856385

2、软件异常的分类与排查

       软件异常主要分软件业务上的异常、软件启动异常与软件异常卡死或崩溃。

​       业务上的异常可能是代码逻辑导致的,也可能是错误的变量值导致的,一般只会导致业务逻辑出现问题,不会导致程序出现异常崩溃。业务上的异常,主要通过运行日志去排查,运行日志中可以看出代码的执行逻辑和运行轨迹,还可以将相关变量的值打印出来,业务逻辑上的异常可能和变量有着直接的关系。有时软件中添加的日志不全,可能还要后期根据问题排查的需要,添加一些新的打印进去。

       软件启动时的异常,比如软件启动不起来(启动时可能发生了崩溃闪退,还没看到软件界面就崩溃了)或者启动报错(弹出找不到程序依赖的dll库或者在某个dll中找不到某个接口)。启动崩溃则可以使用Windbg分析。对于缺少dll库或者在dll库中中找不到对应的接口导致的启动失败, 则需要使用Dependency Walker工具查看程序各模块的依赖关系,看哪个库依赖了缺少的库,都缺少了哪些库,哪些库调用的接口在dll中找不到,然后让相关dll模块维护开发人员去排查(当然有些问题,我们产品层可以直接定位处理)。

       软件异常卡死或崩溃,则需要借助一些工具进行分析。软件卡死可能是死循环、多线程死锁导致的,需要借助Process Explorer或者Process Hacker、调试器Windbg或gdb去排查。软件崩溃则是因为执行了引发异常的汇编指令,或者是软件中遇到问题抛出异常没有得到处理,导致的。对于软件崩溃,如果有dump文件,则用Windbg打开进行静态分析;如果没有dump文件,则可以尝试将Windbg附加到目标程序进程上进行动态调试,一旦复现异常,Windbg就会中断下来,就可以查看此时的函数调用堆栈进行分析了。

       此外,如果软件是UI界面程序,可能会出现与窗口相关的异常,比如窗口显示异常(比如窗口显示位置,窗口z序关系、窗口绘制失败、窗口绘制失败、窗口没有显示出来等等),则可以使用SPY++工具查看窗口风格、位置等信息来辅助排查。而窗口绘制异常(绘制失败),可能是相关API函数调用失败导致的,比如在实现异形窗口时调用UpdateLayeredWindow失败。也可能是程序中发生了GDI对象泄漏,导致程序占用的GDI对象总数接近或达到进程的上限,导致GDI函数执行失败,所以窗口绘制失败,这种情况则需要使用GDIView工具查看是哪种GDI对象有泄漏,然后结合代码去分析。

       上面提到的这些问题,我们在项目中都遇到过,在我的《C++软件调试与异常排查从入门到精通》专栏都有具体的项目问题实战分析案例,详细讲解问题的排查过程和方法,并对相关要点问题进行相关的总结:

C++软件调试与异常排查从入门到精通(包含大量项目问题实战分析案例,很有参考价值)https://blog.csdn/chenlycly/article/details/125529931

3、常用的C++异常排查思路与方法

       排查C++软件异常常用的方法有IDE调试添加打印日志分块注释代码历史版本比对法调试器分析与调试使用IDA工具查看汇编代码辅助分析使用常用的分析工具去辅助分析等,如下所示:

       方法有多个,我们在分析问题时具体使用哪种方法, 就要具体问题具体分析了。有些问题可能比较简单,我们借助一些分析工具,很快就能定位问题。有些问题比较复杂,我们可能需要使用多种方法去综合分析。下面我们就来详细讲述这些常用方法。


        在这里,给大家重点推荐一下我的几个热门畅销专栏,欢迎订阅:(博客主页还有其他专栏,可以去查看)

专栏1:该精品技术专栏的订阅量已达到530多个,专栏中包含大量项目实战分析案例,有很强的实战参考价值,广受好评!专栏文章持续更新中,预计更新到200篇以上!欢迎订阅!

C++软件调试与异常排查从入门到精通系列文章汇总https://blog.csdn/chenlycly/article/details/125529931

本专栏根据多年C++软件异常排查的项目实践,系统地总结了引发C++软件异常的常见原因以及排查C++软件异常的常用思路与方法,详细讲述了C++软件的调试方法与手段,以图文并茂的方式给出具体的项目问题实战分析实例(很有实战参考价值),带领大家逐步掌握C++软件调试与异常排查的相关技术,适合基础进阶和想做技术提升的相关C++开发人员!

考察一个开发人员的水平,一是看其编码及设计能力,二是要看其软件调试能力!所以软件调试能力(排查软件异常的能力)很重要,必须重视起来!能解决一般人解决不了的问题,既能提升个人能力及价值,也能体现对团队及公司的贡献!

专栏中的文章都是通过项目实战总结出来的,包含大量项目问题实战分析案例,有很强的实战参考价值!专栏文章还在持续更新中,预计文章篇数能更新到200篇以上!

专栏2:  

C++常用软件分析工具从入门到精通案例集锦汇总(专栏文章,持续更新中...)https://blog.csdn/chenlycly/article/details/131405795

常用的C++软件辅助分析工具有SPY++、PE工具、Dependency Walker、GDIView、Process Explorer、Process Monitor、API Monitor、Clumsy、Windbg、IDA Pro等,本专栏详细介绍如何使用这些工具去巧妙地分析和解决日常工作中遇到的问题,很有实战参考价值!

专栏3:(本专栏涵盖了C++多方面的内容,是当前重点打造的专栏,专栏文章已经更新到420多篇,持续更新中...)

C/C++实战进阶(专栏文章,持续更新中...)https://blog.csdn/chenlycly/category_11931267.html

以多年的开发实战为基础,总结并讲解一些的C/C++基础与项目实战进阶内容,以图文并茂的方式对相关知识点进行详细地展开与阐述!专栏涉及了C/C++领域多个方面的内容,包括C++基础及编程要点(模版泛型编程、STL容器及算法函数的使用等)、数据结构与算法、C++11及以上新特性(不仅看开源代码会用到,日常编码中也会用到部分新特性,面试时也会涉及到)、常用C++开源库的介绍与使用、代码分享(调用系统API、使用开源库)、常用编程技术(动态库、多线程、多进程、数据库及网络编程等)、软件UI编程(Win32/duilib/QT/MFC)、C++软件调试技术(排查软件异常的手段与方法、分析C++软件异常的基础知识、常用软件分析工具使用、实战问题分析案例等)、设计模式、网络基础知识与网络问题分析进阶内容等。

专栏4:   

VC++常用功能开发汇总(专栏文章,持续更新中...)https://blog.csdn/chenlycly/article/details/124272585

将10多年C++开发实践中常用的功能,以高质量的代码展现出来。这些常用的高质量规范代码,可以直接拿到项目中使用,能有效地解决软件开发过程中遇到的问题。

专栏5: 

C++ 软件开发从入门到精通(专栏文章,持续更新中...)https://blog.csdn/chenlycly/category_12695902.html

根据多年C++软件开发实践,详细地总结了C/C++软件开发相关技术实现细节,分享了大量的实战案例,很有实战参考价值。


3.1、IDE调试

       这是最直接,也是较有效的方法。主要有以下几类调试方法,具体使用那种方法要看问题是否是必现的,问题发生在哪些模块中等。通过直接调试,一般我们可以快速的发现和定位问题。

       关于Visual Studio调试方式的详细说明,可以查看我的文章:

Visual Studio调试方式详解https://blog.csdn/chenlycly/article/details/125587329

3.1.1、Debug和Release下的调试

       如果问题是必现的,最直接的方法就是直接调试源代码了。可以先尝试在Debug下调试,但有的bug可能在Release下才会出现,这时就需要在Release下进行调试了。之所以Debug版本程序和Release版本程序在实际运行时有差异,这个两个编译版本下的特性有关系的,比如Debug下编译器会对一些未初始化的变量进行自动的初始化,再比如debug和release版本在内存管理机制上是有所不同的。

       需要注意的是,在release下调试需要事先将工程选项中的优化关闭掉,比如Windows平台下Visual Studio(本文中的实例均以Windows平台下使用Visual Studio开发C++程序为例,Visual Studio在下文中将简称VS)中相关的设置:

3.1.2、VS附加到进程调试

       这种方法对单独调试dll模块非常好用。对于底层的组件库、协议库等的调试,这些dll库需要依附上层的exe主程序才能运行,可以让底层库的开发人员先安装release版本的安装包,然后只需要编译要调试的模块库,编译完成后将库文件拷贝到安装路径下。然后启动exe程序,用VS的附加到进程的调试功能

去直接调试这些底层库。当然有些问题可能是release下才会有的,所以可能也需要进行release下的调试。

       这个地方需要注意一下,如果要调试程序启动时初始化那部分的代码,则需要在代码中添加阻塞程序执行的代码,比如UI程序中可以人为地添加一个MessageBox,这样去等待VS去设置附加调试,这样就有机会去调试初始化的代码了。

      使用Visual Studio调试代码时,需要掌握一些高效的调试手段和技巧,可以查看我的文章:

Visual Studio高效调试手段与技巧总结(经验分享)https://blog.csdn/chenlycly/article/details/124884225

3.1.3、Windbg附加到进程调试

        Windbg是Windows平台下软件异常静态分析与动态调试的利器,是我们分析Windows程序异常最常用的调试器工具。该工具由微软官方提供。

       对于有些崩溃(比如Stack overflow栈溢出)问题,即便是使用Visual Studio进行调试时检测到了,但进程可能已经退出了,VS随即也退出了调试状态,所以此时是无法查看到崩溃那一刻的函数调用堆栈的,更是没法看到发生异常时变量在内存中的值的。此时可以考虑将Windbg附加到目标进程中:

按操作步骤将崩溃复现后,Windbg中是可以查看到崩溃时的函数调用堆栈的。

3.2、添加日志打印

       添加日志打印应该是大家排查问题用的最多一种方法之一。通过查看日志,可以搞清楚代码的执行轨迹和业务走向,判断业务逻辑和代码执行是否出现异常。

       很多时候根据出问题时的现象,很难判断出到底是哪一块出的问题,且问题是很难复现的,无法直接去调试代码。这时我们可以在代码中添加打印日志,将可能出现问题的地方都添加上打印,将运行到的点以及相关变量的值都打印到目标窗口或文件中,然后根据运行时产生的打印看数据及流程是否有异常,从日志打印中去找线索,找排查问题的突破口。比如我们某个模块的打印日志如下:

       这种方法对于频繁执行、不方便直接调试的代码块,也是非常实用的。

       对于大型软件系统,很有必要去构建一个完备的日志系统,不仅可以检测到系统运行过程中用户行为,还可以通过打印来定位软件运行过程中遇到的问题。就像编译程序时编译器输出的异常提示信息一样,我们也可以对打印出的日志类型进行归类分级,比如有error错误日志、warning警告日志、debug调试日志、info日常运行日志等。

3.3、分块注释代码

       在问题排查不出来时,可以尝试采用分块注释代码的方法去定位。这个方法,在日常项目问题排查中会时不时用到。

       我们可以将某一行或者某些行的代码注释掉后,问题可能就没有了,那基本上可以确定问题就出在被注释的代码上了,甚至能确定问题出在哪一行代码上。然后排查这些被注释的代码,也许就能找到问题的症结了。

有次遇到这样的问题,A库依赖B库,B库定义了结构体Struct1,A库调用了B库的GetData接口, GetData接口是以Struct1结构体作为参数的,GetData中进行了数据的memcpy操作。因为库发布的问题,导致两个库不匹配。B库中在Struct1结构体中增加了字段,但是A库中使用的还是老的结构体,这样在调用GetData传入结构体地址或引用,由于GetData中进行的是memcpy的操作,在调用完该接口后,导致主调函数中栈内存越界,直接篡改了主调函数中其他栈变量的内存,导致代码出现不可解释的异常运行行为。这个问题当时查了很久,就是通过分块注释代码才查出来的。

3.4、数据断点

        对于运行过程中变量的值无故被篡改,或者我们通过排查现有代码始终找不到修改变量值的代码行号时,我们此时可以尝试使用Visual Studio自带的数据断点功能,通过添加数据断点,监测出修改目标内存的代码行。

数据断点本质上是监控某段内存中的数据是否被修改,如果我们对某段内存设置了数据断点,一旦内存中的数据被修改,IDE的调试器就会命中该断点,此时切换到调用堆栈窗口中去查看此时的函数调用堆栈,就知道是哪块的代码修改目标变量的值了。

       设置数据断点是有技巧的,即在目标变量初始化的地方设置普通断点,然后启动VS调试,当VS命中该普通断点时,目标变量就分配了内存(目标变量分配了内存后才能给该内存设置数据断点),此时通过变量名就可以取到目标变量的内存地址了,然后给这个内存地址设置数据断点即可:

 

当有代码修改了该变量的值时,就会命中该数据断点,查看此时的函数调用堆栈就知道是什么代码修改了变量的值了。数据断点在监测变量值无故被篡改时确实非常好用,我们在项目中已多次使用了。

      关于巧用数据断点去排查内存越界的案例,可以参加我的文章:

巧用Visual Studio中的数据断点去排查C++内存越界问题https://blog.csdn/chenlycly/article/details/125626617

3.5、历史版本比对法

       这个方法似乎有点笨拙,甚至会有些耗时,但却是非常有效果。比如某天我们软件突然出现了一个之前未曾出现的问题,实在是找不到线索,我们可以尝试安装不同时间的历史版本:

然后看看这个问题是从哪天开始出现的,然后查看这个时间点附近提交的代码,问题基本上就出在这个时间点提交的代码中。具体操作时,我们可以在时间上使用二分法缩小排查的范围。

举个之前遇到的例子,测试同事发现最近我们的软件在运行时窗口老是有明显的卡顿问题,很是困惑,但最近代码并没有做太大的改动,前一段时间运行还好好的。查找了最近代码的修改记录,似乎也没找到问题。

于是尝试了这个方法,让测试同事多安装几个时间点的版本,看看是哪个时间点的版本开始出现问题的。找到了这个时间点,在svn上查找前一天提交的代码,仔细地看了一下,是从网上拷贝的一段参考代码有问题,不看不知道,一看吓一跳!在代码中启动了一个UI界面的定时器,在定时器消息的处理代码中竟然有sleep操作,这个是运行在UI界面线程中的,当执行到sleep操作时,系统会将UI线程挂起,这就直接导致UI窗口卡住了,虽然sleep时间很短,但是定时器消息会一直触发,这个代码会频繁地执行,所以就出现了界面频繁卡顿的问题了。

       使用该办法找出第一次问题的版本后,就基本能确定是前一次提交的代码引起的,于是我们就找到问题的线索和突破口了,进一步分析提交的代码可能就能很快定位问题了。

要使用历史版本比对法,需要有一个完善的历史版本自动编译和文件存放管理系统自动化编译系统每天对代码进行检测,如果当天代码有变化,则在晚上或者凌晨自动编译版本,并将编译出来的版本自动拷贝到文件服务器上保存下来。将每天自动编译出来的版本保存到对应日期的文件夹中,这样方便日后查阅。

3.6、Windbg静态分析与动态调试

       Windbg是Windows平台下软件异常静态分析与动态调试的利器,是我们分析Windows程序异常崩溃最常用、最重要的调试器工具!该工具由微软官方提供。使用Windbg分析软件异常,主要有两种方式,一种是使用Windbg静态分析dump文件,一种是使用Windbg去动态调试目标进程。 

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

       对于Widnows程序,dump文件包含了软件发生异常时异常上下文信息,包含目标进程的所有线程的信息(比如寄存器信息)、线程中的函数调用堆栈信息,甚至包含有部分或全部的变量值静态分析dump文件,是排查C++软件异常最常用的方法

3.6.1.1、dump文件的生成方式

       dump文件是如何产生的呢?主要有以下四个途径生成dump文件:

1)从任务管理器中导出

        可以在WIndows系统的任务管理器中右键点击目标进程,在弹出的右键菜单中点击“创建转储文件”菜单项导出dump文件。

2)安装异常捕获模块,由该模块自动生成dump文件(最常用的方式

       一般Windows程序中都会安装异常捕获模块(比如google开源的CrashRpt / breakpad / crashpad),一旦软件发生异常崩溃,该异常捕获模块就能感知到,就会将发生异常时的异常上下文信息导出到dump文件中。比如QQ、钉钉、企业微信等常用的软件中都安装了异常捕获模块,当软件发生异常时就会弹出一个提示框:

提示软件发生了崩溃,询问是否将崩溃信息上报给后台,然后后台拿到这个dump文件之后好久可以进行分析了。

3)Windbg动态调试时使用.dump命令动态导出

       直接将Windbg挂载到正在运行的目标进程上,或者直接使用Windbg启动目标进程上,一旦软件发生崩溃,Windbg就会捕获到,Windbg会中断下来,此时可以使用.dump命令将异常上下文信息导出到dump文件中。Windbg捕获到异常后会立即中断下来,按讲此时就可以直接分析了,为啥还要手动将异常信息导出到dump文件中呢?可能当时不能立即查出问题,亦或是出问题的电脑是其他同事的或者是客户的,我们需要导出到dump文件中去详细研究一下。

       建议大家在代码中安装捕获软件异常的模块或代码,比如常用的是google开源的CrashRpt异常捕获库(很多软件比如QQ、钉钉在都使用类似的库),在软件发生崩溃时能实时捕获到异常信息,自动生成dump文件。然后测试人员将取到的dump文件发送给开发人员,开发人员使用Windbg打开dump文件进行分析:

4)Windows系统帮我们自动生成了dump文件

        有时操作系统感知到我们程序发生了异常崩溃闪退,也会帮我们自动生成dump文件,如下所示:

以Win10系统为例,在桌面上右键点击“此电脑”,在弹出的右键菜单中点击“管理”菜单项,如下所示: 

打开计算机管理窗口后,在系统工具节点下,展开事件查看器节点,在该节点下继续展开Windows日志节点,然后点击应用程序节点,这样右边就显示应用程序相关的系统日志了。于是按照程序出问题时的时间点,在应用程序日志列表中,找对应时间点的日志记录,可能就能找到了系统帮我们自动生成的dump文件路径了!在软件中安装的异常捕获模块,没法捕获所有场景下的异常,当程序发生闪退崩溃且异常捕获模块没捕获到时,可以尝试到系统日志中查看系统有没有自动生成dump文件!这种场景,大家平时估计很少用到,正好前段时间在项目中遇到过,已经整理成文章,文章链接如下:

使用Windbg分析从系统应用程序日志中找到的系统自动生成的dump文件去排查问题https://blog.csdn/chenlycly/article/details/1320242533.6.1.2、使用Windbg分析dump文件       

       在Windbg打开dump文件,先用.ecxr命令切换到异常发生时的上下文,然后使用kn、kv或kp命令查看异常发生时的函数调用堆栈,然后就可以对照着代码去详细分析了。这种情况属于Windbg的静态分析,不是动态调试。关于使用Windbg静态分析dump文件的详细步骤及要点,可以参见我的文章:

使用Windbg静态分析dump文件的一般步骤及要点详解https://blog.csdn/chenlycly/article/details/130873143       另外,dump文件还分全dump文件小dump文件。小dump文件则比较小(大概几MB的大小),只包含了少量或部分变量的内存信息,一般是异常捕获模块自动生成的。异常捕获模块自动生成的dump文件可能会比较多,比较频繁,不能生成太大的文件,否则会消耗用户大量的磁盘空间,甚至将用户的磁盘空间占满。而全dump文件包含了所有变量的内存信息,文件大小一般会达到好几百MB,一般是通过任务管理器或者Windbg动态调试时导出的。

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

        我们可以通过Windbg直接启动目标程序,也可以将Windbg附加到已经启动的程序进程上进行动态调试:

       比如程序发生“卡死”或者死循环时,我们可以将Windbg附加到程序进程中进行动态调试,查看此时线程的函数调用堆栈,以确定发生问题的线程以及线程中当前的函数调用堆栈信息。

       甚至在系统已经弹出报错提示框时: 

将Windbg附加到出问题的进程上也能查看到异常信息的。

       另外,我们在遇到一些VS捕获不到异常时,比如遇到Stack overflow线程栈溢出崩溃时,VS中是看不到异常发生时函数调用信息的,但我们可以通过Windbg的动态调试能及时捕获到线程栈溢出异常,然后就可以查看到崩溃时的函数调用堆栈了。

       此外,我们在软件中安装的异常捕获模块并不能捕获所有场景下的异常,特别是有些闪退是捕获不到的,此时我们可以尝试将Windbg挂载到目标进程上去动态调试,然后对软件进行拷机测试(可以使用自动化测试工具自动进行拷机测试)。当软件发生异常时,Windbg是能捕获到的,能能第一时间中断下来。

       关于使用Windbg动态调试目标程序的详细步骤及要点,可以参见我的文章:

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

       关于何时使用Windbg进行静态分析、何时使用Windbg进行动态调试,可以参见我的文章: 

何时使用Windbg静态分析?何时使用Windbg动态调试?https://blog.csdn/chenlycly/article/details/131806819

3.7、使用IDA反汇编工具查看汇编代码

        有时我们查看C++源代码很难搞清楚问题到底出在那句代码上,此时我们可能就需要查看汇编代码去分析了。软件最终是崩溃在某一条汇编代码上,通过查看汇编代码才能最直观的看出为什么会发生异常崩溃。

      在Windows平台下我们主要使用IDA Pro反汇编工具打开二进制文件,去查看二进制文件中的汇编代码。我们一般对汇编代码不是很精通,我们需要将C++源代码和汇编代码对照看,才能看懂汇编代码的上下文,但release下编译器在编译时会对代码进行优化,这样会导致汇编代码有时较难和C++源代码完全对应起来。

      至于如何使用IDA Pro反汇编工具,可以参看我的文章:

使用反汇编工具IDA查看发生异常的汇编代码的上下文去辅助分析C++软件异常https://blog.csdn/chenlycly/article/details/132283582使用IDA查看汇编代码,结合安卓系统生成的Tombstone文件,分析安卓app程序崩溃问题https://blog.csdn/chenlycly/article/details/132283582

       此外,C++开发人员要了解汇编,了解汇编后有诸多的好处。不仅可以辅助排查C++软件异常问题,还可以理解很多高级语言不好理解的代码执行细节(从汇编代码的角度可以看到代码的完整执行细节),特别是多线程编程中的问题。

       关于为什么要学习汇编以及学习汇编有哪些好处,可以查看我的文章:

为什么要学习汇编?学习汇编有哪些好处?https://blog.csdn/chenlycly/article/details/130935428

3.8、使用常用的分析工具去辅助分析

       在日常的C++软件开发和维护的过程中,我们时常会用到一些高效的软件分析工具去辅助分析和排查软件运行中遇到的各类问题。常用的分析工具有SPY++、Dependency Walker、剪切板查看工具Clipbrd、 GDI对象查看工具GDIView、Process Explorer、Process Monitor、API Monitor、调试器Windbg、交互式反汇编工具IDA、抓包工具Wireshark等。

       关于这些工具的具体用途及使用方法,可以查看我的文章:

为什么要学习使用C++软件常用分析工具?学会这些工具都有哪些好处?https://blog.csdn/chenlycly/article/details/131626263

3.9、尝试找到复现问题的方法和步骤,确定代码的排查范围和方向

       复现问题,找到复现问题的方法,也是排查问题的一种有效的辅助手段与方法。

       我们在遇到问题时,可以尝试使用上面讲到的多种方法去排查。但有时我们需要让测试人员去复现问题,找到复现问题的方法与步骤,通过复现的步骤确定问题与哪些操作有关,这样就能大概确定代码的排查范围和方向,然后在查阅代码的过程中结合用户执行的操作(复现问题的步骤),查找代码中存在的逻辑控制错误或业务异常。

       对于软件UI异常或者业务异常,我们可能会尝试去找问题的复现办法,或者是我们在使用一些方法和手段去排查但很难定位问题时,也希望测试人员去尝试复现问题。 再比如,有些问题是在客户环境中出现的,在公司测试环境中没出现过,问题很难排查定位,我们可能需要尝试在公司环境中复现,如果能复现,在公司环境中排查起来也比较方便。

4、排查问题时如何使用上述方法?

       我们要熟练掌握上述排查问题的方法,以便在遇到问题时能有效地应对,能有足够的思路和手段去排查。但这些方法不是孤立存在的,对于一些复杂的或者难以排查的问题,我们可能要尝试使用多种方法去排查,或者将一些方法结合起来去灵活使用。在实际的问题排查过程中,到底使用哪种方法或者哪几种方法,就要具体问题具体分析了。

       对于一些相对简单的问题,一开始就可以使用工具去排查定位,或者使用工具去辅助定位。比如:

1)窗口问题

可以使用SPY++查看窗口的大小和坐标、窗口风格(WS_POPUP等)、父窗口信息等。

2)找不到dll库以及在dll库找不到某个接口导致程序失败问题

这类问题,使用Dependency Walker查看程序中各模块的依赖关系,查看依赖的那些库找不到,哪个模块依赖了找不到的dll库,那个模块的调用了找不到的接口,然后直接在这些模块中查找引发问题的原因。这类问题可以参见我之前写的文章:

3)查看程序启动时加载了哪些dll库,动态启动的dll库有没有启动起来

       使用Process Explorer工具可以查看到当前系统中启动的程序进程列表,类似于系统的任务管理器,但可以看到比任务管理器多的多的信息。

       打开软件后,在进程列表中选中某个进程后,在主界面下方就会显示该进程加载了哪些dll库,可以查看到这些库的详细信息,比如库的路径、版本等。

       通过查看这个列表,可以确定动态启动的库有没有启动起来。动态启动的库主要是通过调用LoadLibrary或LoadLibraryEx启动的,如果该库依赖的dll在机器上找不到或者调用的接口在对应的dll中找不到,则会加载失败,这类问题我们在项目中多次遇到(我们底层的多个业务模块就是根据需要动态加载的)。此时我们使用Dependency Walker查看加载失败的dll库的依赖关系,就能确定是依赖的dll库在机器上找不到(可能依赖了新增的dll库或者运行时库,但没有将之打包到安装包中),还是依赖的库中的接口有问题(通常是dll库的版本不一致导致的),这样就能定位问题了。此类问题的实战分析案例,可以查看我的文章:

4)程序中发生死循环导致的较高CPU占用问题

       程序中发生了死循环,代码在不停歇的执行,会占用大量的CPU时间片,所以会占用较高的CPU。对于这类问题,可以直接使用Process Explorer工具查看到底是哪个线程CPU占用高,然后双击该线程查看该线程的函数调用堆栈,即可确定哪个函数发生了死循环。此类问题的实战分析案例,可以查看我的文章:

5)查看二进制文件的位数与时间戳

       有时排查问题时我们需要查看dll、exe等二进制文件的位数(32位与64位)与时间戳(二进制文件的生成时间),可以用dumpbin或者PE查看工具(比如MiTeC EXE Explorer)查看。如何查看二进制文件的位数与时间戳,可以查看我的文章:

如何查看exe和dll等二进制文件的生成时间(时间戳)和位数(32位/64位)https://blog.csdn/chenlycly/article/details/140043291       而对于一些复杂的问题,则需要使用上面讲到的多种方法去排查,当然在排查的过程中也可以使用这些工具去查看信息。

       我们在系统了解引发C++软件异常的常见原因以及排查C++软件的常见方法之后,这样我们在分析和排查问题时会更加敏锐,思路会更加开阔,可以根据现有的信息和线索进行更有针对性的分析。此外,我们要积极主动地将这些知识点与技能应用到日常项目中去,通过项目实践去获取进一步的理解和认知,进而真正掌握这些方法和技能。

5、学习软件调试技术的心得分享

​       本文讲的东西属于C++软件调试技术中的内容。通过几年的软件异常排查项目实战,对C++软件调试与异常排查技术进行了系统地总结,并开始对外输出这方面的技术内容。在公司范围内开始做C++软件调试技术的分享与培训,在培训和交流的过程中发现很多C++程序员在软件调试这一块比较欠缺,无论是刚毕业的年轻人,还是工作多年的老程序员。这其中有一些环境的原因,也有一些个人的原因

1)环境原因:

可能工作环境中同事们很少使用专用的调试器与分析工具去排查问题,无法接触到系统的软件调试技术。

2)个人原因:

还有一种可能,工作环境中有些同事已经在系统地使用调试技术去解决问题,并在公司、部门或者小组中做过详细的技术分享(培训),大家也有去听课学习,但大部分人都是三分钟热度,部分人可能没有认真学,部分人可能最开始觉得很有价值,学习态度比较好,但没有坚持下去,没有主动将所学的内容用到工作实践中去,然后就不了了之了,然后就没有然后了!学习技术,就是要投入大量的时间和精力,要不断的捣鼓和实践,要坚持不懈,把技术用到工作实践中去,才能获取进一步的理解和认知!这样才能真正掌握这些方法和技能。

       一门技术或技能的学习,是需要不断地实践的,要把所学的东西应用到工作实践中去,在学习中持续实践,在实践中持续学习!在我们漫长的职业生涯中,正是通过很多小事件从量变到质变,去实现技术的提升。

6、最后

      以上内容是在多年的C++软件异常排查实践的基础上总结出来的,有较强的实战参考价值,希望能给大家提供借鉴和参考!在实际的问题排查过程中,到底使用哪种方法或者哪几种方法,就要具体问题具体分析了。

本文标签: 实战思路异常常见经验