如何使用backtrace定位Linux程序的崩溃位置

在嵌入式Linux开发中,特别是复杂软件,多人协作开发时,当某人无意间写了一个代码bug导致程序崩溃,但又不知道崩溃的具体位置时,单纯靠走读代码,很难快速的定位问题。

本篇就来介绍一种方法,使用backtrace工具,来辅助定位程序崩溃的位置信息。

backtrace是 C/C++ 中用于获取程序调用栈信息的函数,借助backtrace可以排查崩溃并定位代码行号。

1 backtrace分析程序崩溃的原理

在linux系统中,运行程序若发生崩溃,会产生相应的信号,例如访问空指针会触发SIGSEGV(signum:11)。

这时可以使用signal函数来捕获这个信息,捕获信号后,支持自定义的handler函数进行一些处理。

在自定义的handler函数中,可以使用backtrace函数,来打印程序调用栈信息

最后使用addr2line函数,将地址转换为可读的函数名和行号

使用backtrace分析程序崩溃,需要在编译时使用 -g 选项生成的调试信息。

使用addr2line工具,将地址转换为可读的函数名和行号,实例如下:

addr2line -e 程序名 -f -C 0x400526
# 输出:
main
/path/to/main.c:42

2 一些要用到的函数

2.1 signal

2.1.1 函数原型

在 C 和 C++ 中,signal 函数用于设置信号处理方式。

其原型定义在 <signal.h> 头文件中:

typedef void (*sighandler_t)(int);sighandler_t signal(int signum, sighandler_t handler);

参数说明:

  • int signum:信号编号(整数),如:
    • SIGINT(2):中断信号(Ctrl+C)
    • SIGSEGV(11):段错误
    • SIGILL(4):非法指令
    • SIGTERM(15):终止信号
    • SIGFPE(8):浮点异常
  • sighandler_t handler:信号处理函数指针,有三种取值:
    • 用户定义函数void handler(int signum) 类型的函数
    • SIG_DFL:默认处理(如终止程序)
    • SIG_IGN:忽略该信号

返回值:

  • 成功:返回之前的信号处理函数指针
  • 失败:返回 SIG_ERR,并设置 errno(如 EINVAL 表示无效信号)

2.1.2 常见信号列表

signum信号名称默认行为触发场景
1SIGHUP终止程序终端连接断开(如 SSH 会话结束),或用户登出时通知进程重新加载配置
2SIGINT终止程序(Ctrl+C)用户在终端按下 Ctrl+C,请求中断当前进程
3SIGQUIT终止程序并生成 Core 文件用户按下 Ctrl+\,通常用于强制退出并生成调试用的 Core 文件
4SIGILL终止程序并生成 Core 文件进程执行非法指令(如无效的机器码),通常由程序编译错误或硬件异常导致
5SIGTRAP终止程序并生成 Core 文件触发断点陷阱(如调试器设置的断点),用于程序调试时的中断
6SIGABRT终止程序并生成 Core 文件通常是由进程自身调用 C标准函数库 的 abort() 函数来触发
7SIGBUS终止程序并生成 Core 文件硬件总线错误(如访问未对齐的内存地址,或内存映射文件错误)
8SIGFPE终止程序并生成 Core 文件发生算术错误(如除零、溢出、精度错误),例如1/0运算
9SIGKILL强制终止程序(不可捕获)系统或用户发送kill -9命令,用于强制终止无响应的进程,无法被忽略或处理
10SIGUSR1终止程序用户自定义信号 1,可由程序自定义处理逻辑(如日志刷新、状态通知)
11SIGSEGV终止程序并生成 Core 文件访问无效内存地址(如空指针解引用、越界访问),是最常见的程序崩溃原因之一
12SIGUSR2终止程序用户自定义信号 2,用途与SIGUSR1类似,供程序开发者自由定义功能
13SIGPIPE终止程序向已关闭的管道或套接字写入数据(如 TCP 连接断开后继续发送数据)
14SIGALRM终止程序定时器超时(由alarm()setitimer()函数触发),用于超时控制
15SIGTERM终止程序(可捕获)系统或用户发送kill命令(默认),请求进程正常退出,程序可自定义处理逻辑
16SIGSTKFLT终止程序栈溢出错误(仅在某些架构上存在,如 x86),通常与硬件相关的栈异常有关
17SIGCHLD忽略信号子进程状态改变(如终止或暂停),父进程可通过wait()系列函数获取子进程信息
18SIGCONT继续运行暂停的进程当进程被暂停(如SIGSTOP)后,用于恢复其执行,默认行为为继续运行
19SIGSTOP暂停进程(不可捕获)系统或用户发送kill -STOP命令,用于暂停进程执行,无法被忽略或处理

