基于开源CrashRpt与微软开源Detours技术深度改造的异常捕获库分享

目录

1、异常捕获模块概述

2、为什么需要异常捕获模块?

3、在有些异常的场景下是没有生成dump文件的

4、开源异常捕获库CrashRpt介绍

5、对开源库CrashRpt的改进


C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/125529931C/C++基础与进阶(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_11931267.htmlVC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/124272585C++软件分析工具从入门到精通案例集锦(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/131405795开源组件及数据库技术(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_12458859.html网络编程与网络问题分享(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_2276111.html       最近有很多朋友问到异常捕获机制及开源异常捕获库的问题,今天有时间,就给大家说说异常捕获库相关的内容

1、异常捕获模块概述

       异常捕获模块是内置在程序中的,当程序发生异常,异常捕获模块感知(捕获)到,自动生成包含异常上下文的dump,文件,可能同时也会生成程序版本等信息的日志文件。事后,我们可以取来这些日志及dump文件去分析软件发生异常崩溃的原因。

       异常捕获模块一般是以函数回调的方式通知给主程序,此时主程序可以弹出相关的提示框,提示用户程序发生了异常崩溃,用户可以重新运行程序。程序中内置异常捕获模块,已经成为一种标配,大部分程序都会自带异常捕获模块。以PC版的微信为例,当微信发生崩溃时,其内置的异常捕获模块会捕获到异常并生成日志及dump文件,同时会弹出如下的发送错误报告的提示框:

提示框的下方会自动带上崩溃相关的文件,其中最后一个文件就是包含异常上下文信息的dump文件,点击确定则会将这些文件发送到腾讯远端的后台服务器上。腾讯后台的运维人员会收到通知,然后到服务器上将dump等文件下载下去分析。

       有些软件可能没有上传崩溃日志到服务器的功能,捕捉到异常时会自动将dump文件保存到指定的路径中,事后可以到该路径中取到对应的dump文件。如果客户机器上遇到崩溃,可以和客户联系,让客户帮忙从对应的路径中取来出dump文件发过去。

2、为什么需要异常捕获模块?

       在日常项目开发维护的过程中,会遇到这样那样的异常崩溃,分析这些异常崩溃最常用、最重要的途径就是事后分析异常发生时生成的dump文件。

       要分析软件异常崩溃,首先要有dump文件,那包含异常上下文信息的dump文件是如何生成的呢?以前我们讲过生成dump文件的三种方式:

1)通过内置的异常捕获模块,去捕获软件发生的异常崩溃,自动生成包含异常上下文的dump文件。这是生成dump文件最常用的方式。
2)可以在动态调试的Windbg中,使用.dump命令手动导出包含异常上下文信息的dump文件。为啥要导出dump文件呢?既然Windbg在动态调试,为啥不能直接分析呢?在条件允许的情况下可以直接在Windbg中分析,但一时半会分析不出来问题时或者不能长时间占用用户的电脑时,就需要导出dump文件,供事后分析了。
3)可以从Windows任务管理中导出dump文件。在任务管理器的进程列表中找到目标进程,右键点击,在弹出的右键菜单中点击导出转储文件即可。这种方式首先要求出问题的程序进程还存活着的(进程还在的),比如程序发生卡死、死循环或者程序弹出报错提示框时(弹出报错提示框时,如果不点击提示框中的按钮将提示框关闭掉,则程序进程还在的)。

其中,通过异常捕获模块去生成dump文件,是最常用的一种方式。事后分析dump文件,也是我们分析和排查软件异常崩溃问题最常用的方法。

       关于dump文件及生成方式的详细说明,可以查看我之前写的文章:

dump文件类型与dump文件生成方法详解icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/127991002       有人可能会说,遇到问题,我直接使用IDE去调试不就行了,我不需要什么dump文件的!如果这样说,只能说其处理异常问题的经验太有限了!因为有些问题可能是很难复现的,或者问题只在客户的环境中复现,这样也就无从去调试代码了。再就是程序中有多个模块,不同的模块由不同的开发组负责,如果程序崩溃在底层的模块中,还需要底层去调试代码。底层模块的开发人员需要搭建调试环境,然后使用附加到进程的方式去调试代码。

       如果在程序中安装了异常捕获模块,在程序发生异常时自动生成包含异常上下文的dump文件,我们直接将dump文件取来分析就好了,就不用老想着如何去复现问题了。直接使用Windbg静态分析dump文件,查看发生异常的那条汇编指令以及发生异常的函数调用堆栈,一般就能快速地定位问题了,可以有效地提高我们排查问题和解决问题的效率。

当然,并不是说有了dump文件,就一定不需要复现问题了。在个别情况下,通过分析dump文件很难排查出问题时,可以尝试去复现问题(找到复现问题的操作规律),找到问题发生的场景,这样可能去辅助分析问题的。所以有时候,为了排查分析问题,我们需要使用多种手段与方法的!


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

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

C++软件调试与异常排查从入门到精通系列文章汇总icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/125529931

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

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

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

专栏2: 

C/C++基础与进阶(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_11931267.html

以多年的开发实战为基础,总结并讲解一些的C/C++基础与进阶内容,以图文并茂的方式对相关知识点进行详细地展开与阐述!专栏涉及了C/C++领域的多个方面的内容,同时给出C/C++及网络方面的常见笔试面试题,并详细讲述Visual Studio常用调试手段与技巧!

专栏3: 

开源组件及数据库技术icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_12458859.html

以多年的开发实战为基础,分享一些开源组件及数据库技术!


3、在有些异常的场景下是没有生成dump文件的

       即便程序中安装了异常捕获模块,在某些异常的场景下,可能也是没有生成dump文件的。这里面是有多种原因的。

       首先,异常捕获模块不是万能的,只能捕捉(感知)到大部分的异常,但不能捕获所有的异常,这和异常捕获模块的捕获机制的缺陷有关。其次,可能在生成dump文件时产生了二次崩溃,生成的dump文件是空的。对于没有生成dump文件的场景,就只能尝试Windbg的动态调试了,将Windbg附加到目标进程上,和目标进程一起跑,一旦程序发生异常,作为正在调试的调试器Windbg,会第一时间感知到并中断下来,此时去查看函数调用堆栈去分析就好了。

       再者就是,程序只是发生了闪退,但程序没有发生异常崩溃。比如我们在项目中遇到的两个典型的场景,一个是在用开源库jsoncpp时错误地解析json串中的字段类型时,jsoncpp发现类型错误,就直接认为是致命的,直接调用abort接口将进程终止了。还有一个场景是,我们在使用开源库WebRTC时,当调用malloc动态申请内存失败时(可能是程序内存不足导致的申请失败),malloc返回NULL,WebRTC认为内存申请不到了,业务没法正常运行下去,认为这是致命的,程序没有继续活着的意义了,直接调用abort强行将进程终止了。

       对于这两种强行终止进程的场景,并没有发生C++上的异常,只是代码人为地终止了进程。从现象上看,程序直接闪退了(没了),给人一种程序发生异常崩溃导致程序闪退的感觉,其实程序并没有发生异常崩溃,是代码人为终止进程的。对于这种强行终止进程的场景,就需要使用Windbg动态调试了,将Windbg附加到目标进程上,和目标进程一起跑,C函数abort内部会产生一个特殊的异常,让让调试器Windbg中断下来,此时查看函数调用堆栈,就知道是调用了abort强行终止进程的,然后继续结合函数调用堆栈去分析问题了。

       对于使用开源jsoncpp和开源WebRTC遇到的强行终止进程的问题,具体细节可以去查看我之前写的文章:

C++程序中执行abort等操作导致没有生成dump文件的问题案例分析icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/129003869       此外,有时即使拿到了dump文件,可能也需要用Windbg进行动态调试的。比如从现有dump文件中分析不出问题时或者看到的函数调用堆栈不全或有问题,可以尝试使用Windbg进行动态调试。再比如,以前我们讲过,有时可能要查看相关变量的值,这些变量的值可能是排查问题的关键线索,但异常捕获模块生成的dump文件是小dump文件(比如几百KB或几MB,只保存了小部分内存信息),只能部分变量的值,有些变量的值是看不到的,但这些变量可能是分析问题的关键线索,这时也可以尝试使用Windbg进行动态调试,动态调试时可以看到所有变量的值。

       对于通过查看相关变量值去快速定位问题的案例,可以查看我的文章:
通过查看Windbg中变量值去定位C++软件异常问题icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/125731044       对于很好复现的问题,使用Windbg动态调试很方便的,将Windbg挂到目标进程上,快速复现问题,Windbg感知到异常中断下来,就可以立即分析了。对于不好复现或者很难复现的问题,只能每次启动程序测试时都将Windbg挂上去,等待复现为止。

      关于何时使用Windbg静态分析与动态调试,可以查看我的文章:

何时使用Windbg静态分析?何时使用Windbg动态调试?icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/131806819

4、开源异常捕获库CrashRpt介绍

       如何去设计异常捕获库呢?大家都会想到使用系统API函数SetUnhandledExceptionFilter去设置异常回调,当程序发生异常时就会调用设置的回调函数,然后在回调函数中调用API函数MiniDumpWriteDump去生成dump文件。但使用这种方式,缺陷很明显,很多异常捕获不到,可以选择开源的异常捕获库。

       常见的开源捕获库有CrashRpt和Google开源的CrashPad,今天我们来重点介绍一下CrashRpt。CrashRpt 是一个免费的、轻量级的开源错误报告库开源库,旨在拦截C++程序中的异常,收集有关崩溃的技术信息并通过互联网向软件供应商发送错误报告,用于 Windows C++应用程序中。

1)CrashRpt 可以处理主线程和用户模式程序的所有工作线程中的异常:SEH 异常(Structured Exception Handling 结构化异常处理)、未处理C++类型异常、信号和 CRT (C运行时)错误。在 CrashRpt 可以处理的错误类型中,有:NULL 指针分配、访问冲突、无限递归、堆栈溢出、内存耗尽等。
2)CrashRpt能生成错误报告,包括小型崩溃转储minidump文件、可扩展的崩溃描述XML文件、应用程序自定义的文件(如程序日志文件)等。

