在Linux中如何解决程序崩溃的问题

一、背景

在Linux上的C/C++环境如何调试程序崩溃问题?通常在这种情况,通过拿到出问题时产生的core文件,然后再利用gdb调试来看到出错时的程序栈信息。但某些特殊的情况,如不正确的系统设置或文件系统出现问题时,导致我们没有拿到core文件,那我们还有补救的办法吗?

二、相关说明

1.函数说明

​ 在Linux上的C/C++编程环境下,我们可以通过如下三个函数来获取程序的调用栈信息。它们由GNU C Library提供,关于它们更详细的介绍可参考Linux Programmer’s Manual(https://man7.org/linux/man-pages/man3/backtrace.3.html)中关于backtrack相关函数的介绍。

#include <execinfo.h>int backtrace(void *buffer[.size], int size);缓冲区(buffer)中将存储一系列活动函数的堆栈帧信息地址,类型为void*。size参数指定可以存储在缓冲区中的最大数量。如果信息数量大于设定的size值,则只会返回最近调用函数的相应信息地址,因此若想获取完整的信息,请确保缓冲区和size足够大。原则上backtrace()的返回值应该小于size,否则说明size设置不足够,有部分信息被截断。char **backtrace_symbols(void *const *array, int size);将backtrace()获取到的 缓冲区(buffer)转换为一个字符串数组。size参数指定缓冲区中的地址数。调用成功,则会返回指向这些字符串的指针;失败,会返回NULLvoid backtrace_symbols_fd(void *const buffer[.size], int size, int fd);backtrace_symbols_fd()和backtrace_symbols()采用相同的缓冲区和大小参数,但它将字符串写入文件描述符fd。

2.注意事项

  • backtrace的实现依赖于栈指针(fp寄存器),在gcc编译过程中任何非零的优化等级(-On参数)或加入了栈指针优化参数-fomit-frame-pointer后多将不能正确得到程序栈信息;
  • backtrace_symbols的实现需要符号名称的支持,在gcc编译过程中需要加入-rdynamic参数;
  • 内联函数没有栈帧,它在编译过程中被展开在调用的位置;
  • 尾调用优化(Tail-call Optimization)将复用当前函数栈,而不再生成新的函数栈,这将导致栈信息不能正确被获取。

3.捕获系统异常信号

​ 当程序出现异常时,通常伴随着会收到一个由内核发过来的异常信号,如当对内存出现非法访问时将收到段错误信号SIGSEGV,然后才退出。利用这一点,当我们在收到异常信号后将程序的调用栈进行输出,它通常是利用signal()函数。

三、从backtrace信息分析定位问题

1、程序样例

​ 为了更好的说明和分析问题,我这里将举例一个小程序,它有三个文件组成分别是backtrace.c、dump.c、test.c。

  • test.c:提供了对空指针的赋值操作,这样人为的造成段错误的发生;
  • dump.c:用于输出backtrace信息;
  • backtrace.c:程序入口main函数,它会先注册段错误信号的处理函数,然后再调用test()函数来触发段错误。

它们的源程序分别如下:

1.1.test.c文件

#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
int test()  
{  int *pTemp = NULL;  *pTemp = 0x01;  /* 这将导致一个段错误,致使程序崩溃退出 */  return (*pTemp);  
}  

1.2.dump.c文件

#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <signal.h>     
#include <execinfo.h>   
void signal_handler(int signo)  
{  printf("\n=========>>>catch signal %d <<<=========\n", signo);  void *buffer[BACKTRACE_SIZE];  int nptrs = backtrace(buffer, BACKTRACE_SIZE);  printf("backtrace() returned %d addresses\n", nptrs);  char **strings = backtrace_symbols(buffer, nptrs);  if (strings == NULL) {  perror("backtrace_symbols");  exit(EXIT_FAILURE);  }  for (int j = 0; j < nptrs; j++){printf("  [%02d] %s\n", j, strings[j]);  }  free(strings);  signal(signo, SIG_DFL); /* 恢复信号默认处理 */  raise(signo);           /* 重新发送信号 */  
}  

1.3.backtrace.c文件

#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <signal.h>       /* for signal */  extern void signal_handler(int signo);  
extern int test();  int main(int argc, char *argv[])  
{  signal(SIGSEGV, signal_handler);  /* 为SIGSEGV信号安装新的处理函数 */  test();  return 0;  
}  

最后为了支持bracktrace,编译指令如下

gcc -g -rdynamic backtrace.c test.c dump.c -o backtrace

2.错误分析

​ 为了更清晰的展示分析过程,将使用实际项目的程序进行演示,也可以自行使用上面的样例进行测试。

2.1.静态链接错误分析

<1>首先,在实际使用的代码中增加如下代码,代码位于msacv.c的第453和454行

char *url=ms_null;
strcpy(url, "123");

<2>编译并执行代码,得到如下的backtrace信息:

 2024-06-17 16:17:52[I][SIGNAL][mssignal_innerapi_backTrace 65]================Catch a signal(11):Segmentation violation2024-06-17 16:17:52[I][SIGNAL][mssignal_innerapi_backTrace 72]backtrace() returned 6 addresses2024-06-17 16:17:52[I][SIGNAL][mssignal_innerapi_backTrace 79][00] /usr/local/msacv/lib/libmscommon.so(+0x59a73) [0x7f97344efa73]2024-06-17 16:17:52[I][SIGNAL][mssignal_innerapi_backTrace 79][01] /lib/x86_64-linux-gnu/libc.so.6(+0x37970) [0x7f9733491970]2024-06-17 16:17:52[I][SIGNAL][mssignal_innerapi_backTrace 79][02] ./out_x8664_msacv_gdb() [0x5d00c1]2024-06-17 16:17:52[I][SIGNAL][mssignal_innerapi_backTrace 79][03] ./out_x8664_msacv_gdb(main+0x1c25) [0x5d23dd]2024-06-17 16:17:52[I][SIGNAL][mssignal_innerapi_backTrace 79][04] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xeb) [0x7f973347e09b]2024-06-17 16:17:52[I][SIGNAL][mssignal_innerapi_backTrace 79][05] ./out_x8664_msacv_gdb(_start+0x2a) [0x40fb2a]