信号分类

  • 不可捕获信号:无法通过signalsigaction修改处理方式,只能由系统强制控制。
    • SIGKILL(9)
    • SIGSTOP(19)
  • 用户自定义信号:可由程序自由定义处理逻辑,常用于进程间通信或调试。
    • SIGUSR1(10)
    • SIGUSR2(12)
  • 异常信号:通常由程序错误(如内存操作异常)触发,默认会生成 Core 文件用于调试。
    • SIGBUS(7)
    • SIGSEGV(11)

默认行为的差异

  • 多数信号的默认行为是终止程序,但部分信号(如SIGCHLD)默认会被忽略,而SIGCONT则用于恢复进程运行。

2.2 backtrace

在 C 和 C++ 中,backtrace 函数用于获取当前程序的调用堆栈信息,常用于调试和错误处理。

其原型定义在 <execinfo.h> 头文件中:

/* 获取当前调用堆栈中的函数地址 */
int backtrace(void **buffer, int size);
  • 参数
    • void **buffer:指向存储函数地址的数组的指针。
    • int size:数组的最大元素数(即最多获取的堆栈帧数)。
  • 返回值
    • 成功:返回实际获取的堆栈帧数(不超过 size)。
    • 失败:返回 0(极罕见,通常仅在内存不足时发生)。

2.3 backtrace_symbols

/* 将函数地址转换为可读的字符串(如函数名、偏移量) */
char **backtrace_symbols(void *const *buffer, int size);
  • 参数
    • void *const *buffer:backtrace返回的函数地址数组
    • int size:backtrace返回的实际帧数
  • 返回值
    • 成功:返回指向字符串数组的指针,每个元素对应一个堆栈帧(需用 free() 释放)
    • 失败:返回 NULL,并设置 errno

2.4 backtrace_symbols_fd

/* 将函数地址直接输出到文件 */
void backtrace_symbols_fd(void *const *buffer, int size, int fd);
  • 参数
    • void *const *buffer:同 backtrace_symbols
    • int size:同 backtrace_symbols
    • int fd:文件描述符(如 STDERR_FILENO),用于输出结果
  • 返回值:无(直接输出到文件)

3 实例代码

3.1 主函数

//g++ -g test.cpp -o test
#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <csignal>
#include <string.h>
#include <fcntl.h>
#include <vector>//<---信号处理函数添加到这里void TestFun()
{printf("[%s] in\n", __func__);std::vector<int> a;printf("[%s] a[1]=%d\n", __func__, a[1]);
}int main()
{std::vector<int> vSignalType = {SIGILL, SIGSEGV, SIGABRT};                             for (int &signalType : vSignalType){if (SIG_ERR == signal(signalType, SignalHandler)){printf("[%s] signal for signalType:%d err\n", __func__, signalType);}}TestFun();return 0;
}

3.2 信号处理函数

