Tombstone原理分析

article/2025/10/6 23:08:34

本文主要围绕三个问题对tombstone进行分析和介绍,debuggerd是如何监控进程并生成tombstone的?tombstone文件中的信息都是什么,是怎么获取的?tombstone文件应该怎么分析?



一、Tombstone简介

当一个native程序开始执行时,系统会注册一些连接到debuggerd的signal handlers。针对进程出现的不同的异常状态,Linux kernel会发送相应的signal给异常进程,debuggerd捕获这些signal,做出相应处理的同时(一般来说是退出异常进程),在/data/tombstones目录下生成一个tombstone。Tombstone文件以tombstone_XX形式命名,该文件个数上限可以进行设置,当超过上限时则每次覆盖时间最老的文件。Tombstone记录了崩溃的进程的基本信息,堆栈调用信息,内存信息等等。

二、Tombstone生成过程

为了更好地分析tombstone触发的过程,我们可以先了解一下Android中一个进程是如何跑起来的。

一个APP一般是存储在UFS,EMMC之类的存储设备上,当用户去点击图标的时候,操作系统会把这个APP从Flash里面加载到主存当中,通过类似fork()的命令去创建对应的进程。进程创建完毕之后通过类似exec的命令去加载APP的内容,然后由/system/bin/linker程序加载APP所用到的一些共享库。最终跳转到APP程序的入口处执行。

Debuggerd守护进程的初始化同样是在linker中进行的,下面我们首先分析linker部分中是如何去初始化debuggerd的。

1.Debuggerd初始化

Debuggerd是在linker中进行初始化的,我们从linker的起始位置开始进行分析,在/bionic/linker/arch/arm64/begin.S中可以看到,这里调用了__linker_init函数,对linker进行初始化,__linker_init应该要返回可执行程序的开始地址,之后跳转过去执行。

在__linker_init()方法中调用了__linker_init_post_relocation(),__linker_init_post_relocation()初始化linker的全局变量,之后调用linker_main()函数获取可执行程序的开始地址,然后跳转到开始地址继续执行。

可以看到linker_main()函数中对系统环境进行了确认,对系统属性进行了初始化设置,之后调用linker_debuggerd_init()初始化debuggerd。

linker_debuggerd_init()定义了一个callbacks结构体,之后把这个callbacks作为参数传递给debuggerd_init(),继续进行debuggerd的初始化。

在debuggerd_init()中首先把callbacks赋给g_callbacks,之后调用mmap为线程的栈分配空间,然后调用mprotect函数,设置stack对应的内存区的保护属性为可读可写,之后把栈的起始点定义在页末尾并对齐。最后调用debuggerd_register_handlers()去注册一些异常信号,而异常信号的处理函数是debuggerd_signal_handler()。

在debuggerd_register_handlers()函数中注册异常信号,可以通过这个函数看到都有哪些异常信号会被debuggerd捕获。



2.Debuggerd处理异常

当native进程出现了问题,则通过linux内核判断会发生信号,最终信号在被debuggerd捕获之后由debuggerd_signal_handler()函数处理。debuggerd_signal_handler()使用互斥锁pthread_mutex_lock()来保护线程,防止同一时间多个线程处理信号产生冲突。首先调用log_signal_summary()来输出一些信息,调用log_signal_summary()的目的是防止后面动作出错,最终无法定位到是哪个进程出现了错误,所以先在这里打印一些关键信息。可以在logcat里找到对应的信息。在这一部分中打印如下信息:信号的num、信号code、Fault addr出错时的地址、tid:对应的线程id和pid:对应的进程id。

之后debuggerd_signal_handler()调用debuggerd_dispatch_pseudothread(),通过clone系统调用clone出一个伪线程,去处理dispatch信号,原来的线程原地等待子线程的开始和结束。

在伪线程中通过fork去创建子线程,新创建的子线程中通过execle系统调用去执行crash_dump64程序,父进程等待crash_dump64进程退出。

在crash_dump64进程中,再fork出一个新进程,父进程通过fork_exit_read去等待子进程,子进程继续执行crash_dump的任务。

在crash_dump中,通过/proc/PID/cmdline获取进程的名字,通过/proc/PID/fd/获取此进程打开了多少文件,每个文件都有一个文件描述符。

循环遍历这个进程中的所有线程,对进程中的每一个线程进行ptrace操作,对目标线程读取其crashinfo。Crashinfo读取完毕后detach当前的线程。

之后在crash_dump()中调用tombstoned_connect()通过socket连接到tombstone进程。根据signal的si_val的值不同做出不同的判断,为0时dump tombstone,为1时只dump backtrace。

最终tombstone通过engrave_tombstone()函数生成,engrave_tombstone函数的第二个参数,unwinder,是输出 backtrace等信息的关键函数。