​ 查看以上信息,有用的信息为[SIGNAL][mssignal_innerapi_backTrace 79][02] ./out_x8664_msacv_gdb() [0x5d00c1]

<3>使用addr2line命令获取最终信息: addr2line -e out_x8664_msacv_gdb 0x5d00c1

/home/msos/src/main/msacv.c:454

结果:问题发生在msacv.c文件的第454行,结果符合预期

2.2.动态链接错误分析

<1>首先,在实际使用的代码中增加如下代码,代码位于msptc.c的第1144和1145行

char *url=ms_null;
strcpy(url, "123");

<2>编译并执行代码,得到如下的backtrace信息:

 2024-06-17 16:29:51[I][SIGNAL][mssignal_innerapi_backTrace 65]================Catch a signal(11):Segmentation violation2024-06-17 16:29:51[I][SIGNAL][mssignal_innerapi_backTrace 72]backtrace() returned 9 addresses2024-06-17 16:29:51[I][SIGNAL][mssignal_innerapi_backTrace 79][00] /usr/local/msacv/lib/libmscommon.so(+0x59a73) [0x7f825a8c9a73]2024-06-17 16:29:51[I][SIGNAL][mssignal_innerapi_backTrace 79][01] /lib/x86_64-linux-gnu/libc.so.6(+0x37970) [0x7f825986b970]2024-06-17 16:29:51[I][SIGNAL][mssignal_innerapi_backTrace 79][02] /usr/local/msacv/lib/libptc.so(msptc_api_register+0x199a) [0x7f8259d05553]2024-06-17 16:29:51[I][SIGNAL][mssignal_innerapi_backTrace 79][03] ./out_x8664_msacv_gdb(register_all+0x13) [0x41c5a9]2024-06-17 16:29:51[I][SIGNAL][mssignal_innerapi_backTrace 79][04] ./out_x8664_msacv_gdb() [0x5ce260]2024-06-17 16:29:51[I][SIGNAL][mssignal_innerapi_backTrace 79][05] ./out_x8664_msacv_gdb() [0x5cff70]2024-06-17 16:29:51[I][SIGNAL][mssignal_innerapi_backTrace 79][06] ./out_x8664_msacv_gdb(main+0x1c25) [0x5d23cb]2024-06-17 16:29:51[I][SIGNAL][mssignal_innerapi_backTrace 79][07] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xeb) [0x7f825985809b]2024-06-17 16:29:51[I][SIGNAL][mssignal_innerapi_backTrace 79][08] ./out_x8664_msacv_gdb(_start+0x2a) [0x40fb2a]

