关于Linux下C++程序内存dump的分析和工具

前言

         程序崩溃令人很崩溃,特别是让人找不到原因的崩溃,但是合适的工具可以帮助人很快的定位到问题,在AI基础能力ASR服务开发时,找到了一种比较实用和简单的内存崩溃的dump分析工具breakpad,

可以帮助在Linux下C++开发程序时发生崩溃快速定位

breakpad简介

       Google breakpad是一个非常实用的跨平台的崩溃转储和分析模块,支持Linux、mac、solaris、windows。可以借助Google breakpad来捕捉程序程序崩溃的错误报告。即在程序崩溃时会生成dump文件。

而dump文件是进程的内存镜像,能够保存程序中断时的进程状态,让我们在程序崩溃后能够了解具体原因。

breakpad 结构和原理示意图

获取breakpad

     breakpad在github网站上的地址为: GitHub - google/breakpad: Mirror of Google Breakpad project


     在ASR工程化服务中也已经集成好了breakpad库breakpad.zip

breakpad使用

代码示例

     网上搜索的办法不一而足,缺陷较多,很多没有兼顾到的地方,而且使用过程中有许多需要注意的地方;在AI基础能力开发过程中,我们已经形成了比较简单和易操作的方式来使用breakpad,使用breakpad需要嵌入的代码十分简单,以下是示例:

breakpad示例

#include "breakpad/src/client/linux/handler/exception_handler.h"
#include <string>
 
bool DumpCallback(const google_breakpad::MinidumpDescriptor &descr, void *context, bool succeeded) {
        return succeeded;
    }
 
int main(int argc, char** argv){
    //breakpad  只接受绝对路径的dump dir
     
    std::string dump_dir = "/home/alex/dumpdir";
 
    //为breakpad创建dump存放目录
    mkdir(dump_dir.data(), 0775);
 
    google_breakpad::MinidumpDescriptor descriptor(dump_dir);
 
    // minidump文件目录
    google_breakpad::ExceptionHandler eh(descriptor, NULL, CppProcess::DumpCallback, NULL, true, -1);
     
    do_your_stuff();
}

编写代码工程时,包含breakpad的头文件,并指定程序链接libbreakpad_client.a库

如上,在为breakpad需要生成dump文件准备好相应的目录后,创建一个descriptor和eh实例即可,在程序崩溃后,breakpad会在/home/alex/dumpdir目录下创建一个后缀为dump的文件

注意事项

  1. breakpad所创建的实例,descriptor和eh,属于栈上的对象,在其生命期内可以接受异常,它要尽可能早的创建,和尽可能晚的关闭,基于这个原则,最好是把它放在main函数的开头
  2. breakpad生成dump的目录需要传入绝对路径
  3. 对于 DumpCallback回调函数,应当尽量写的简短,就像内联函数一样,因为程序在崩溃后所能做的操作有限,某些阻塞性的系统调用如申请内存,调用其他库的函数等操作可能无法完成
  4. 为了生成有用的信息,编译程序时,需要加上-g编译选项,使程序和库包含调试信息

崩溃分析

      当程序发生崩溃时,通过前面的方式获取到dump文件后,接下来就是分析崩溃文件,找到程序崩溃的位置和原因,需要做以下几步:

  1.  从breakpad的结构和原理示意图中可以了解到,要得到最终的信息,需要结合程序和库的符号信息,和dump文件,来生成可读的栈信息,breakpad提供了从程序和库中分离出符号的工具,附带在breakpad库中,会随着breakpad库一起编译出来,
    工具程序是dump_syms,下面是一个从程序或者库文件中分离出符号信息(注意编译时的-g选项)的示例代码:

    分离程序或库中的符号信息

    #!/bin/bash
     
    for  program in `ls ./`
    do
     
    #生成相应库文件的sym文件
    ./dump_syms ./$program > $program.sym
     
    #获取属于该库文件的一个唯一编号,如00A5F6B1C92FB3657CC65C7B1C4E62920
    uuid=`head -n1 $program.sym | awk '{print $4}'`
     
    #获取该文件在符号信息中的名称,可能和程序名一致,如http_service
    prodir=`head -n1 $program.sym | awk '{print $5}'`
     
    #创建存放sym文件的目录
    mkdir -p ./symbols/$prodir/$uuid
     
    #将符号文件移动到相应位置下
    mv $program.sym ./symbols/$prodir/$uuid/$prodir.sym
     
    done

    以上是一个小脚本,可以为当前目录下所有文件生成符号信息,为后续做准备。
     

  2. 当程序崩溃时,会生成一个dump文件,一般是这种格式:59638c7c-ae27-4d04-bf4c4eac-75e328be.dmp在做好了上一步准备后,下一步是生成人类可读的栈信息,仍然需要使用从breakpad库中编译得到的一个工具命令minidump_stackwalk,使用方法如下:

  3. 生成堆栈信息

    ./minidump_stackwalk 59638c7c-ae27-4d04-bf4c4eac-75e328be.dmp symbols/