通过unwinder的初始化函数我们可以看到,unwinder初始化过程中获取了当前进程的内存和memory map。这些信息会在后面帮助debuggerd生成tombstone文件。

对tombstone的生成过程做一个小结,当Native进程发生了异常,操作系统会去异常向量表的地址去处理异常,然后发送信号。在debuggred_init()注册的信号处理函数就会捕获信号并处理,创建伪线程去启动crash_dump进程,crash_dump则会获取当前进程中各个线程的crash信息。tombstoned进程是开机就启动的,开机时注册好了socket等待连接。当crash_dump()去连接tombstoned进程的时候,根据传递的dump_type类型会返回一个/data/tombstones/下文件描述符。

crash_dump进程后续通过engrave_tombstone函数将所有的线程的详细信息写入到tombstone文件中,至此就在/data/tombstones下生成了此次对应的tombstone_XX文件。



三、Tombstone文件实例分析

上文中我们提到,tombstone通过engrave_tombstone()函数生成,在这一节中,我们将结合实例和代码来介绍tombstone文件中都包含哪些内容,这些内容都是如何获取和输出出来的。实例中的tombstone文件由Google提供的development.apk生成。

engrave_tombstone()中首先调用的是dump_header_info()函数打印tombstone头信息。

可以从tombstone实例中看到dump_header_info()函数输出的是一些和编译,CPU架构相关的信息。

调用dump_time_stamp()打印native crash发生的时间。

Tombstone实例如下,这里打印的时间是本机时间,因为实验机没有联网设置时间所以显示时间是2020-01-01 12:23:27+0800。

然后engrave_tombstone()要打印的是线程相关信息,首先调用unwinder的GetProcessMemory()方法,获取进程对应的内存信息,之后engrave_tombstone()把unwinder和获取到的内存信息作为参数传递给dump_thread()打印线程相关的信息。

dump_thread()中调用的第一个函数是dump_thread_info(),它的职责是打印出错的线程所属的进程pid,线程tid,线程名,进程名和出错线程对应的apk的uid。Tombstone实例如下:

 

然后dump_thread()调用dump_signal_info()函数打印引发这次tombstone的信号信息,dump_thread()在调用这个函数时把thread_info和进程对应的内存传递给了dump_signal_info()。在dump_signal_info函数中首先调用signal_has_si_addr()对信号的signo进行判断,可以在下面的函数中看到,只有在信号不是manually sent并且是某些特定信号的情况下,才会有对应的si_addr。

Tombstone实例如下,可以看到虽然SIGSEGV信号在上面列出的信号列表中,但是因为si_code是SI_USER,所以还是没有对应的信号地址。

之后调用dump_probable_cause(),通过分析signal_info打印可能的原因信息,主要依靠分析signal number和fault address来得出可能的结论。如果没有分析出可能的原因就不会打印出任何信息,在本文使用的这个tombstone的例子中就没有任何信息输出。

之后调用dump_registers()输出出错时寄存器的值,thread_info里面记录了错误发生时的寄存器信息,dump_registers将他们按顺序输出到tombstone文件里。Tombstone实例如下:

调用log_backtrace()打印backtrace信息,先在unwinder中调用unwind()方法解析内存中的信息,然后log_backtrace函数中调用了unwinder的FormatFrame()函数把获取的堆栈信息输出到tombstone文件中。调用unwinder的unwind()方法会导致保存的寄存器的值发生变化,所以在调用这个方法之前先对寄存器的值做一个备份。

unwind()函数中,首先根据pc寄存器的值找到函数unwind的段内存地址

然后根据unwind段中信息找到指令相关的编码数据,elf表等

接着根据入栈地址,分析函数上一级的栈底保存的sp和lr寄存器的值;最后更新寄存器信息。

在tombstone实例中,依次输出pc寄存器的值,对应的文件名,对应的函数名和offset。最近的frame中的pc寄存器的值可以直接从thread_info当中获取,后面的pc寄存器的值在unwind的过程中更新;后面的文件名可以根据memory map和pc寄存器中的地址得出;后面的function name和function offset是通过memory map和pc寄存器中的地址找到dex文件,解析dex文件可以得到对应的函数名和offset。

接下来调用dump_memroy_and_code()函数,这个函数只在主线程中调用,它循环遍历每一个寄存器,打印寄存器附近的memory信息。dump_memory_and_code()中主要是获取寄存器名和值,还有map_info的name属性编辑到label中,真正进行dump的部分在dump_memory()函数里。dump_memory()函数中从寄存器记录的地址addr的位置读取了256个byte的内存到data中,并输出到Tombstone文件,每16个byte一行,输出了16行,输出的第一段是对应的起始地址,第二第三段是内存的值,每一个byte用两个16进制数表示,第四段是对应的ascii码。