#define MAX_STACK_FRAMES 100void SignalHandler(int signum)
{printf("[%s] signum:%d(%s)\n", __func__, signum, strsignal(signum));signal(signum, SIG_DFL); //恢复默认行为// [backtrace] 获取当前调用堆栈中的函数地址void *buffer[MAX_STACK_FRAMES];size_t size = backtrace(buffer, MAX_STACK_FRAMES);printf("[%s] backtrace() return %zu address. Stack trace:\n", __func__, size);// [backtrace_symbols] 将函数地址转换为可读的字符串char **symbols = (char **) backtrace_symbols(buffer, size);if (symbols == NULL) {printf("[%s] backtrace_symbols() null\n", __func__);return;}for (size_t i = 0; i < size; ++i){printf("#%d %s\n", (int)i, symbols[i]); //打印每一个函数地址}free(symbols);// [backtrace_symbols_fd] 将函数地址直接输出到文件int fd = open("backtrace.txt", O_CREAT | O_WRONLY, S_IRWXU | S_IRWXG | S_IRWXO);if (fd >= 0){backtrace_symbols_fd(buffer, size, fd);close(fd);}
}

3.3 addr2line解析backtrace信息

#!/bin/shif [ $# -lt 2 ]; thenecho "example: myaddr2line.sh test backtrace.log"exit 1
fiBIN_FILE=$1
BACK_TRACE_FILE=$2lines=$(cat $BACK_TRACE_FILE | grep ${BIN_FILE})
for line in ${lines}; doaddr=$(echo $line | awk -F '(' '{print $2}' | awk -F ')' '{print $1}')addr2line -e ${BIN_FILE} -C -f $addr
done

addr2line 是一个用于将程序地址(如内存地址)转换为源代码位置(文件名和行号)的工具。以下是其常用参数的详细含义:

参数含义说明
-e--exe=FILE指定要分析的可执行文件或共享库(必选参数)。
-p--pretty-print以更易读的格式输出信息(如添加换行和缩进)。
-C--demangle[=style]还原 C++ 符号名(如将 _Z3foov 转换为 foo())。
-i--inlines显示内联函数的调用信息(包括原始函数和内联位置)。
-f--functions显示函数名(默认仅显示地址对应的行号)。

3.4 测试结果

可以看到,定位到了test.cpp的50行为崩溃的位置,代码中的vector a没有赋值,直接访问vector[1]将会崩溃。

具体的调用栈关系为:

  • main函数,test.cpp的65行:调用的TestFun函数
  • TestFun函数,test.cpp的50行:执行的printf("[%s] a[1]=%d\n", __func__, a[1]);
  • SignalHandler函数,test.cpp的20行:崩溃触发的SIGSEGV信号被捕获后,在SignalHandler函数中的backtrace被处理

SignalHandler函数中,通过backtrace_symbols打印的信息,与通过backtrace_symbols_fd保存在backtrace.txt文件中的信息,其实是一样的:

使用myaddr2line.sh脚本,可以方便打印所有的行号信息。

当然也可以手动使用addr2line来打印行号信息,只是效率较低。

另外,注意backtrace的地址,圆括号 ()方括号 [] 中的地址具有不同含义,分别对应 符号表中的函数地址实际执行地址

  • 圆括号 (...) 中的地址

    • 含义:函数内部的 相对偏移量(相对于函数起始地址)
    • 格式函数名+0x偏移量
    • 作用:指示崩溃发生在该函数的具体位置。
  • 方括号 [...] 中的地址

    • 含义:指令在 内存中的实际地址(绝对地址)
    • 格式0xXXXXXXXX
    • 作用:可直接用于 addr2line 等工具定位源代码

但在本示例程序测试中,却要使用圆括号中的地址,addr2line才能显示行号,这里有待再研究。

4 总结

本篇介绍了如何使用backtrace工具来定位Linux应用程序崩溃的位置信息,首先通过signal捕获崩溃信息,然后通过backtrace记录崩溃时的堆栈调用信息,最后使用addr2line来显示对应的崩溃时的代码行号。

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

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

相关文章

十大排序算法汇总