5、对开源库CrashRpt的改进

       CrashRpt对多线程支持的不好,导致很多异常捕获不到。另外,CrashRpt在捕获到异常时,会自动自动截屏、录制视频和发送邮件,这些功能其实是什么用处的,直接把它们裁剪掉。对于发送邮件,CrashRpt中是直接使用socket实现的,其稳定性不太好,如果需要用到这个功能,可以使用开源库libcurl,libcurl中支持POP3和SMTP邮件协议。

       为了解决CrashRpt很多异常捕获不到的问题,我们首先添加了对多线程的支持,然后引入了微软的Detours技术,有效地提高了异常捕获的效率,基本可以捕获90%以上的异常。捕获到异常后,就会生成dump文件,事后使用Windbg去分析dump文件即可。对于没捕捉到的异常的场景,则需要使用使用Windbg进行动态调试了,上面这些场景我也详细讲到了。

如果要获取到该改进的异常捕获库,可以联系微信kvsthinking

       关于如何使用Windbg去静态分析dump文件,可以查看我的文章:
使用Windbg分析dump文件的一般步骤及要点详解icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/130873143      关于如何使用Windbg去动态调试目标进程,可以查看我的文章:

使用Windbg调试目标进程的一般步骤及要点详解icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/135484906

  • 140
    点赞
  • 125
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 102
    评论
