C++软件分析师异常分析工作经验汇总

点击蓝字

1d0761d0c6efea2c4f4a848f7a14a70c.png

关注我们

最近几年工作当中很大一部分内容是排查软件运行过程中遇到的各种异常,积累了一定的经验,在此给大家分享一下。本文将详细讲述Windows系统中软件异常的分类以及常用的排查方法,给大家提供一个借鉴与参考。

1、软件异常的分类

常见的软件异常有内存越界、内存访问违例、stack overflow线程栈溢出、空指针与野指针、死循环、死锁、内存泄露、GDI对象泄露、函数调用约定不一致导致的栈不平衡等。

有的异常会立即导致软件崩溃。有的异常在运行一段时间或者长时间运行后才会导致崩溃,比如内存泄露。有的异常则不会导致崩溃,只会导致软件发生堵塞或卡死,比如死循环和死锁。

还有一类问题会导致业务代码执行上的异常,这类问题不会导致软件崩溃,会导致业务代码没有按照正常逻辑或者分支被执行,导致业务逻辑出异常。比如函数抛出异常导致部分代码被跳过,即该执行的代码没有执行到,导致后续业务代码执行时出现逻辑异常。正常情况下,这些被跳过的代码会执行一些判断,会设置一些变量的值,会直接影响后续代码的判断与执行逻辑,所以会导致后续代码在运行时出现业务逻辑上的异常。这样的问题我们以前遇到过几次。

再比如系统API函数的lasterror被覆盖,导致后续判断lasterror值的条件判断出现逻辑上的错误,即产生了误判。这个问题我们以前也遇到过,在libjingle库的开源代码中添加了一个打印,因为开源代码的接口封装的层次比较深,导致我们在添加日志打印的代码时没看出会把lasterror值覆盖的问题。具体的是,添加的那句打印日志的代码中调用系统API函数,该行打印日志的代码执行完后就会把其前一句的开源库代码产生的lasterror值给覆盖了。而在打印日志下面的下一句开源代码中的接口内部会判断上一句开源代码执行后的lasterror值,因为该lasterror值被覆盖了,所以导致对lasterror的判断条件出现误判,导致后续的业务代码出现了逻辑上的异常。

2、使用windbg分析软件异常

windbg是Windows平台一个最强大、最通用的软件调试分析工具,Windows平台主要使用它来分析各种软件异常。

大部分异常崩溃问题,软件中的异常捕获模块(大家都在使用开源的carshreport异常捕获库)都能捕获到,并将发生异常时的上下文保存到dump文件中,事后可以使用windbg静态分析这些dump文件。

对于有些不会导致软件崩溃的异常,比如死锁、死循环和内存泄露,需要我们将windbg挂载到目标进程上进行动态分析了。

对于少数异常捕获模块捕获不到的异常,比如程序在运行过程中发生闪退,需要将windbg附加到目标进程上跑,即windbg和其附加的目标进程绑定在一起运行,一旦目标进程出现异常,windbg就能立即感知到并中断下来。将windbg附加到目标进程上后,我们需要想办法去复现异常,异常产生后windbg会捕获到并中断下来,此时可以直接去使用windbg命令去分析了,也可以将使用.dump命令将异常上下文导出到dump文件中供事后分析。可能分析问题会比较耗时,可能出问题的电脑是同事的或者是领导的,不能一直占着别人的电脑,此时可以选择导出dump文件供事后用windbg分析。

对于一些弹框报错或者软件发生卡死的异常,软件一直卡在这个点上(目标进程还在),此时可以直接将windbg挂上去,这个时间点已经出现了异常,但挂载windbg的时机也不晚,也是可以获取到异常的上下文信息的。对于这类问题,不要点击报错的确定按钮,或者不要急于通过资源管理器去强杀目标进程,将目标进程保留住,此时将windbg挂上去正是时候,也能获取到完整的异常上下文信息。这些异常可能是事后很难复现的,必须要逮住这个机会,将windbg直接挂载到出问题的进程上进行分析。错过这个机会,下次可能就很难复现了,这样软件中就留下了很大的隐患。