​ 查看以上信息,有用的信息为/usr/local/msacv/lib/libptc.so(msptc_api_register+0x199a) [0x7f8259d05553]。

<3>使用addr2line命令获取最终信息: addr2line -e out_x8664_msacv_gdb 0x7f8259d05553

??:0

是不是觉得很莫名其妙?都是个啥。

​ 出现这种情况是由于动态链接库是程序运行时动态加载的,而加载地址每次可能不一样。0x7f8259d05553是一个非常大的地址,也不是一个实际的物理地址,而是经过MMU(内存管理单元)映射过的。

**如何解决?**核心思想是通过map文件将0x7f8259d05553地址转换为实际地址。

2.2.1.通过进程maps文件获取实际地址

<1>在获取backtrace信息时,打印出进程的maps文件信息,如代码:

char buff[64] = {0x00};  
sprintf(buff,"cat /proc/%d/maps > /var/log/msacv_signal", getpid());  
system((const char*) buff);  

<2>由上面的backtrace信息可知道问题发生在libptc.so库中,直接在“/var/log/msacv_signal”文件中搜索libptc.so,就可以得到代码段地址。(如以下地址)

7f8259ceb000-7f8259cee000 rw-p 00000000 00:00 0 
7f8259cee000-7f8259cf5000 r--p 00000000 103:03 3421573                   /usr/local/msacv/lib/libptc.so
7f8259cf5000-7f8259d56000 r-xp 00007000 103:03 3421573                   /usr/local/msacv/lib/libptc.so
7f8259d56000-7f8259d73000 r--p 00068000 103:03 3421573                   /usr/local/msacv/lib/libptc.so
7f8259d73000-7f8259d74000 ---p 00085000 103:03 3421573                   /usr/local/msacv/lib/libptc.so
7f8259d74000-7f8259d78000 r--p 00085000 103:03 3421573                   /usr/local/msacv/lib/libptc.so
7f8259d78000-7f8259d7a000 rw-p 00089000 103:03 3421573                   /usr/local/msacv/lib/libptc.so
7f8259d7a000-7f8259eb5000 rw-p 00000000 00:00 0 

​ 由上面信息可以得到libptc.so库的代码段地址范围为7f8259cee000-7f8259d7a000。而前面得到的0x7f8259d05553也正好在这个区间。我们使用公式“发生问题代码地址”-“代码段起始地址”就可以得到实际的地址。

0x7f8259d05553-0x7f8259cee000=0x17553

<3>使用addr2line命令获取最终信息: addr2line -e /usr/local/msacv/lib/libptc.so 0x17553

/home/msos/extern/src/msptc/src/msptc.c:1145

结果:问题发生在msptc.c文件的第1145行,结果符合预期。

2.2.2.通过add.map文件获取实际地址

<1>编译时,增加-Wl,-Map,add.map选项,如下

<2>Map文件中将包含关于动态库的信息,我们搜索函数名msptc_api_register就可以找到其在.text段的地址为0x15BB9;

<3>结合信息为/usr/local/msacv/lib/libptc.so(msptc_api_register+0x199a) ,将0x15BB9+0x199a=0x17553;

<4>使用addr2line命令获取最终信息: addr2line -e /usr/local/msacv/lib/libptc.so 0x17553

/home/msos/extern/src/msptc/src/msptc.c:1145

结果:问题发生在msptc.c文件的第1145行,结果符合预期。

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

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