在C++中,动态链接(Dynamic Link Library, DLL)崩溃通常指的是由于运行时错误,如内存泄漏、未初始化的指针引用、除以零错误或其他程序错误导致的程序终止。当动态中的代码抛出异常,如果没有得到适当的处理,程序就会崩溃。 捕获异常(Exception Handling)是C++提供的一种机制,用于处理运行时错误。在遇到异常时,程序不会立即终止,而是跳转到预先设置的异常处理代码块(try-catch-finally)。以下是异常处理的基本步骤: 1. **try**块:包含可能抛出异常的代码。如果在这个块内发生异常,程序控制权将立即转移到对应的catch块。 2. **catch**块:定义了异常类型,试图捕获特定类型的异常。如果try块中的异常类型匹配catch块中的异常类型,控制权会进入该块执行相应的错误处理代码。 3. **throw**语句:用于手动引发异常。在程序中,可以使用throw表达式来抛出一个自定义的异常对象。 4. **catch(...)**:这是泛型的catch块,用于捕获所有未被其他catch块匹配的异常。通常用在没有特定处理的情况下,或者是为了记录详细的错误信息。 5. **finally**块(可选):无论是否发生异常,finally块中的代码都会被执行,通常用于资源清理操作。 相关问题: 1. C++中常见的异常类型有哪些? 2. 如何在catch块中处理异常,比如记录日志或恢复部分功能? 3. 如何避免动态崩溃,特别是当调用第三方时? 4. C++中如何自定义异常类? 5. 动态链接崩溃后,如何调试和追踪错误源?

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 102
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dvlinker

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值