C++程序中执行abort等操作导致没有生成dump文件的问题案例分析

news2024/9/22 9:52:41

目录

1、概述

2、查看C运行时函数abort的内部实现

3、开源库jsoncpp中调用abort的代码场景说明

4、开源库WebRTC中调用abort的代码场景说明

5、项目问题实例分析

5.1、问题说明

5.2、进一步分析

5.3、动态申请内存失败的可能原因分析

6、最后


VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931        C++程序在运行过程中发生了异常闪退,但并没有生成包含异常上下文的dump文件,可能是程序执行了abort等直接程序止当前进程的操作。这类问题我们在实际项目中遇到过若干次,在此给大家做了大致的总结与分享。

1、概述

        C++程序在运行过程中发生了异常闪退,但没有生成包含异常上下文的dump文件,没有dump文件问题也就不太好分析了。一般情况下,我们通过Windbg事后静态分析包含异常上下文的dump文件(dump文件是程序中安装的异常捕获模块感知到程序发生异常时自动生成的)去找线索、去排查的。dump文件是关键切入点,没有了dump文件,问题就很难排查下去的。

        这类异常闪退不生成dump文件的问题,可能是程序中安装的异常捕获模块没捕获到异常,因为异常捕获模块是有一定缺陷的,没法捕获到所有的异常,只能捕捉到大部分情况下的异常。也有可能是程序中执行了abort等直接终止当前进程的操作,我们在实际项目中已经遇到过若干次了,比如jsoncpp库中监测到一些异常时会直接调用abort或exit操作,开源的WebRTC库中在用malloc动态申请内存时失败后会调用abort将进程直接终止运行。今天我们就以jsoncpp和WebRTC开源库为例,来详细讲述这两个库调用abort函数终止进程的问题。

2、查看C运行时函数abort的内部实现

        abort是系统C运行时库中的C函数,可以直接在Visual Studio中go到该函数的实现代码,如下所示:

/***
*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 _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);
}

上述代码中先调用了raise(SIGABRT),该函数是触发一个SIGABRT信号终止异常,如果当前正在调试状态,会让调试器中断下来。接下来调用C函数_exit退出当前进程。

3、开源库jsoncpp中调用abort的代码场景说明

        以jsoncpp中的Value::asInt接口为例,接口代码的实现如下:

我们看case intValue分支,会调用isInt接口判断Value对象中的值是否是int类型的。然后我们走进JSON_ASSERT_MESSAGE宏内部:

如果传入的条件表达式condition为FALSE,则会调用JSON_FAIL_MESSAGE宏,然后再看JSON_FAIL_MESSAGE宏的实现部分,会先调用assert断言,紧接着就会调用abort函数。

        再回到case intValue分支的代码处,如果isInt接口返回FALSE,则会执行到JSON_FAIL_MESSAGE宏中的代码,进而执行abort函数。以上就是jsoncpp内部触发abort函数调用的流程。

4、开源库WebRTC中调用abort的代码场景说明

        以malloc申请动态内存失败的处理代码为例,如下所示:、

上面代码调用malloc申请内存,然后调用宏RTC_CHECK对malloc返回的指针是否为空,于是跳转到RTC_CHECK宏的实现代码:

RTC_CHECK宏中调用了rtc_FatalMessage函数,跳转到该函数的实现处: 

rtc_FatalMessage函数中又调用了FatalLog接口,跳转到实现处:

最终FatalLog接口中先是调用了DebugBreak接口,尝试让当前正在调试的调试器中断下来,然后紧接着调用abort接口将进程强行终止掉。

DebugBreak是系统API函数,是通知当前正在调试的调试器,让调试器中断下来。比如当前正在使用Visual Studio调试代码,或者使用Windbg附加到进程上调试,调试器会中断下来!

5、项目问题实例分析

5.1、问题说明

       有用户反馈,在其笔记本电脑上我们的程序在入会后会频繁地闪退,虽然不是必现的,但复现的概率很大。但在程序指定的目录中并没有找到dump文件,没生成dump文件可能有以下三种原因:

1)程序中的异常捕获模块没捕获到异常;
2)捕获到异常后生成dump文件时又产生了异常(二次异常),导致dump文件没有生成;
3)代码中检测到异常(比如malloc函数申请堆内存失败,返回空指针,并不是抛出异常,代码中检测该函数返回了空指针),直接调用abort或exit退出进程了,导致程序闪退了。

5.2、进一步分析

        对于没有生成dump文件的场景,并且问题较好复现,可以使用Windbg附加到进程上进行动态调试,即Windbg和目标进程一起运行,如果目标进程发生问题,Windbg一般会第一时间感知到并中断下来,然后就可以查看当时的函数调用堆栈去分析了。

        于是远程到用户的机器上,重新运行程序,然后启动Windbg,将Windbg附加到已经运行的程序进程上调试运行。复现问题后,Windbg自动中断了下来,于是用kn命令(也可以使用kv或kp命令)查看到如下的函数调用堆栈:

通过函数调用堆栈,出问题的代码就是上面给出的WebRTC中的相关代码。malloc申请堆内存失败,返回空指针,进入RTC_CHECK宏代码,接着进入rtc_FatalMessage函数中,紧接着又进入了FatalLog接口中。在FatalLog接口先是调用了DebugBreak接口,该接口使得当前正在调试运行的Windbg中断了下来,这就是我们在函数调用堆栈中看到的。

如果我们在Windbg中输入g命令让程序继续运行,Windbg还会中断一下,是因为abort内部抛出了一个SIGABRT信号异常,该异常也会让正在调试的调试器中断下来。

5.3、动态申请内存失败的可能原因分析

        引发本例问题的原因是,调用malloc申请堆内存失败,返回空指针引起的。至于为啥在项目中会出现malloc申请堆内存失败,我们在这里就不再赘述了。此处,我们详细说一下动态申请堆内存失败可能的几个原因:

1)申请的内存过大,进程中没有这么大内存可用了

        可能受一些异常数据的影响,申请了很大尺寸的内存。比如前段时间排查一个崩溃问题,当时因为数据有异常,一次性申请了9999*9999*4*2=762MB的堆内存,进程中没有这么大可用的堆内存了,所以申请失败了,new操作抛出了一个异常,而程序没有对异常处理,直接导致程序崩溃了。

2)用户态的内存已经达到了上限,申请不到内存了

        有可能是虚拟内存占用太多,也有可能代码中有内存泄露,导致用户态的内存快被消耗完了。对于一个32程序,系统会给对应的进程分配4GB的虚拟地址空间,而用户态和内核态内存各占一半,即用户态的内存只有2GB,如果程序占用的虚拟内存比较大,比如接近2GB的用户态虚拟内存了,再申请大的内存就会申请失败,因为已经达到2GB的上限,虚拟内存要耗尽了。或者程序中有内存泄露,快要把用户态的2GB的虚拟内存给占用完了,再申请内存可能会申请失败的。

3)进程中的内存碎片过多

       如果进程中在大量的new和delete,产生了大量的小块内存碎片,可用的内存大多是一小块一小块的小内存块,而要申请的是一块长度很长的内存,因为到处是内存碎片,没有这么一大块连续的可用内存,可能就会导致内存申请失败。

4)发生堆内存越界,导致堆内存被破坏,导致new操作产生异常(此时new不会返回NULL,会抛出异常)

        我们可以在出问题的地方,对该处的new添加一个保护(但不可能对代码中所有new的地方都加这样的保护),我们通过添加try...catch去捕获new抛出的异常,并将异常码打印出来,如下所示:(下面的代码在循环申请内存,直到内存申请失败为止,主要用来测试用)

#include <iostream>
using namespace std;
 
int main(){
    char *p;
    int i = 0;
    try
    {
        do{
            p = new char[10*1024*1024];
            i++;
            
            Sleep(5);
        }
        while(p);
    }
    catch(const std::exception& e)
    {
        std::cout << e.what() << "\n"
                    << "分配了" << i*10 << "M" << std::endl;
 
    }
    
    return 0;   
}

        还有一种方式,在new时传如一个std::nothrow参数,让new在申请不到内存时不要抛出异常,直接返回为NULL,这样我们就可以通过返回的地址是否为NULL(空),判断是否是内存申请失败了,示例代码如下:

#include <iostream>
 
int main(){
    char *p = NULL;
    int i = 0;
    do{
        p = new(std::nothrow) char[10*1024*1024]; // 每次申请10MB
        i++;
        
        Sleep(5);
    }
    while(p);
 
    if(NULL == p){
        std::cout << "分配了 " << (i-1)*10 << " M内存"         //分配了 1890 Mn内存第 1891 次内存分配失败           
                  << "第 " << i << " 次内存分配失败";
 
    }
    return 0;
}

使用C语言中的malloc函数去申请堆内存是不会抛出异常的,申请失败时会返回NULL。对于代码中出现申请堆内存的失败的问题,一般的做法是终止进程的运行,很多开源库中就是这么处理的,比如我们在WebRTC开源库中就看到过。因为内存申请失败,正常的代码逻辑和业务也就没法正常继续下去了,让程序继续运行可能就没多大意义了。

6、最后

        本文详细讲述了调用abort函数强行终止进程导致没有生成dump文件的相关细节及处理办法,这些内容都是通过项目问题实战总结和整理出来的,有一定的参考价值。

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

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

相关文章

计算机网络(第三版) 胡亮 课后习题第二章答案

计算机网络&#xff08;第三版&#xff09; 胡亮 课后习题第二章答案 1、数据通信系统由哪些部分组成&#xff1f; 信源、发送设备、传输设备、接受设备&#xff0c;信宿 2、数据通信应该解决的主要问题有哪些&#xff1f; 提高传输系统的利用率接口&#xff0c;编码和同步交换…

C++类基础(十二)

运算符重载&#xff08;终&#xff09; ● 类型转换运算符 – 函数声明为 operator type() const – 与单参数构造函数一样&#xff0c;都引入了一种类型转换方式 struct Str {Str(int p): val(p){}operator int() const //重载类型转换运算符: 没有显示声明返回类型&#xff…

百趣代谢组学分享,补充α-酮酸的低蛋白饮食对肾脏具有保护作用

文章标题&#xff1a;Reno-Protective Effect of Low Protein Diet Supplemented With α-Ketoacid Through Gut Microbiota and Fecal Metabolism in 5/6 Nephrectomized Mice 发表期刊&#xff1a;Frontiers in Nutrition 影响因子&#xff1a;6.59 作者单位&#xff1a;…

opencv调取摄像头录制

大家好&#xff0c;我是csdn的博主&#xff1a;lqj_本人 这是我的个人博客主页&#xff1a; lqj_本人的博客_CSDN博客-微信小程序,前端,python领域博主lqj_本人擅长微信小程序,前端,python,等方面的知识https://blog.csdn.net/lbcyllqj?spm1011.2415.3001.5343哔哩哔哩欢迎关注…

月薪11k!从财务专员到软件测试工程师,成都校区小哥哥用三个月实现转行换岗

好久没和大家分享学员的转行经历了&#xff0c;或许在一些人看来他们的故事与自己无关&#xff0c;但同样也能引起一些人的共鸣&#xff0c;可以帮助到那些陷于就业焦虑的同学找到目标和方向。相仿的年龄、相同的职业、相似的压力…在转行软件测试追求更好生活的路上&#xff0…

Python - 文件基础操作

目录 文件的读取 open()打开函数 read类型 read()方法 readlines()方法 readline()方法 for循环读取文件行 close() 关闭文件对象 with open 语法 文件的写入 文件的追加 文件的读取 操作 功能 文件对象 open(file, mode, encoding) 打开文件获得文件对象 文件…

C语言学习笔记(六): 探索函数与变量

函数的定义 形参和实参 在定义函数时函数名后面括号中的变量名称为“形式参数”&#xff08;简称“形参”&#xff09;或“虚拟参数”。 在主调函数中调用一个函数时&#xff0c;函数名后面括号中的参数称为“实际参数”&#xff08;简称“实参”&#xff09;。 当函数被调用…

独自开:提供创业机会、享受平台分红、推出新颖赚钱副业

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; 前言 独自开&#xff1a;一款聚焦软件定制开发&#xff0c;独立、自主、开放平台 独创分层标准化平台架构,满足系统不断生长的个性化需求多端一键部署前端业务交互与展…

KMP算法详解

注意&#xff1a;PC阅读效果更佳&#xff0c;建议阅读的同时完成代码实践加深理解一、问题描述指定文本串&#xff1a;aabaabaaf和模式串&#xff1a;aabaaf使用KMP算法判断模式串是否在文本串中出现过&#xff1f;假定模式串的长度小于文本串二、思路解析BF算法的问题是&#…

【pytorch安装】conda安装pytorch无法安装cpu版本(完整解决过程)

问题描述 在安装pytorch过程中&#xff0c;发现最后验证torch时总是返回结果为False&#xff0c;结果翻上去发现自己安装的是cpu版本的。 然后又通过conda去更换不同版本尝试&#xff0c;发现都是cpu版本的。 问题分析 通过conda安装pytorch是从源中搜索匹配指令中的文件&am…

@Validated注解不生效问题汇总

Validated注解不生效问题汇总 文章目录Validated注解不生效问题汇总背景&#xff1a;一&#xff1a;可能原因原因1&#xff1a;原因2&#xff1a;原因3&#xff1a;原因4&#xff1a;二&#xff1a;补充全局异常对validation的处理背景&#xff1a; 项目框架应用的是validatio…

捕鱼大作战协议解密

捕鱼大作战协议解密协/议/流/量/解/密分析捕鱼大作战这款游戏流量的加密方式及解密方法。序捕鱼大作战是tuyoo公司在很多年前上线的一款休闲游戏&#xff0c;对&#xff0c;就是那个之前本号批斗过的途游&#xff0c;这款游戏以海洋深处作为背景&#xff0c;玩家通过在海底施展…

D31 Vue2 + Vue3 K104-K123

D31.Vue F17.打包 图片懒加载&#xff08;K104-K106&#xff09; 1.打包 1&#xff09;vue.config.js module.exports {//打包时不生成map文件(用来进行错误提示的文件&#xff0c;很占用空间)productionSourceMap: false,// 关闭ESLINT校验工具lintOnSave: false, }pnp…

学完Scrapy-Splash秒变爬虫大佬

在做爬虫的时候&#xff0c;大多数的网页中会存在数据动态加载的部分&#xff0c;而且多数都是后期渲染上的。正常情况下爬虫程序仅能爬取被渲染过的数据。因此我们看到的数据也许并非是爬虫直接获取来的。 而scrapy-splash担任了一个中间人的角色&#xff0c;程序通过splash服…

吴思进——复杂美创始人首席执行官

杭州复杂美科技有限公司创始人兼CEO, 本科毕业于浙江大学机械专业&#xff0c;辅修过多门管理课程&#xff1b;1997年获经济学硕士学位&#xff0c;有关对冲基金的毕业论文被评为优秀&#xff1b;2008年创办杭州复杂美科技有限公司。 吴思进 中国电子学会区块链委员会专家&…

计算机网络-基本概念

目录 计算机网络-基本概念 互联网 Java的跨平台原理 ​编辑 C\C的跨平台原理 解释性语言的跨平台原理(python,js等) 客户端 vs 服务器 什么是协议&#xff1f; 网络互连模型 请求过程 计算机之间的通信基础 计算机之间的连接方式-网线直连(需要用交叉线&#xff0c;而…

GIS数据经纬度投影坐标转换总结(涵盖几乎全行业的坐标转换方法)

在处理GIS数据的过程中,避免不了要与坐标和坐标系打交道。这篇文章对能够进行地理坐标转换的所有软件框架做一个一次性总结。 软件类: 1.arcgis arcgis能够进行很全面的很方便的坐标处理,无论是经纬度坐标转投影坐标还是投影坐标转经纬度坐标都非常的简单。arcgis能够对导…

Qt编写视频监控系统70-0SD标签和图形信息(支持写入到文件)

一、前言 作为一个完整的视频监控系统&#xff0c;用户还需要自定义一些OSD标签信息显示在对应通道上面&#xff0c;而且不止一个OSD标签信息&#xff0c;位置可以在四个角或者指定坐标显示。最开始本系统设计的时候&#xff0c;由于本人擅长的是painter绘制&#xff0c;所以直…

MySQL视图特性

文章目录MySQL视图特性基本使用准备测试表创建视图修改视图影响基表修改基表影响视图删除视图视图规则和限制MySQL视图特性 视图的概念 视图是一个虚拟表&#xff0c;其内容由查询定义&#xff0c;同真实的表一样&#xff0c;视图包含一系列带有名称的列和行数据。视图中的数据…

opencv读取摄像头和视频数据

大家好&#xff0c;我是csdn的博主&#xff1a;lqj_本人 这是我的个人博客主页&#xff1a; lqj_本人的博客_CSDN博客-微信小程序,前端,python领域博主lqj_本人擅长微信小程序,前端,python,等方面的知识https://blog.csdn.net/lbcyllqj?spm1011.2415.3001.5343哔哩哔哩欢迎关注…