好的&#xff0c;下面为你整理一篇面试全覆盖、极其深入的十大排序算法总结博客&#xff0c;涵盖算法原理、复杂度、稳定性、应用场景、工程实践、C与Python实现&#xff08;含详细注释&#xff09;&#xff0c;并对比分析各种排序的优缺点与适用情境。内容力求结构清晰、讲解透…

零基础 “入坑” Java--- 七、数组(二)

文章目录 一、数组转字符串二、数组的拷贝三、求数组中元素的平均值四、查找数组中指定元素&#xff08;顺序查找&#xff09;五、数组排序&#xff08;冒泡排序&#xff09;六、查找数组中指定元素&#xff08;二分查找&#xff09;七、判断两个数组中的元素是否相等八、填充数…

【C++ 真题】P1104 生日

P1104 生日 题目描述 cjf 君想调查学校 OI 组每个同学的生日&#xff0c;并按照年龄从大到小的顺序排序。但 cjf 君最近作业很多&#xff0c;没有时间&#xff0c;所以请你帮她排序。 输入格式 输入共有 n 1 n 1 n1 行&#xff0c; 第 1 1 1 行为 OI 组总人数 n n n&…

Oracle DB和PostgreSQL,OpenGauss主外键一致性的区别

针对于unique索引在主外键上的表现&#xff0c;o和PG的行为确实不一致&#xff0c;测试样例&#xff1a;PG:测试1&#xff1a;test# CREATE TABLE gdb_editingtemplates ( objectid INTEGER NOT NULL, globalid VARCHAR(38) DEFAULT {00000000-0000-0000-0000-000000000000} …

06.自动化测试概念

自动化测试概念 1. 自动化1.1 回归测试1.2 自动化分类 1.3 自动化测试金字塔2. web自动化测试3.Selenium 1. 自动化 ​ **自动化测试&#xff08;Automated Testing&#xff09;&#xff1a;**是指使用软件工具或脚本来自动执行测试任务&#xff0c;代替人工进行重复性、繁琐的…

页面登录数据的加密(前端+后端)

本加密过程使用的 AESRSA概要1.使用AES对传输数据进行加密AES为对称加密,加密和解决所需要的key是一样的,所以拦截到AES key就可以直接解密,所以需要结果RSA进行加密2.对AES的key进行RSA加密RSA为非对称加密,客户端只能获取到publicKey(公钥),而解密只能使用服务器的privateKey…

PC端基于SpringBoot架构控制无人机(一):初识无人机控制

一、无人机飞控系统的概述飞控&#xff08;Flight Controller&#xff09;是无人机最为核心的组成部分之一&#xff0c;负责实现无人机的自主飞行控制和稳定飞行。飞控系统的功能决定了无人机的飞行性能&#xff0c;包括飞行的稳定性、操控的响应速度、导航的精确度等。通过飞控…

QT6 源(154)模型视图架构里的列表视图 QListView:先学习属性部分,

&#xff08;1&#xff09;属性总图&#xff0c;以及测试程序的框架 &#xff1a; 开始属性的学习 &#xff1a; &#xff08;2&#xff09; 继续属性学习 &#xff1a; &#xff08;3&#xff09; 谢谢

MySQL——9、事务管理

事务管理 1、什么是事务&#xff1f;2、事务常见操作方式3、事务隔离级别4、数据库并发场景4.1、读-写4.2、RR与RC的本质区别 1、什么是事务&#xff1f; mysql是基于CS模式的&#xff0c;是一套网络服务&#xff0c;所以我们是可以在本地连接上远程服务器的mysql服务端的。my…

Python之面向对象详解(一篇足矣)

目录 一、初阶面向对象 1. 初识面向对象 1.1 对象和self 1.2 常见成员 1.3 应用示例 将数据封装到一个对象&#xff0c;便于以后使用。 将数据封装到对象中&#xff0c;在方法中对原始数据进行加工处理。 根据类创建多个对象&#xff0c;在方法中对对象中的数据进行修改…