紧接上一步,就会生成本次程序崩溃的相关信息,以下是对于栈崩溃的分析示例:

崩溃栈示例

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

Operating system: Linux

                  0.0.0 Linux 4.15.0-52-generic #56-Ubuntu SMP Tue Jun 4 22:49:08 UTC 2019 x86_64

CPU: amd64

     family 6 model 85 stepping 4

     1 CPU

GPU: UNKNOWN

Crash reason:  SIGSEGV /SEGV_MAPERR

Crash address: 0x8

Process uptime: not available

Thread 35 (crashed)

 0  libtlvkaldi.so!sub_func_add_timeinfo [tlv_kaldi_dec_sub_func.cc : 34 + 0x8]

    rax = 0x0000000000000000   rdx = 0x00007fde7ce397c0

    rcx = 0x00007fde7c0008d0   rbx = 0x00007fde7cfe77e0

    rsi = 0x00007fde7c0008e8   rdi = 0x00007fde7cfe77e0

    rbp = 0x00007fde7ce397c0   rsp = 0x00007fdf0bffcc70

     r8 = 0x00007fe242ee48f8    r9 = 0x00007fde7c0008d0

    r10 = 0x0000000000000018   r11 = 0x00007fde7ce38930

    r12 = 0x00007fded5a21ba8   r13 = 0x00007fded5a21b40

    r14 = 0x0000000000000001   r15 = 0x0000000000000014

    rip = 0x00007fe242195c38

    Found by: given as instruction pointer in context

 1  libtlvkaldi.so!tlv_kaldi_dec_get_rslt [tlv_kaldi_dec.cc : 293 + 0x17]

    rbx = 0x00007fde77e17c10   rbp = 0x00007fde7c008310

    rsp = 0x00007fdf0bffcd20   r12 = 0x00007fde7c18bb50

    r13 = 0x00007fde5ab21300   r14 = 0x00007fdf0bffcdd4

    r15 = 0x00007fdf0bffcdd8   rip = 0x00007fe242194b39

    Found by: call frame info

 2  libprotos.so!TlvKaldiVadec::Decode(char*, unsigned int, VadecDataInfo const&) [tlv_kaldi_vadec.cc : 181 + 0x13]

    rbx = 0x00007fdf0bffd030   rbp = 0x00007fde88ffe230

    rsp = 0x00007fdf0bffcda0   r12 = 0x0000000000000c80

    r13 = 0x00007fdf0bffd1ac   r14 = 0x15f0b8657b590764

    r15 = 0x0000000000000000   rip = 0x00007fe243fef36b

    Found by: call frame info

 3  libprotos.so!boost::detail::thread_data<TlvKaldiVadec::Init(tlv_kaldi_vadec_callback_t, std::__cxx11::string)::<lambda()> >::run [tlv_kaldi_vadec.cc : 56 + 0xe]

    rbx = 0x00007fde88ffe230   rbp = 0x00007fde7c0010a0

    rsp = 0x00007fdf0bffd1a0   r12 = 0x00007fded3c4c940

    r13 = 0x00007fdf0bffd1ac   r14 = 0x00007fdf0bffd1a8

    r15 = 0x0000000000000004   rip = 0x00007fe243fefc0e

    Found by: call frame info

 4  libprotos.so!thread_proxy [thread.cpp : 171 + 0x9]

    rbx = 0x00007fded3c4c940   rbp = 0x0000000000000000

    rsp = 0x00007fdf0bffd200   r12 = 0x00007fdea54720e0

    r13 = 0x0000000000000000   r14 = 0x00007fded3c4c940

    r15 = 0x00007fe238e75470   rip = 0x00007fe243ffe38d

    Found by: call frame info

 5  libpthread-2.27.so + 0x76db

    rbx = 0x0000000000000000   rbp = 0x0000000000000000

    rsp = 0x00007fdf0bffd240   r12 = 0x00007fdf0bffd300

    r13 = 0x0000000000000000   r14 = 0x00007fded3c4c940

 示例开头描述了操作系统的一下信息:
Operating system: Linux
                                     0.0.0 Linux 4.15.0-52-generic #56-Ubuntu SMP Tue Jun 4 22:49:08 UTC 2019 x86_64


紧接着描述CPU指令集
CPU: amd64
           family 6 model 85 stepping 4
           1 CPU

崩溃原因描述:这里是进程的SIGSEGV/SEGV_MAPERR信号,一般是段错误造成的崩溃

Crash reason: SIGSEGV /SEGV_MAPERR
Crash address: 0x8
Process uptime: not available

其后就是我们所需要关注的程序的栈信息,它以线程thread为分组,从编号0开始向下递增,每一个编号的部分表示一层函数调用栈,也就是0层是本线程的最顶层栈,标号行也描述了栈调用的函数所在的库,文件和行号,以方便程序员定位和分析崩溃的代码
如下示例,Thread 35 (crashed)表示在35号线程崩溃,0号栈是线程调用栈顶层,右侧描述了该函数在libtlvkaldi.so库的tlv_kaldi_dec_sub_func.cc文件,函数名sub_func_add_timeinfo,在文件的34行,紧接着是该函数的栈的各寄存器的值,2号栈调用1号栈,1号栈
调用0号栈,崩溃发生在0号所在位置,此时即可分析代码,找出可能造成崩溃的原因

崩溃栈示例

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

Thread 35 (crashed)

 0  libtlvkaldi.so!sub_func_add_timeinfo [tlv_kaldi_dec_sub_func.cc : 34 + 0x8]

    rax = 0x0000000000000000   rdx = 0x00007fde7ce397c0

    rcx = 0x00007fde7c0008d0   rbx = 0x00007fde7cfe77e0

    rsi = 0x00007fde7c0008e8   rdi = 0x00007fde7cfe77e0

    rbp = 0x00007fde7ce397c0   rsp = 0x00007fdf0bffcc70

     r8 = 0x00007fe242ee48f8    r9 = 0x00007fde7c0008d0

    r10 = 0x0000000000000018   r11 = 0x00007fde7ce38930

    r12 = 0x00007fded5a21ba8   r13 = 0x00007fded5a21b40

    r14 = 0x0000000000000001   r15 = 0x0000000000000014

    rip = 0x00007fe242195c38

    Found by: given as instruction pointer in context

 1  libtlvkaldi.so!tlv_kaldi_dec_get_rslt [tlv_kaldi_dec.cc : 293 + 0x17]

    rbx = 0x00007fde77e17c10   rbp = 0x00007fde7c008310

    rsp = 0x00007fdf0bffcd20   r12 = 0x00007fde7c18bb50

    r13 = 0x00007fde5ab21300   r14 = 0x00007fdf0bffcdd4

    r15 = 0x00007fdf0bffcdd8   rip = 0x00007fe242194b39

    Found by: call frame info

 2  libprotos.so!TlvKaldiVadec::Decode(char*, unsigned int, VadecDataInfo const&) [tlv_kaldi_vadec.cc : 181 + 0x13]

breakpad与工程代码的管理

            在实际工程应用中,编译出的二进制文件与符号表应当是分开存放的,二进制文件中不应当附带符号信息,从编译到部署到生产环境,大致分为以下几步:
    1)编译代码后,使用dump_syms工具将编译出的二进制文件内的符号表(symblos)分离出来,按照版本存放

    2)使用strip命令将编译后的二进制文件内的符号信息剥除

    3)将剥除后的程序部署,后按照正常测试,上线

    4)若运行过程中发生崩溃,生成了dump文件,将dump文件和1)中保存的相应版本的符号表信息按照前文所说的步骤获得栈崩溃上下文信息,然后分析崩溃原因

总结 


       工欲善其事,必先利其器!
