Linux程序调试优化(2)—— 一次学会嵌入式Linux下程序崩溃定位

news2024/12/30 2:10:57

文章目录

  • 1.gdb调试
    • 1.1 gcc 编译时不带-g
    • 1.2 gcc 编译时带-g
  • 2.coredump栈回溯
    • 2.1 使能core文件生成
    • 2.1 借助core文件回溯堆栈
  • 3.backtrace捕捉SIGSEGV信号
  • 4.其他工具
  • 5.总结

写程序时,难免碰到程序崩溃的现象,而如何排查这些内存崩溃问题,例如访问空指针、访问非法内存、栈溢出等,如何快速解决该类问题,则是一项比较重要(比较头秃)的技能。
在这里汇总了几个最常见的方法,分别是
1)gdb调试
2)coredump堆栈回溯、
3)signal捕捉挂死信号+backtrace回溯堆栈+ -rdynamic编译参数
4)signal捕捉挂死信号+backtrace回溯堆栈+addr2line获取具体函数地址

#include <iostream>
#include <string.h>

int main(int argc, char **argv)
{
    char *str = NULL;
    strcpy(str, "hello world");
    
    printf("str is %s\n", str);
    return 0;
}

像这个访问空指针,运行则会直接提示Segmentation fault

xzx@ubuntu:~/share/project_ipc/debug_tools/crash$ ./test 
Segmentation fault (core dumped)

1.gdb调试

多用于程序开发阶段,能稳定复现的问题,直接挂个gdb再复现一次,看到函数调用栈后,基本就能发现问题原因。因为是在虚拟机环境下调试,所以直接执行gdb便可以,如果是嵌入式环境,需要交叉编译gdb工具.

gcc编译时,其中的-g参数,标识在编译时加入调试信息,在发生问题之后,能在排查时带来更大的帮助,但相应的会增加可执行程序的体积。这里分别对使用-g和不使用-g去进行分类讨论

1.1 gcc 编译时不带-g

xzx@ubuntu:~/share/project_ipc/debug_tools/crash$ g++ test.cpp -o test
xzx@ubuntu:~/share/project_ipc/debug_tools/crash$ gdb ./test 

输入r,让程序跑到崩溃处,使用bt查看堆栈,能看到程序崩溃在main函数中,但没有看到相应崩溃的具体行号和具体行数,对定位崩溃帮助有限
image.png

1.2 gcc 编译时带-g

xzx@ubuntu:~/share/project_ipc/debug_tools/crash$ g++ -g test.cpp -o test
xzx@ubuntu:~/share/project_ipc/debug_tools/crash$ gdb ./test

同样在崩溃后,输入bt查看挂死堆栈
image.png
这里能看到充分的提示,挂死在crashFunc,在test.cpp的第十三行,常见的问题提示到这份上,基本缕缕代码逻辑都可以解决。
但是这里要注意,使用-g属性,会将更多调试信息收集放到可执行文件中,将会大大增加可执行文件的体积。如下是使用-g和不使用-g的对比,明显大了一倍不止,所以不适用正式释放给测试的版本
image.png

2.coredump栈回溯

当设备已经不在调试阶段,要给到测试验证功能的时候,就没法再用gdb直接进行调试。
这时可以借助coredump文件,在崩溃发生时,生成core文件,崩溃发生后,可以简单把core文件拿来,使用gdb便能看到挂死时的堆栈,快速定位问题。

2.1 使能core文件生成

1)设置core文件最大大小
正常系统上默认未打开core文件生成,可以通过ulimit -c 512设置生成core文件最大大小。
设置后输入ulimit -a 确认,可以看到core file size 为512,注意这里也不能设置的太大,因为太多core文件会挤爆机器的空间,造成其他严重问题!

xzx@ubuntu:~/share/project_ipc/debug_tools/crash$ ulimit -c 512
xzx@ubuntu:~/share/project_ipc/debug_tools/crash$ ulimit -a
core file size          (blocks, -c) 512
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 31504
max locked memory       (kbytes, -l) 65536
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1048576
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 31504
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