【Qt】qml组件对象怎么传递给c++

将QML组件对象传递给C的方法 在QML和C之间传递完整的组件对象需要特殊处理&#xff0c;因为QML组件是动态创建的JavaScript对象。以下是几种有效的方法&#xff1a; 1. 使用QObject指针传递 C端设置 // MyClass.h #include <QObject> #include <QQuickItem>cla…

Java基础 集合框架 List框架

list架构 list接口list 核心特性以及扩展Collection的体现 抽象类 AbstractList抽象类 AbstractSequentialList (简化链表的顺序访问)AbstractSequentialList 核心特点自定义实现示例代码讲解其实现原理AbstractSequentialList 总结与AbstractList的对比 List 实现类 ArrayList…

2025年6月28和29日复习和预习(C++)

学习笔记大纲​一、预习部分&#xff1a;数组基础​&#xff08;一&#xff09;核心知识点​数组的创建&#xff1a;掌握一维数组的声明方式&#xff0c;如int arr[5];&#xff08;创建一个包含 5 个整数的数组&#xff09;。重点在于理解数组长度需为常量&#xff0c;且在声明…

【centos8服务如何给服务器开发3306端口】

在 CentOS 8 中开放 MySQL 默认端口 3306&#xff0c;需要配置防火墙和 SELinux。以下是详细步骤&#xff1a; 1. 开放防火墙端口&#xff08;Firewalld&#xff09; CentOS 8 默认使用 firewalld 管理防火墙&#xff0c;执行以下命令开放 3306 端口&#xff1a; # 开放 TCP 33…

python系列之:使用md5和sha256完成签名认证,调用接口

python系列之:使用md5和sha256完成签名认证,调用接口 MD5签名和sha256签名认证md5认证代码sha256认证代码拼接签名生成签名拼接url调用接口MD5签名和sha256签名认证 MD5签名认证 算法特性: 生成128位(16字节)的哈希值计算速度快已被证明存在碰撞漏洞(不同输入可能产生相同…

SpringBatch配置与入门实例

通过对SpringBatch基础概念的了解&#xff0c;参考&#xff1a;SpringBatch使用介绍 任何技术用起来之后&#xff0c;再去探究内部细节的原理&#xff0c;才会事半功倍。下面记录一下笔者在SpringBoot项目中集成SpringBatch&#xff0c;并且通过一个小的实例展示如何简单使用它…

spdlog 项目介绍与二次封装

目录 介绍 二次封装 介绍 spdlog 是C开源的第三方日志库&#xff0c;整个项目在 spdlog 命名空间中。 在 spdlog 命名空间的 level 命名空间里定义了枚举类型&#xff0c;把日志分为了 5 个等级&#xff1a;trace debug info warn err critical enum level_enum : in…

shell编程之awk命令详解

1. awk 教程 1.1 调用 awk awk 是一种强大的文本处理工具&#xff0c;在 Linux 系统中广泛应用于日志分析、数据处理等场景。调用 awk 主要有以下三种方式&#xff1a; 1.1.1 命令行方式 基本语法为&#xff1a; awk (-F filed-separator) commands input-files其中&#…

服务器需要备案吗?在哪些地区需要备案?

&#x1f3af; 服务器是否需要备案&#xff1f; 是否需要备案&#xff0c;关键看以下两个因素&#xff1a; 服务器所在地&#xff08;机房位置&#xff09; 网站面向的访问群体&#xff08;境内或境外&#xff09; &#x1f3f7; 中国大陆&#xff08;境内&#xff09;服务器…

HarmonyOS学习3---ArkUI

1、组件 1.1、基础组件 1.2、布局容器 1.3、页面导航 1.4、其他组件 2、ArkTs/C混合开发&#xff0c;高性能编码 3、布局能力&交互归一 4、实时开发预览