在C++工程所生成的库和程序中,不应当附带符号信息,以防止逆向工程,而且带有符号信息会显著增加程序和库的文件大小,不方便传输;
可以在分离出符号信息并保存后,使用linux的stip命令将工程的C++程序和库的符号信息去除,缩小文件大小;

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C语言初阶-数据类型和变量【下】

紧接上期------------------------->>>C语言初阶-数据类型和变量【上】 全局变量和局部变量在内存中存储在哪⾥呢&#xff1f; ⼀般我们在学习C/C语⾔的时候&#xff0c;我们会关注内存中的三个区域&#xff1a; 栈区 、 堆区 、 静态区 。 内存的分配情况 局部变量是…

Java->排序

目录 一、排序 1.概念 2.常见的排序算法 二、常见排序算法的实现 1.插入排序 1.1直接插入排序 1.2希尔排序(缩小增量法) 1.3直接插入排序和希尔排序的耗时比较 2.选择排序 2.1直接选择排序 2.2堆排序 2.3直接选择排序与堆排序的耗时比较 3.交换排序 3.1冒泡排序…

肺腺癌预后新指标:全切片图像中三级淋巴结构密度的自动化量化|文献精析·24-10-09

小罗碎碎念 本期这篇文章&#xff0c;我去年分享过一次。当时发表在知乎上&#xff0c;没有标记参考文献&#xff0c;配图的清晰度也不够&#xff0c;并且分析的还不透彻&#xff0c;所以趁着国庆假期重新分析一下。 这篇文章的标题为《Computerized tertiary lymphoid structu…

【实战】Nginx+Lua脚本+Redis 实现自动封禁访问频率过高IP

大家好&#xff0c;我是冰河~~ 自己搭建的网站刚上线&#xff0c;短信接口就被一直攻击&#xff0c;并且攻击者不停变换IP&#xff0c;导致阿里云短信平台上的短信被恶意刷取了几千条&#xff0c;加上最近工作比较忙&#xff0c;就直接在OpenResty上对短信接口做了一些限制&am…

《Linux运维总结:基于ARM64+X86_64架构CPU使用docker-compose一键离线部署mongodb 7.0.14容器版分片集群》

总结&#xff1a;整理不易&#xff0c;如果对你有帮助&#xff0c;可否点赞关注一下&#xff1f; 更多详细内容请参考&#xff1a;《Linux运维篇&#xff1a;Linux系统运维指南》 一、部署背景 由于业务系统的特殊性&#xff0c;我们需要面向不通的客户安装我们的业务系统&…

C++入门基础知识110—【关于C++ if...else 语句】

成长路上不孤单&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a; 【14后&#x1f60a;///C爱好者&#x1f60a;///持续分享所学&#x1f60a;///如有需要欢迎收藏转发///&#x1f60a;】 今日分享关于C if...else 语句的相关内容&#xff01…

数据结构-5.2.树的性质

一.树的常考性质&#xff1a; 性质1&#xff1a;结点数 总度数 1(结点的度&#xff1a;结点分支的数量) 一个分支中&#xff0c;如父结点B&#xff0c;两个子结点为E和F&#xff0c;结点B的度的值为2&#xff0c;等于子结点数量&#xff0c;加上这一个父结点(父结点只能有一…

部署私有仓库以及docker web ui应用

官方地址&#xff1a;https://hub.docker.com/_/registry/tags 一、拉取registry私有仓库镜像 docker pull registry:latest 二、运⾏容器 docker run -itd -v /home/dockerdata/registry:/var/lib/registry --name "pri_registry1" --restartalways -p 5000:5000 …

数据结构-5.5.二叉树的存储结构

一.二叉树的顺序存储&#xff1a; a.完全二叉树&#xff1a; 1.顺序存储中利用了静态数组&#xff0c;空间大小有限&#xff1a; 2.基本操作&#xff1a; (i是结点编号) 1.上述图片中i所在的层次后面的公式应该把n换成i(图片里写错了)&#xff1b; 2.上述图片判断i是否有左…

ClickHouse的原理及使用,

1、前言 一款MPP查询分析型数据库——ClickHouse。它是一个开源的&#xff0c;面向列的分析数据库&#xff0c;由Yandex为OLAP和大数据用例创建。ClickHouse对实时查询处理的支持使其适用于需要亚秒级分析结果的应用程序。ClickHouse的查询语言是SQL的一种方言&#xff0c;它支…