2)设置core文件生成路径
这里我设置的是自己当前程序的路径,文件格式名为core+程序名+pid+unix时间戳

echo "/home/xzx/share/project_ipc/debug_tools/crash/cor%e-%p-%t" > /proc/sys/kernel/core_pattern

设置好这两个之后,便可以使用。

2.1 借助core文件回溯堆栈

xzx@ubuntu:~/$ ./test 
Segmentation fault (core dumped)
xzx@ubuntu:~/$ ls
1.map  cortest-99532-1713865380  test  test.cpp
xzx@ubuntu:~/$ gdb test cortest-99532-1713865380
GNU gdb (Ubuntu 8.1.1-0ubuntu1) 8.1.1
Core was generated by `./test'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x000056122f20c7ef in crashFunc () at test.cpp:13
13          strcpy(str, "hello world");
(gdb) bt
#0  0x000056122f20c7ef in crashFunc () at test.cpp:13
#1  0x000056122f20c84e in main (argc=1, argv=0x7ffc431caa38) at test.cpp:27
(gdb) 

运行可执行程序后,可以看到生成了cortest-99532-1713865380文件,使用gdb test cortest-99532-1713865380后
结果直接使用gdb看到的一样,能准确定位到挂死函数的具体位置。(这里如果编译时没使用-g选项,则看不到行号)

3.backtrace捕捉SIGSEGV信号

使用coredump文件进行堆栈回溯有一定的风险,如果设置的参数不对,程序反复崩溃的情况下,可能会将机器内的空间全部占用,造成其他严重问题
可以使用backtrace函数,当发生崩溃时,直接捕捉SIGSEGV/SIGPIPE信号等,在进程退出之前将挂死时的堆栈打印到日志文件中,通常这是最简单高效的方法。(uclibc目前不支持backtrace函数,可以借助libunwind实现

改写以下demo,加入对崩溃信号的处理,并打印堆栈

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <execinfo.h>


void sigHandleFunc(int signo)
{
    printf("sigHandleFunc recv sig:%d", signo);
    int j, nptrs;

    void *buffer[100];
    char **strings;
 
    nptrs = backtrace(buffer, 100);
    printf("backtrace() returned %d addresses\n", nptrs);
 
    /* The call backtrace_symbols_fd(buffer, nptrs, STDOUT_FILENO)
       would produce similar output to the following: */
 
    strings = backtrace_symbols(buffer, nptrs);
    if (strings == NULL) {
        perror("backtrace_symbols");
        exit(EXIT_FAILURE);
    }
    for (j = 0; j < nptrs; j++){
        printf("%s\n", strings[j]);
    }

    free(strings);
    signal(signo,SIG_DFL);
}
void normalFunc()
{
    return ;
}
void crashFunc()
{
    char *str = NULL;   
    strcpy(str, "hello world");
    printf("str is %s\n", str);

    return ;
}
int main(int argc, char **argv)
{
    int i = 0;
    signal(SIGSEGV, sigHandleFunc); //segmentation violation
    signal(SIGABRT, sigHandleFunc); //abort program (formerly SIGIOT)
    signal(SIGPIPE, sigHandleFunc); //floating-point exception
    while (1)
    {
        if (i++ == 10)
        {
            printf("crashFunc begin\n");
            crashFunc();
            printf("crashFunc done\n");
        }
        else
        {
            printf("normalFunc begin\n");
            normalFunc();
            printf("normalFunc done\n");
        }

    }
    return 0;
}

运行后,得到的结果为:

alientek@ubuntu16:~/study_linux_project/linux_unix/study_note/crash$ ./test 
sigHandleFunc recv sig:11backtrace() returned 6 addresses
./test() [0x4009c1]
/lib/x86_64-linux-gnu/libc.so.6(+0x354c0) [0x7fd462d4c4c0]
./test() [0x400abd]
./test() [0x400b45]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7fd462d37840]
./test() [0x4008a9]
Segmentation fault

但是这里只看到了挂在main函数以及之后的偏移地址,但却没看到具体的函数以及行号,这里有两个方法可以解决:
1)编译时添加-rdynamic参数,那么崩溃时能给backtrace提供更多符号信息。但是会增大动态符号表,降低程序运行中动态寻找符号表的效率
这里分别编译了了两种情况下的可执行程序,能看到使用-rdynamic参数编译的可执行程序,.dynsym表明显更大

g++ -rdynamic test.cpp -o have_rdynamic
g++ test.cpp -o no_rdynamic

image.png
image.png

与之对应的,添加-rdynamic后的可执行程序挂死时将能看到更多信息,包括详细的函数堆栈,如下:

sigHandleFunc recv sig:11backtrace() returned 6 addresses
./have_rdynamic(_Z13sigHandleFunci+0x4b) [0x400c11]
/lib/x86_64-linux-gnu/libc.so.6(+0x354c0) [0x7f0b14ed34c0]
./have_rdynamic(_Z9crashFuncv+0x1e) [0x400d0d]
./have_rdynamic(main+0x65) [0x400d95]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f0b14ebe840]
./have_rdynamic(_start+0x29) [0x400af9]
Segmentation fault

2)使用addr2line等函数,进行反汇编操作找到函数名,推荐该方法,对程序性能没有影响,这里也是重点介绍这种方法。
这里有一个小技巧,因为使用-g编译参数时,我们能得到更多调试信息,但是这种方式会增大可执行程序的体积,不适合作为release版本,所以我们可以选择在编译时,生成带-g的debug版本,以及不带-g的release版本,将release版本作为正式测试版本,而debug版本的可以保存起来,当发生问题时,可以用addr2line配合debug版本,将崩溃的地址转化为对应的函数名以及行号!

这里我们模拟这种情况,分别编译两个可执行程序,debug和release

g++ test.cpp -o release
g++ -g  test.cpp -o debug

假设release版本得到的结果如下:

sigHandleFunc recv sig:11backtrace() returned 6 addresses
./no_rdynamic() [0x4009c1]
/lib/x86_64-linux-gnu/libc.so.6(+0x354c0) [0x7fa3040364c0]
./no_rdynamic() [0x400abd]
./no_rdynamic() [0x400b45]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7fa304021840]
./no_rdynamic() [0x4008a9]
Segmentation fault

那么拿到这份信息之后,我们用addr2line+debug版本进行堆栈回溯。

alientek@ubuntu16:~/$ addr2line -e debug 0x4009c1 -f
_Z13sigHandleFunci
/home/alientek/study_linux_project/linux_unix/study_note/crash/test.cpp:17
alientek@ubuntu16:~/$ addr2line -e debug 0x400abd -f
_Z9crashFuncv
/home/alientek/study_linux_project/linux_unix/study_note/crash/test.cpp:45

即避免了调试信息对可执行程序体积、执行效率的影响,又能准确定位挂死堆栈,效果立竿见影!

4.其他工具

其他一些可用的,包括strace工具、内存消毒工具等等,都可以debug,主要还是根据具体情况来选择对应的方法

5.总结

方法名优点缺点
gdb调试简单方便,还能调试其他问题基本仅适用于debug阶段
coredump文件+gdb栈回溯可用于release版本需要debug版本挂死才能看到具体行号
使用不当会引入其他严重问题
signal捕捉挂死信号+
backtrace回溯堆栈+
rdynamic编译参数
不需要-g选项,不增大应用程序体积会增大动态符号表,降低程序运行中动态寻找符号表的效率
signal捕捉挂死信号+
backtrace回溯堆栈+
addr2line获取具体函数地址
不需要-g选项,不增大应用程序体积
不需要-rdynamic编译参数,不会降低程序运行中动态寻找符号表的效率
可用于release版本
没啥缺点其实,唯一就是需要保留一份带-g选项的可执行程序

到此为止!另外也有程序崩溃时堆栈已经被破坏的情况,这种等之后单独再总结一篇~

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

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

相关文章

Pulsar Meetup 深圳 2024 会务介绍

“ Hi&#xff0c;各位热爱 Pulsar 的小伙伴们&#xff0c;Pulsar Meetup 深圳 2024 报名倒计时啦&#xff0c;快来报名。这里汇集了腾讯、华为和谙流科技等大量 Pulsar 大咖&#xff0c;干货多多&#xff0c;礼品多多&#xff0c;不容错过啊。 ” 活动介绍 图片 由 AscentStre…

巧用波卡生态优势,Mythical Games 引领 Web3 游戏新航向

Polkadot 对创新、安全和治理的承诺为 Mythical Games 提供了极大的发展价值。这个链上生态不仅将支持 Mythical Games 成长发展&#xff0c;还将帮助其他 Mythos 合作伙伴来壮大建设项目。 —— Mythical Games 创始人兼首席执行官 John Linden 近期 Web3 游戏行业又有新动向&…

【管理咨询宝藏86】MBB物流公司营销项目价值定位与目标客户规划报告

本报告首发于公号“管理咨询宝藏”&#xff0c;如需阅读完整版报告内容&#xff0c;请查阅公号“管理咨询宝藏”。 【管理咨询宝藏86】MBB物流公司营销项目价值定位与目标客户规划报告 【格式】PDF版本 【关键词】战略规划、MBB、麦肯锡 【核心观点】 - 价值定位是企业希望深…

HORROR SYSTEM

HORROR SYSTEM是一个创新的工具包,允许开发者在Unity3D中创建独特的原创恐怖游戏。 HORROR SYSTEM是一款强大而灵活的工具,旨在基于Unity3D引擎创建沉浸式第三人称恐怖游戏。 这项资产易于使用且直观,可以让任何经验水平的开发人员将他们的想法付诸实践,创造出高质量、充满…

飞行汽车飞行控制系统功能详解

飞行汽车是一种创新的交通工具&#xff0c;结合了汽车和飞机的特点。它可以在陆地上行驶&#xff0c;同时也具备在空中飞行的能力。飞行汽车的概念已经存在多年&#xff0c;并且近年来随着技术的进步和研发的深入&#xff0c;这种交通工具正在逐渐从概念走向现实。 飞行汽车的…

NLP方面知识

NLP方面知识 一 基础1.Tokenizer1.1 分词粒度&#xff1a;1.2 大模型的分词粒度1.3 各路语言模型中的tokenizer 2.Embedding layer2.1 理解Embedding矩阵 一 基础 1.Tokenizer tokenizer总体上做三件事情&#xff1a; 分词。tokenizer将字符串分为一些sub-word token string&…

学习Rust的第10天:枚举和模式匹配

今天我们来看看一个类似的概念 enums 。 Enums: We saw that in Rust, enums are data types that list possible values, giving a simple and type-safe mechanism to describe alternatives. We looked at how to create enums and use them to represent similar possibili…

MT8788智能模块简介_MTK联发科安卓核心板方案厂商

MT8788安卓核心板是一款具备超高性能和低功耗的4G全网通安卓智能模块。该模块采用联发科AIOT芯片平台&#xff0c;供货周期长。 MT8788核心板搭载了12nm制程的四个Cortex-A73处理器核心和四个Cortex-A53处理器核心&#xff0c;最高主频可达2.0GHz。板载内存容量可选为4GB64GB(也…

【Win】怎么下载m3u8视频\怎么通过F12开发人员工具获取视频地址\怎么下载完整的.ts格式视频

怎么下载m3u8视频&#xff1f;首先通过浏览器本地的开发人员工具&#xff0c;获取m3u8的地址&#xff0c;然后再通过第三方下载工具下载&#xff0c;此处以N_m3u8DL-CLI_v3.0.2为例 如下图的步骤&#xff0c;即可获取到视频的m3u8地址 打开N_m3u8DL-CLI_v3.0.2&#xff0c;粘贴…

生成式AI在B端产品的应用分析

AI产品发展到现在&#xff0c;消费端的产品应用还受到比较大的限制&#xff1b;但是在B端&#xff0c;已经有了不错的表现。作者总结了AI产品在B端的几款应用&#xff0c;一起来看看表现如何。 生成式AI在B端产品的应用分析© 由 ZAKER 提供 随着今年生成式AI应用的大范围…

ROS1快速入门学习笔记 - 04创建工作环境与功能包

一、定义 工作空间(workspace)是一个存放工程开发相关文件的文件夹。 src:代码空间&#xff08;Source Space&#xff09;build: 编辑空间&#xff08;Build Space&#xff09;devel:开发空间&#xff08;Development Space&#xff09;install:安装空间&#xff08;Install …

【网络安全】HTTP协议 — 基础

专栏文章索引&#xff1a;网络安全 有问题可私聊&#xff1a;QQ&#xff1a;3375119339 目录 学习目标​ 一、万维网的诞生与发展​编辑 1.万维网的诞生与发展 2.HTTP协议诞生与发展 二、网络基础 1.TCP/IP分层传输 1&#xff09;TCP/IP协议 2&#xff09;封装与拆封 …

【linux】匿名管道|进程池

1.进程为什么要通信&#xff1f; 进程也是需要某种协同的&#xff0c;所以如何协同的前提条件(通信) 通信数据的类别&#xff1a; 1.通知就绪的 2.单纯的数据 3.控制相关的信息 2.进程如何通信&#xff1f; 进程间通信&#xff0c;成本会高一点 进程间通信的前提&#xff0c;先…

vue【vuex状态管理】

1&#xff1a;vuex是什么&#xff1a; vuex是一个状态管理工具&#xff0c;状态就是指的数据&#xff0c;可以将数据存放到vuex中以供其他组件使用时进行调用 2&#xff1a;应用场景&#xff1a; ①&#xff1a;像用户登录客户端&#xff0c;这个用户的数据需要在多个组件中…

VUE3 ref,props,生命周期

1.--ref属性 1.1代码 1.1.1子表 <template><div class"person"><h1>中国</h1><h2 ref"title2">北京</h2><h3>尚硅谷</h3><button click"showLog">点我输出h2这个元素</button>&l…

每天五分钟计算机视觉:基于YOLO算法精确分类定位图片中的对象

滑动窗口的卷积的问题 滑动窗口的卷积实现效率很高,但是它依然不能够输出最精准的边界框,比如下面所示: 我们可以看到蓝色框不论在什么位置都不能很好的确定车的位置,有一个算法是YOLO 算法它能够帮助我们解决这个问题。 YOLO 算法 比如我们的输入图像是100*100,我们会…

【网络安全】对称加密、非对称加密以及密钥分配

目录 1、对称加密 2、非对称加密 3、如何分配对称密钥&#xff1f; 4、如何分配非对称密钥&#xff1f; 1、对称加密 所谓对称加密&#xff0c;就是指加密密钥与解密密钥都使用相同的密钥。如下图所示&#xff0c;通信双方使用的就是对称加密密钥。//代表&#xff1a;DES和…

Hive服务详解

Hive服务 HiveServer2、Hive Metastore 服务服务共同构成了 Hive 生态系统中的核心功能&#xff0c;分别负责管理元数据和提供数据查询服务&#xff0c;为用户提供了一个方便、高效的方式来访问和操作存储在 Hive 中的数据。 1. Hive 查询服务&#xff08;HiveServer2&#xf…

恶补《操作系统》2_1——王道学习笔记

2操作系统-进程 2.1_1 进程的定义、组成、组织方式、特征 组成&#xff1a;PCB&#xff08;进程存在唯一的标志&#xff09;&#xff0c;程序段&#xff0c;数据段 组织方式&#xff1a;链接方式&#xff0c;指针指向不同的队列&#xff1b;索引方式&#xff0c;索引表 特征…

【深度学习】yolo-World,数据标注,zeroshot,目标检测

仓库&#xff1a;https://github.com/AILab-CVC/YOLO-World 下载权重&#xff1a; 仓库下载和环境设置 下载仓库&#xff1a;使用以下命令从 GitHub 上克隆仓库&#xff1a; git clone --recursive https://github.com/AILab-CVC/YOLO-World.git创建并激活环境&#xff1a…