随后调用dump_all_maps()打印map信息,dump_threads()向dump_all_maps()传入unwinder和信号的对应地址,和dump_memroy_and_code()一样,dump_all_maps()只在主线程中被调用。Map信息在unwinder初始化的时候获取,map信息记录了进程对应的内存映射,包括开始地址,长度,访问权限,文件描述符,offset等信息。

从Tombstone实例中可以看到输出的信息依次为内存的起始和结束位置,对内存的操作权限,内存的offset,内存的长度,map的名字和buildID

至此dump_threads()函数运行完成,回到engrave_tombstone()函数中。随后engrave_tombstone()函数调用dump_logs()打印systemlog和mainlog的信息。Tombstone实例如下:



四、Tombstone文件分析方法

在tombstone中我们可以看到进程的pid和线程的tid,如果pid和tid相等,那么可以判断crash发生在这个进程的主线程中,后面的name属性表明了crash进程的名称和它在文件系统中的位置。在tombstone中我们还可以看到程序是因为什么信号导致了Crash以及出现错误的地址。根据这些信息可以初步判断crash的类型。下面列出一些信号的常见发送方和触发条件。

除了上面介绍的信息之外,我们主要要分析的是backtrace信息,我们可以通过addr2line工具去分析backtrace,根据backtrace提供的内存地址和符号库文件去找到代码出错的具体位置。我们可以通过addr2line工具找到是哪个文件下的哪个函数的哪一行发生了crash,在backtrace中越靠上的越接近最后被调用的函数。值得一提的是只有带symbol的so文件才能被定位到。本文中使用的实例中的一些库是在apex文件夹下的,这样的so文件里面是没有symbol的,所以即便找到了对应的so文件,也无法找到对应的代码段。

在可执行程序中都包含有调试信息(编译的时候需要加-g选项),addr2line根据程序源程序的行号和编译后的机器代码之间的对应关系Line Number Table去找找对应的行号。

另一个可以帮助我们解读tombstone文件的是工具是objdump,它可以反汇编指定so文件来得到对应的源代码和汇编代码,objdump工具和addr2line在同一个路径下。

长按关注

内核工匠微信

Linux 内核黑科技 | 技术文章 | 精选教程


http://chatgpt.dhexx.cn/article/8IA8b2HN.shtml

相关文章

【date】Linux date命令修改时间的问题

Linux date命令修改时间的问题 问题路径找原因解决方法 问题 Android10;高通平台 使用下面date命令修改时间日期,时分秒生效,年月日不生效 > date -D YYYY-MM-DD hh:mm:ss 路径 \android\external\toybox\toys\posix\date.c \android\e…

i2ctools工具移植到android(使用NDK方式 在某android平台测试)

前提条件 主板i2c已在设备树配置status和引脚复用正常,即设备的i2c总线达到正常使用条件I2C device interface假设内核已配置进去 编译工具链NDK环境搭建 下载NDK 下载地址点我解压 ~/workspace/ndk$ ls android-ndk-r22b android-ndk-r22b-linux-x86_64.zip …

高通平台 Android9 adb shell “hwclock -w“ 报错

hwclock -w 报错 文章目录 hwclock -w 报错问题现象分析1. hwclock命令分析2. /dev/rtc0驱动节点分析 修改设备树后hwclock -w报错没有了,但是系统会重启,原因未知 问题现象 sdm660_64:/ # hwclock -w hwclock: ioctl 4024700a: Invalid argument分析 …

Android top命令、ps命令、busybox命令