3、除windbg之外的常用异常排查方法

当然除了windbg的静态分析和动态调试,还有一些其他的常用方法,这些方法也很重要,也需要掌握,比如使用VS直接调试(Debug或Release调试)、附加到进程中调试、添加打印日志、历史版本比对法(找出开始出问题的那个时间点)、分块注释代码、设置数据断点(对内存进行实时监控)等。有时,我们需要将多种方法结合起来使用。

对于业务上的一些逻辑异常,一般需要通过添加日志打印来排查。还有一种典型的异常是软件运行过程中遇到了错误,软件自己判断出来了,并认为是致命性错误,会直接调用abort或者exit强行终止进程的。比如在开源的jsoncpp库中,在解析到异常的json节点时出错,会直接调用abort强行终止整个进程。再比如在开源的webrtc库中,在用new申请堆内存失败时,webrtc库内部会认为发生了致命性的错误,也会调用abort强行终止进程。这类主动强行终止进程的,异常捕获模块是捕获不到的异常,因此是不会生成dump文件的。

       这类情况可以将windbg挂载到目标进程上跑,一旦调用abort接口,windbg就会中断下来,此时查看函数的调用堆栈就能看到是何处代码触发的问题了。为啥windbg能感知到,软件中安装的异常捕获模块捕获不到呢?因为软件本身并没有RaiseException,是软件主动终止进程的。那为啥windbg能感知到呢?因为abort接口中会产生一个SIGABRT终止信号通知,这个调试器能感知到,于是windbg就产生了中断。此时使用kn命令查看函数调用堆栈,就可以看到是哪些接口触发的问题了。可以直接在Visual Studio中go到abort函数实现处,查看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.
*
*Entry:
*   None.
*
*Exit:
*   Does not return.
*
*Uses:
*
*Exceptions:
*
*******************************************************************************/void __cdecl abort (
void)
{_PHNDLR sigabrt_act = SIG_DFL;#ifdef _DEBUG
if (__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);
}

从代码中可以看出,产生了一个SIGABRT终止信号通知。 

4、开源的CrashReport库存在的问题

很多厂商都在使用开源的crashreport异常捕获库,但原生的CrashReport库是有缺陷的。很多大厂使用的应该是经过深度改进的crashreport库。

开源的crashreport异常捕获库是动态地将已加载的库的导入表中创建线程的API函数CreateThread HOOK成我们自定义的MyCreateThread函数(不管调用哪个创建线程的接口,最终都会走到CreateThread接口中的),这样就能在MyCreateThread中调用系统API函数SetUnhandledExceptionFilter给每个创建的线程挂载异常处理函数了。

但这种机制是有缺陷的,不能给软件的所有模块的线程挂载异常处理函数,只能给在crashreport库之前加载的库挂载异常处理函数,在crashreport之后加载的库就没法去hook了,这样就会导致没进行hook操作的那些库中发生的异常都捕获不到了。在exe启动时,会把所有依赖的库加载到进程空间中,我们没法控制所有的库都在crashreport库之前被加载的,这也导致了有些异常崩溃crashreport时捕获不到的。

后来我们对crashreport库进行了改进,使用微软开源的detours项目中的代码将windows系统库中的UnhandledExceptionFilter接口给HOOK掉。因为基本所有的异常都会最终进入到该函数中,我们将UnhandledExceptionFilter接口hook成我们自定义的接口,我们就能在该自定义的接口中感知到几乎所有的异常了。感知到异常后,就可以生成包含异常上下文的dump文件了。这样就能很好的解决老版本crashreport不能hook后加载的库的问题,新版本的crashreport就可以作用于当前进程的所有模块了,基本可以捕获到进程的所有异常了。

当然改进后的crashreport也不是100%的异常都能捕获到,但可以捕获大概90%以上的异常。对于捕获不到的场景,就需要将windbg挂载到目标进程上,让windbg去捕获了。