相关文章

项目学习---Javaweb(超市订单管理系统)

知识点 MVC模型: 实现步骤:分为3级 1.M(Model) 持久层 代码与数据库进行交互的代码(Mybatis-dao层) 2.C(Control) 控制层 完成某项业务的具体操作过程(Controller层----Service层) 3.V(View) 视图层 一般指用户看到的内容(页面) 项目目录 .filter //过滤器 解决中文字符集…

【绝对有用】c++线程池相关技术点一

1.这段代码是在 C 中创建多个线程并启动它们。让我们逐步解析每个部分&#xff1a; for (size_t i 0; i < threadCount; i) {: • 这是一个 for 循环&#xff0c;从 i 0 开始&#xff0c;一直执行到 i 小于 threadCount 的时候。i 表示每次循环后将 i 加 1。threads.empl…

4、多分支判断 - 课件

一、基础知识 多分支判断的基本语法可以表示为: if (判断条件1) {// 如果判断条件1为真,执行这里的代码 } else if (判断条件2) {// 如果判断条件1为假且判断条件2为真,执行这里的代码 } else if (判断条件3) {// 如果判断条件1和判断条件2都为假且判断条件3为真,执行这里的代…

眼动研究实验设计方法

摘要 本文对基于实验室的眼动实验设计进行了总体回顾&#xff0c;并侧重于回顾实验程序和方法&#xff0c;从而为眼动追踪实验提供一个框架或背景。本文内容涵盖了基本的实验设计&#xff0c;这与实验心理学课本没有太大的区别&#xff0c;其中析因设计在眼动追踪研究中特别受…

day02 CSS基础

目录 CSS介绍 CSS使用方式 内联方式 内部样式表 外部样式表 CSS特性 优先级 选择器 元素选择器 id选择器 类选择器 派生选择器 子选择器 属性选择器 首个子元素 指定元素选择器 伪类选择器 文本 字体 列表 表格 背景 鼠标 border 宽高 box模型 元素水…

SpringCloudAlibaba组件集成

SpringCloudAlibaba组件集成 Nacos服务注册与发现 1.Nacos认识与安装 1.1.什么是Nacos Nacos和Eureka有着相同的能力&#xff0c;甚至更为强大&#xff0c;作为Dubbo 生态系统中重要的注册中心实现。官方对它有如下定义&#xff1a; Nacos致力于帮助您发现&#xff0c;配置…

查看mysql数据库端口号

在MySQL中&#xff0c;可以通过多种方式查看数据库服务器的端口号。以下是一些常见的方法&#xff1a; 1. 使用MySQL配置文件 MySQL服务器的端口号通常在配置文件中设置。在Linux系统上&#xff0c;这个配置文件通常是/etc/my.cnf、/etc/mysql/my.cnf&#xff0c;或者在/etc/…

Java+Angular+Nginx+RESTful API 医院云HIS系统源码 全国中小型诊所都在用的诊所his系统门诊业务流程 自主版权

JavaAngularNginxRESTful API 医院云HIS系统源码 全国中小型诊所都在用的诊所his系统门诊业务流程 自主版权 HIS系统&#xff08;Hospital Information System&#xff09;在门诊业务中的应用带来了许多显著的优势&#xff0c;这些优势不仅提高了医疗服务的质量和效率&#xf…

centos8 安装python3、pip、pyinstall

centos8安装python3 安装python3&#xff1a;https://www.cnblogs.com/qq931399960/p/11664594.html bash # 步骤&#xff1a;1、输入python3看有没有 # 2、使用yum -y install python 安装 # 3、指定版本安装&#xff1a;yum -y install python39 [rootlocalhost ~]# python…

Zookeeper:启动占用8080端口

zookeeper最近的版本中有个内嵌的管理控制台是通过jetty启动&#xff0c;也会占用8080 端口。 通过查看zookeeper的官方文档&#xff0c;发现有3种解决途径&#xff1a; &#xff08;1&#xff09;.删除jetty。 &#xff08;2&#xff09;修改端口。 一种是在启动脚本中增加…

【linux】操作系统使用wget下载网络文件,内核tcpv4部分运行日志

打印日志代码及运行日志(多余日志被删除了些)&#xff1a; 登录 - Gitee.comhttps://gitee.com/r77683962/linux-6.9.0/commit/55a53caa06c1472398fac30113c9731cb9e3b482 测试步骤和手段&#xff1a; 1、清空 kern.log&#xff1b; 2、使用wget 下载linux-6.9.tar.gz&…

切割游戏介绍

简介 上大学时&#xff0c;在学校实验室里玩过一个貌似使用VC写的小游戏&#xff0c;一个小球在界面上四处游荡&#xff0c;玩家使用鼠标切割背景&#xff0c;将背景切割剩余到一定的百分比后&#xff0c;就胜利了&#xff0c;后边的背景图会全部展示出来。 使用qt的qml技术&a…

初始化一个Android项目时,Android Studio会自动生成一些文件和目录结构,以帮助你快速上手开发

当你初始化一个Android项目时&#xff0c;Android Studio会自动生成一些文件和目录结构&#xff0c;以帮助你快速上手开发。这些文件和目录各自有其特定的功能和用途。下面我为你解释一下这些自动生成的内容&#xff1a; 1. app 目录 这是你的应用模块的根目录&#xff0c;包…

django学习入门系列之第二点《浏览器能识别的标签1》

文章目录 文件的编码(head)网站表头信息(head)标题&#xff08;body&#xff09;div和span往期回顾 文件的编码(head) <!--浏览器会以"UTF-8"这种编码来读取文件--> <meta charset"UTF-8">网站表头信息(head) <title>Title</title&…

使用 Iceberg、Tabular 和 MinIO 构建现代数据架构

现代数据环境需要一种新型的基础架构&#xff0c;即无缝集成结构化和非结构化数据、轻松扩展并支持高效的 AI/ML 工作负载的基础架构。这就是现代数据湖的用武之地&#xff0c;它为您的所有数据需求提供了一个中心枢纽。然而&#xff0c;构建和管理有效的数据湖可能很复杂。 这…

为何选择企业微信作为私域运营的首选工具?

随着数字化营销的不断深入&#xff0c;私域流量的价值愈发凸显。在众多的私域运营工具中&#xff0c;企业微信凭借其独特的功能和优势&#xff0c;逐渐成为了众多企业的首选。本文将详细阐述为何选择企业微信作为私域运营的首选工具&#xff0c;并从多个维度进行对比分析。 一…

总台,地方卫视媒体邀约新闻报道采访怎么做?

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 总台对选题要求非常严格&#xff0c;在想做总台新闻报道之前&#xff0c;让我们先来了解下总台对新闻选题有哪些要求&#xff1a; 一、新闻价值 社会意义&#xff1a;新闻报道的首要任务…

【CHIP】LTC2991 读取温度电压电流 调试实例

文章目录 0. ENV1. LTC2991 数据说明1. 数据计算公式2. 寄存器概述1. 管脚使能寄存器2. 芯片使能寄存器 2. 软件实现1. 概述2. 源码(部分)3. 参考log 0. ENV 软件系统&#xff1a;略 LTC2991&#xff1a;VCC3.3 温度&#xff1a;温控接v1-v2 / v2-v3 / … (双端采样)电压&#…

C#面:C# 类的执行顺序?

C# 类的执行顺序可以分为以下几个步骤&#xff1a; 静态字段初始化&#xff1a;在类的第一次使用之前&#xff0c;静态字段会被初始化。静态字段的初始化顺序是按照它们在代码中的声明顺序进行的。静态构造函数&#xff1a;如果类中定义了静态构造函数&#xff0c;它会在类的第…

QT工作笔记

文章目录 QDialog的accept()和reject()介绍QPushButton提示属性样式表QComboBox QDialog的accept()和reject()介绍 accept() 和reject() 这两个槽函数都会和close() 一样关闭dialogaccept() 关闭后 返回了Dialog::Acceptedreject() 关闭后 返回了Dialog::Rejected这样当我们需…