top命令 usage: top [-Hbq] [-k FIELD,] [-o FIELD,] [-s SORT] [-n NUMBER] [-m LINES] [-d SECONDS] [-p PID,] [-u USER,]Show process activity in real time.-H Show threads -k Fallback sort FIELDS (default -S,-%CPU,-ETIME,-PID) -o Show FIELDS (def PID,USER,PR,N…

OpenHarmony啃论文俱乐部—盘点开源鸿蒙引用的三方开源软件[1]

目录这里写自定义目录标题 OpenHarmony third_party三方库:学术研究和参与开源的结合third_party_openh264third_party_ninjathird_party_gnthird_party_markupsafethird_party_toyboxthird_party_gstreamerthird_party_ffmpegthird_party_mtdevthird_party_flutter…

Android缺少awk工具的几种解决方法

在日常测试中,我们会用到各种各样的Android平台,用于测试存储设备的性能。其中,我们依赖到Android平台自身的工具,通过编写shell脚本来实现测试存储设备的性能。   而awk工具(shell命令)在shell脚本中会经常用到,一般…

toybox 和 busybox 的作用

来自知乎:程序员秘书 ##前言## 我们在做android开发时,经常会有在板子系统里要修改文件内容对比验证问题,或者要操作特殊的shell命令操作看些信息,等等一些需求。但是往往会因为刷到板子的系统里默认没有/不支持相关的命令&…

欢乐听:一个简洁的瀑布流模式的音乐分享站

欢乐听 一个简洁的瀑布流模式的音乐分享站。

分享5个高质无损音乐网站,歌曲很丰富,爱听歌的小伙伴有耳福了

生活中很多人都离不开音乐,散步的时候听音乐,等待的时候听着音乐,心情不好的时候听音乐,不管走到哪,有音乐的陪伴一点也不寂寞,不同音乐的旋律给我们带来不同的心情,今天小编就给爱听音乐的小伙…

【音乐】收藏的300多首抖音神曲,MP3音乐分享,近一年的抖音歌曲

今天给大家分享N多(300)首抖音神曲。抖音大家都知道,各种火,各种原因,其中的BGM(背景音乐)更是起到了至关重要(画龙点睛)的作用,不知道是哪个大神搞的,去年我搞视频的时候…

搭建一个点歌QQ机器人,另外还能看美女

目录 前言具体实现1、爆照2、生日书3、获取歌词和分享音乐 完整项目下载地址(配置了python环境)完整项目下载地址(电脑没有python环境) 前言 完整项目,包括框架、代码和详细使用说明可以去社区下载(下载完…

基于java的音乐网站的设计与实现

欢迎添加微信互相交流学习哦! 项目源码:https://gitee.com/oklongmm/biye 基于java的音乐网站的设计与实现 摘 要 随着互联网和宽带上网的普及,网络传输以其特有的快速、高效、便捷的传输方式越来越被人们接受。在当今社会的影响下&…

android wifi传输音乐,让你通过WiFi分享手机上的歌曲,音乐共享软件MyStream十一发布Android版...

MyStream原来是 iOS上的音乐共享应用,十一期间,它将跨出iOS平台,首次推出Android版音乐共享服务。 MyStream和主流的Pandora、Spotify、Turntable.fm、Songza这些音乐分享服务并不一样。它将手机上的本地音乐通过WiFi或蓝牙和周围的设备进行音…

竞品分析:网易云音乐和QQ音乐,音乐类app的战场

文章从产品的角度分别分析两款产品的行业市场、功能、业务模式以及运营策略,进一步了解两款产品的差异与不同。 一、产品概况 1. 产品概述及版本 网易云音乐是一款专注于发现与分享的音乐产品,依托专业音乐人、DJ、好友推荐及社交功能,为用…

网易云音乐竞品分析

概述 1.原因 个人平时使用网易云较多,正好想学学竞品分析怎么写。因此,想通过梳理市场最新报告和数据表现,了解在线音乐行业的现状和市场情况;分析网易云音乐目前的市场地位、功能设计、UI设计等方面,与相关竞品进行对…

音乐平台程序源码分享

简介: 这是一个音乐分享平台源码,用户可以自行上传音乐分享,源码自适应手机,使用很方便。 安装说明: 演示环境:宝塔PHP5.5 mysql5.6 Apache 2.4.46,把程序上传到根目录,然后修改数…

分享两个音乐播放地址

下歌吧音乐下载平台 http://music.y444.cn/ 搜索时候如果没有想要的,可以切换线路搜索一下 MYFREEMP3 MYFREEMP3 一个音乐下载以及播放网站 下载后的音乐名称需要直接更改一下,可以下载歌曲的歌词等 http://tools.liumingye.cn/music/?pagesearch…

基于web的音乐分享网站的设计与实现

欢迎添加微信互相交流学习哦! 项目源码:https://gitee.com/oklongmm/biye2 音乐分享网站的设计与实现 摘 要 随着社会的发展时代的前进,IT行业的发展也是日新月异,对人类的生产和生活方式产生了很大影响。网络传播以其特有的快…

把自己录制的mp3音乐分享到朋友圈

参考:https://www.zhihu.com/question/345647212 录了首歌,想上传至微信朋友圈,发现还没那么简单。 mp3音乐文件上传到网易云音乐后,无法使用分享功能,所以无法分享到朋友圈。 可通过以下步骤解决。 一、将mp3文件发…

#pragma comment

原因:突然看到#pragma comment,不知其意思.感觉自己是个渣渣.所以写了一篇博客. #pragma comment 简单来说就是链接了一个文件&#xff0c;它可以是compiler&#xff0c;exestr&#xff0c;lib&#xff0c;linker文件. 例如&#xff1a; #include<WinSock2.h> #pragma…