*声明:本文于网络整理,版权归原作者所有,如来源信息有误或侵犯权益,请联系我们删除或授权事宜。

055c27fef6d7c18bdbbc57f21dc893be.png

a79b5941bcb35b254d4e3e25789ccca5.gif

戳“阅读原文”我们一起进步

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

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

相关文章

java fix_Java中的低延迟FIX引擎

java fix总览 Chronicle FIX是我们的Low Latency FIX引擎和Java数据库。 是什么使它与众不同? 是为Java中的超低GC *设计的。 支持字符串和日期时间的方式可以最大程度地减少垃圾和开销。 可自定义为仅包含您期望的字段。 使用通常在二进制解析器和生成器中使用…

linux 查看防火墙状态_每天五分钟学习Linux系列之 - 系统安全配置

20年IT从业,二哥的团队使用最多的系统就是Linux,开发,运维的小伙伴们都离不开Linux系统,特别是大数据和人工智能领域更是如此,但由于日常工作忙,小伙伴们没有太多成块的时间系统的学习Linux, 并且现版CentO…

C++红黑树模拟实现map和set

点击蓝字关注我们一、红黑树及其节点的设计对于底层都是红黑树的map和set来说,他们之间存在的最大的区别就是:对于set是K模型的容器,而map是KV模型的容器。为了更好的灵活兼容实现map和set,就需要在红黑树以及树节点上进行特别的设…

c语言连接mysql_聊聊数据库MySQL、SqlServer、Oracle的区别,哪个更适合你?

一、MySQL优点:体积小、速度快、总体拥有成本低,开源;支持多种操作系统;是开源数据库,提供的接口支持多种语言连接操作 ;MySQL的核心程序采用完全的多线程编程。线程是轻量级的进程,它可以灵活地…

绩效工作流_流绩效–您的想法

绩效工作流上周,我介绍了一些有关Java 8流性能的基准测试结果。 你们和gal足够有兴趣留下一些想法,还有哪些可以介绍。 这就是我所做的,这里是结果。 总览 最后一篇文章的序言也适用于此。 阅读它,以找出所有数字为何撒谎&#…

C语言访问MCU寄存器的两种方式

点击蓝字关注我们单片机的特殊功能寄存器SFR,是SRAM地址已经确定的SRAM单元,在C语言环境下对其访问归纳起来有两种方法。1、采用标准C的强制类型转换和指针来实现采用标准C的强制转换和指针的概念来实现访问MCU的寄存器,例如:#define DDRB (*…

08_优先队列

08_优先队列 一、优先队列最大优先队列最大优先队列API设计 最小优先队列最小优先队列API设计最小优先队列代码实现 索引优先队列索引优先队列实现思路索引优先队列API设计索引优先队列代码实现 一、优先队列 :::info 普通的队列是一种先进先出的数据结构,元素在队…

Python3实现红黑树[下篇]

Python3实现红黑树[下篇]我写的红黑树的上篇在这里:https://blog.csdn.net/qq_18138105/article/details/105190887 这是我近期看的文章 https://www.cnblogs.com/gcheeze/p/11186806.html 我看了很多关于红黑树删除的文章和博客,介绍得是相当相当的复…

C语言内存泄露很严重,如何应对?

点击蓝字关注我们1. 前言最近部门不同产品接连出现内存泄漏导致的网上问题,具体表现为单板在现网运行数月以后,因为内存耗尽而导致单板复位现象。**一方面,内存泄漏问题属于低级错误,此类问题遗漏到现网,影响很坏&…

python发送邮件outlook_通过Python发送Outlook电子邮件?

I am using Outlook 2003. What is the best way to send email (through Outlook 2003) using Python? 解决方案 For a solution that uses outlook see TheoretiCALs answer below. Otherwise, use the smtplib that comes with python. Note that this will require your e…

C++队列queue用法详解(超详细)