网络安全之XXE攻击

0x01 什么是 XXE 个人认为&#xff0c;XXE 可以归结为一句话&#xff1a;构造恶意 DTD 介绍 XXE 之前&#xff0c;我先来说一下普通的 XML 注入&#xff0c;这个的利用面比较狭窄&#xff0c;如果有的话应该也是逻辑漏洞。 既然能插入 XML 代码&#xff0c;那我们肯定不能善罢…

图像分类-demo(Lenet),tensorflow和Alexnet

目录 demo(Lenet) 代码实现基本步骤&#xff1a; TensorFlow 一、核心概念 二、主要特点 三、简单实现 参数: 模型编译 模型训练 模型评估 Alexnet model.py train.py predict.py demo(Lenet) PyTorch提供了一个名为“torchvision”的附加库&#xff0c;其中包含…

【在Linux世界中追寻伟大的One Piece】信号捕捉|阻塞信号

目录 1 -> 信号捕捉初识 2 -> 阻塞信号 2.1 -> 信号其他相关常见概念 2.2 -> 在内核中的表示 2.3 -> sigset_t 2.4 -> 信号集操作函数 2.5 -> sigprocmask 2.6 -> sigpending 3 -> 捕捉信号 3.1 -> 内核如何实现信号的捕捉 3.2 ->…

VBA高级应用30例应用3Excel中的ListObject对象:选择表的一部分

《VBA高级应用30例》&#xff08;版权10178985&#xff09;&#xff0c;是我推出的第十套教程&#xff0c;教程是专门针对高级学员在学习VBA过程中提高路途上的案例展开&#xff0c;这套教程案例与理论结合&#xff0c;紧贴“实战”&#xff0c;并做“战术总结”&#xff0c;以…

【Spring】获取 Cookie和Session

回顾 Cookie HTTP 协议自身是属于“无状态”协议 无状态&#xff1a;默认情况下&#xff0c;HTTP 协议的客户端和服务器之间的这次通信和下次通信之间没有直接的联系 但是在实际开发中&#xff0c;我们很多时候是需要知道请求之间的关联关系的 例如登录网站成功后&#xff…

Linux高效查日志命令介绍

说明&#xff1a;之前介绍Linux补充命令时&#xff0c;有介绍使用tail、grep命令查日志&#xff1b; Linux命令补充 今天发现仅凭这两条命令不够&#xff0c;本文扩展介绍一下。 命令一&#xff1a;查看日志开头 head -n 行数 日志路径如下&#xff0c;可以查看程序启动是否…

安装和配置k8s可视化UI界面dashboard-1.20.6

安装和配置k8s可视化UI界面dashboard-1.20.6 1.环境规划2.初始化服务器1&#xff09;配置主机名2&#xff09;设置IP为静态IP3&#xff09;关闭selinux4&#xff09;配置主机hosts文件5&#xff09;配置服务器之间免密登录6&#xff09;关闭交换分区swap&#xff0c;提升性能7&…

系统架构设计师考试背记精要

1、架构的本质&#xff1a; &#xff08;1&#xff09;软件架构为软件系统提供了一个结构、行为和属性的高级抽象。&#xff08;2&#xff09;软件架构风格是特定应用领域的惯用模式&#xff0c;架构定义一个词汇表和一组约束。 2、数据流风格&#xff1a;适合于分阶段做数据处…

Springboot从入门到起飞-【day01】

个人主页→VON 收录专栏→Springboot从入门到起飞 一、前言 经过了近两个月的沉淀开始了新专栏的学习&#xff0c;经过深思熟虑还是决定重新学习java&#xff0c;因为基础部分东西太多太乱就不进行逐一的更新了&#xff0c;等到学完了一同进行更新。 二、Springboot简要概述 …

汽车免拆诊断案例 | 2013款宝马116i车偶尔加速不良

故障现象  一辆2013款宝马116i车&#xff0c;搭载N13B16A 发动机&#xff0c;累计行驶里程约为12.1万km。车主反映&#xff0c;该车行驶中偶尔加速无反应&#xff0c;且发动机故障灯异常点亮。 故障诊断 接车后试车&#xff0c;故障现象无法再现。用故障检测仪检测&#xff…