点击蓝字关注我们一、定义queue是一种容器转换器模板&#xff0c;调用#include< queue>即可使用队列类。二、queue初始化queue<Type, Container> (<数据类型&#xff0c;容器类型>&#xff09;初始化时必须要有数据类型&#xff0c;容器可省略&#xff0c;省…

python随机抽取人名_python实现艾宾浩斯背单词功能,实现自动提取单词、邮件发送,再也不用担心背单词啦...

&#xfeff;已经完成了利用python爬虫实现定时QQ邮箱推送英文文章&#xff0c;辅助学习英语的项目&#xff0c;索性就一口气利用python多做一些自动化辅助英语学习的项目&#xff0c;对自己的编程能力和英文水评也有一定的帮助&#xff0c;于是在两天的努力下&#xff0c;我完…

用不到125行C语言代码就可以编写一个简单的16位虚拟机?

点击蓝字关注我们一位国外的软件工程师分享了这么一篇博文&#xff1a;Writing a simple 16 bit VM in less than 125 lines of C&#xff08;用不到 125 行 C 语言编写一个简单的 16 位虚拟机&#xff09;。博文地址&#xff1a;https://www.andreinc.net/2021/12/01/writing-…

用一个程序生成另一个程序_还有另一个报告生成器?

用一个程序生成另一个程序如果您具有业务应用程序开发的经验&#xff0c;那么很可能会遇到要求该应用程序具有灵活的报告机制的需求。 我工作的公司主要专注于开发业务解决方案&#xff0c;而报告是必不可少的&#xff0c;实际上&#xff0c;它必须包含我们开发的所有企业系统的…

CocosCreator1.x实现水流动的效果

CocosCreator1.x实现水流动的效果Cocos Creator版本&#xff1a;1.10.2 运行结果&#xff1a;(H5和原生都支持) 场景: 脚本&#xff1a; HelloWorld.js&#xff1a; let shader require(shader);cc.Class({extends: cc.Component,properties: {water: cc.Node,waterNorm…

python爬虫xpath教程_使用 Xpath 进行爬虫开发

使用 Xpath 进行爬虫开发 Xpath( XML Path Language, XML路径语言)&#xff0c;是一种在 XML 数据中查找信息的语言&#xff0c;现在&#xff0c;我们也可以使用它在 HTML 中查找需要的信息。 既然谈到 Xpath 是一门语言&#xff0c;当然它就会有自己的一些特定的语法。我们这里…

用C语言写烟花,给心中的那个人看!

点击蓝字关注我们前言程序员不懂浪漫? 大错特错&#xff01;今天就让你们看看什么是程序员的浪漫&#xff01;你向窗外看烟花&#xff0c;我在窗边看你&#xff0c;这时&#xff0c;你比烟花好看的多&#xff0c;你的眼眸倒映满天的烟火&#xff0c;我的瞳孔倒映你温柔的脸庞…

手把手教你做一个线程池--C语言版

点击蓝字关注我们1、线程池原理我们使用线程的时候就去创建一个线程&#xff0c;这样实现起来非常简便&#xff0c;但是就会有一个问题&#xff1a;如果并发的线程数量很多&#xff0c;并且每个线程都是执行一个时间很短的任务就结束了&#xff0c;这样频繁创建线程就会大大降低…

oracle 48小时内_缺血性脑梗死后48小时内使用阿替普酶能够降低脑损伤程度

一项刊登在影响因子7.6杂志Neurology上题为“Effect of IV alteplase on the ischemic brain lesion at 24–48 hours after ischemic stroke”的研究报告中&#xff0c;来自爱丁堡大学的科学家们发现&#xff0c;alteplase与病变可视性的短期进展降低相关。在荟萃分析中&#…

MySQL夺命16问,你能坚持到第几问?

点击蓝字关注我们1、数据库三大范式是什么&#xff1f;第一范式&#xff1a;每个列都不可以再拆分。第二范式&#xff1a;在第一范式的基础上&#xff0c;非主键列完全依赖于主键&#xff0c;而不能是依赖于主键的一部分。第三范式&#xff1a;在第二范式的基础上&#xff0c;非…