【Android12】Android Framework系列---tombstone墓碑生成机制

tombstone墓碑生成机制

Android中程序在运行时会遇到各种各样的问题,相应的就会产生各种异常信号,比如常见的异常信号 Singal 11:Segmentation fault表示无效的地址进行了操作,比如内存越界、空指针调用等。
Android中在进程(主要指native进程)崩溃时会生成墓碑文件,这些文件中记录了崩溃时的调用堆栈、日志信息、寄存器二进制数据等等,用以帮助开发者已经崩溃问题。
墓碑文件默认保存在**/data/tombstones/**目录中,以tombstone_xxx(xxx表示编号)方式命名。墓碑文件数量有上限,达到上限时会删除最旧的墓碑文件,可以通过配置属性 tombstoned.max_tombstone_count来修改默认的墓碑文件数量。

本文源码基于Android12版本。

墓碑环境初始化

在这里插入图片描述

bionic为程序初始化墓碑生成环境

bionic是android提供的符合POSIX接口的标准C库,其中提供了Linker(动态连接器)。动态链接器的作用是在运行动态链接的可执行文件时,动态链接器负责加载程序到内存中,并解析对符号的引用。
bionic在Linker中初始化墓碑生成环境,下面的汇编代码中执行了__linker_init这个符号(函数)

//bionic/linker/arch/arm64/begin.S
#include <private/bionic_asm.h>ENTRY(_start)// Force unwinds to end in this function..cfi_undefined x30mov x0, spbl __linker_init/* linker init returns the _entry address in the main image */br x0
END(_start)

__linker_init这个函数定义在/bionic/linker/linker_main.cpp中,先后执行__linker_init、__linker_init_post_relocation、linker_main。在linker_main函数中,调用linker_debuggerd_init,初始化墓碑生成环境。另外,在linker_main函数中可以看到很多比较重要的初始化函数,比如__system_properties_init。

//bionic/linker/linker_main.cpp
extern "C" ElfW(Addr) __linker_init(void* raw_args) {// Initialize TLS early so system calls and errno work.// 省略return __linker_init_post_relocation(args, tmp_linker_so);
}static ElfW(Addr) __attribute__((noinline))
__linker_init_post_relocation(KernelArgumentBlock& args, soinfo& tmp_linker_so) {// 省略// 执行linker_mainElfW(Addr) start_address = linker_main(args, exe_to_load);if (g_is_ldd) _exit(EXIT_SUCCESS);INFO("[ Jumping to _start (%p)... ]", reinterpret_cast<void*>(start_address));// Return the address that the calling assembly stub should jump to.return start_address;
}static ElfW(Addr) linker_main(KernelArgumentBlock& args, const char* exe_to_load) {ProtectedDataGuard guard;#if TIMINGstruct timeval t0, t1;gettimeofday(&t0, 0);
#endif// Sanitize the environment.__libc_init_AT_SECURE(args.envp);// Initialize system properties__system_properties_init(); // may use 'environ'// Initialize platform properties.platform_properties_init();// 这里!!!// Register the debuggerd signal handler.linker_debuggerd_init();// 省略return entry;
}

linker_debuggerd_init函数定义在/bionic/linker/linker_debuggerd_android.cpp中,该函数调用了libdebuggerd_handler_core库(/system/core/debuggerd)的debuggerd_init函数。

//bionic/linker/linker_debuggerd_android.cpp
void linker_debuggerd_init() {// There may be a version mismatch between the bootstrap linker and the crash_dump in the APEX,// so don't pass in any process info from the bootstrap linker.debuggerd_callbacks_t callbacks = {
#if defined(__ANDROID_APEX__).get_process_info = get_process_info,
#endif.post_dump = notify_gdb_of_libraries,};// 这里debuggerd_init(&callbacks);
}
debuggerd模块为Signal安装处理的Handler

debuggerd_init函数中,会为各个异常信号Signal注册用来处理信号的Handler。这样当程序发生异常时,就会调用注册好的Handler。

//system/core/debuggerd/handler/debuggerd_handler.cpp
void debuggerd_init(debuggerd_callbacks_t* callbacks) {// 省略// linux sigaction的标准用法struct sigaction action;memset(&action, 0, sizeof(action));sigfillset(&action.sa_mask);// debuggerd_signal_handler就是用来处理异常信号的Handleraction.sa_sigaction = debuggerd_signal_handler;action.sa_flags = SA_RESTART | SA_SIGINFO;// Use the alternate signal stack if available so we can catch stack overflows.action.sa_flags |= SA_ONSTACK;#define SA_EXPOSE_TAGBITS 0x00000800// Request that the kernel set tag bits in the fault address. This is necessary for diagnosing MTE// faults.action.sa_flags |= SA_EXPOSE_TAGBITS;// 为各个异常信号注册Handlerdebuggerd_register_handlers(&action);
}

debuggerd_register_handlers函数在头文件中实现(这种形式叫内联函数)。可以通过ro.debuggabledebug.debuggerd.disable属性来控制注册过程。

//system/core/debuggerd/include/debuggerd/handler.h
static void __attribute__((__unused__)) debuggerd_register_handlers(struct sigaction* action) {char value[PROP_VALUE_MAX] = "";bool enabled =!(__system_property_get("ro.debuggable", value) > 0 && !strcmp(value, "1") &&__system_property_get("debug.debuggerd.disable", value) > 0 && !strcmp(value, "1"));if (enabled) {// 针对不同异常注册sigaction(SIGABRT, action, nullptr);sigaction(SIGBUS, action, nullptr);sigaction(SIGFPE, action, nullptr);sigaction(SIGILL, action, nullptr);sigaction(SIGSEGV, action, nullptr);sigaction(SIGSTKFLT, action, nullptr);sigaction(SIGSYS, action, nullptr);sigaction(SIGTRAP, action, nullptr);}sigaction(BIONIC_SIGNAL_DEBUGGER, action, nullptr);
}

到此墓碑环境注册完成,在这个流程中可以选择通过ro.debuggable或debug.debuggerd.disable来关闭墓碑。

墓碑生成流程

在这里插入图片描述

完成了上述墓碑环境初始化后,当程序运行发生异常,比如内存越界触发了SIGSEGV就会调用debuggerd_signal_handler这个函数(

//system/core/debuggerd/handler/debuggerd_handler.cpp// Handler that does crash dumping by forking and doing the processing in the child.
// Do this by ptracing the relevant thread, and then execing debuggerd to do the actual dump.
static void debuggerd_signal_handler(int signal_number, siginfo_t* info, void* context) {// 省略// clone一个子进程出来(在clone出来的进程中处理墓碑生成)// Essentially pthread_create without CLONE_FILES, so we still work during file descriptor// exhaustion.pid_t child_pid =clone(debuggerd_dispatch_pseudothread, pseudothread_stack,CLONE_THREAD | CLONE_SIGHAND | CLONE_VM | CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID,&thread_info, nullptr, nullptr, &thread_info.pseudothread_tid);if (child_pid == -1) {fatal_errno("failed to spawn debuggerd dispatch thread");}// Wait for the child to start...futex_wait(&thread_info.pseudothread_tid, -1);// and then wait for it to terminate.futex_wait(&thread_info.pseudothread_tid, child_pid);// 后面是一些收尾处理// Restore PR_SET_DUMPABLE to its original value.if (prctl(PR_SET_DUMPABLE, orig_dumpable) != 0) {fatal_errno("failed to restore dumpable");}// Restore PR_SET_PTRACER to its original value.if (restore_orig_ptracer && prctl(PR_SET_PTRACER, 0) != 0) {fatal_errno("failed to restore traceable");}if (info->si_signo == BIONIC_SIGNAL_DEBUGGER) {// If the signal is fatal, don't unlock the mutex to prevent other crashing threads from// starting to dump right before our death.pthread_mutex_unlock(&crash_mutex);} else {// Resend the signal, so that either the debugger or the parent's waitpid sees it.resend_signal(info);}
}

上面的函数中,clone了一个子进程来处理了墓碑生成流程。clone出来的子进程会执行debuggerd_dispatch_pseudothread函数。

static int debuggerd_dispatch_pseudothread(void* arg) {// 省略// 创建pipe管理(因为后面还要fork一个进程来执行crash_dump64这个bin程序)// pipe用来与之后fork的进程通信用unique_fd input_read, input_write;unique_fd output_read, output_write;if (!Pipe(&input_read, &input_write) != 0 || !Pipe(&output_read, &output_write)) {fatal_errno("failed to create pipe");}// fork一个子进程// Don't use fork(2) to avoid calling pthread_atfork handlers.pid_t crash_dump_pid = __fork();if (crash_dump_pid == -1) {async_safe_format_log(ANDROID_LOG_FATAL, "libc","failed to fork in debuggerd signal handler: %s", strerror(errno));} else if (crash_dump_pid == 0) {// 省略// 子进程执行 "/apex/com.android.runtime/bin/crash_dump64 这个程序// crash_dump64程序是墓碑文件真正的生成者execle(CRASH_DUMP_PATH, CRASH_DUMP_NAME, main_tid, pseudothread_tid, debuggerd_dump_type,nullptr, nullptr);async_safe_format_log(ANDROID_LOG_FATAL, "libc", "failed to exec crash_dump helper: %s",strerror(errno));return 1;}// 省略
}

在debuggerd_dispatch_pseudothread中主要做了两个事件,一个是创建Pipe用来与子进程通信。一个是fork了一个子进程,让子进程执行crash_dump64这个二进制程序。crash_dump64这个二进制程序中会真正的生成墓碑文件。
crash_dump64的实现在/system/core/debuggerd/crash_dump.cpp

int main(int argc, char** argv) {// 省略// 判断debug.debuggerd.wait_for_debugger,是否等待gdb// Defer the message until later, for readability.bool wait_for_debugger = android::base::GetBoolProperty("debug.debuggerd.wait_for_debugger",android::base::GetBoolProperty("debug.debuggerd.wait_for_gdb", false));if (siginfo.si_signo == BIONIC_SIGNAL_DEBUGGER) {wait_for_debugger = false;}// 连接tombstoned守护进程,通过tombstoned得到墓碑文件的FD(g_output_fd){ATRACE_NAME("tombstoned_connect");LOG(INFO) << "obtaining output fd from tombstoned, type: " << dump_type;g_tombstoned_connected = tombstoned_connect(g_target_thread, &g_tombstoned_socket, &g_output_fd,&g_proto_fd, dump_type);}// 使用unwindstack生成函数调用堆栈// TODO: Use seccomp to lock ourselves down.unwindstack::UnwinderFromPid unwinder(256, vm_pid, unwindstack::Regs::CurrentArch());if (!unwinder.Init()) {LOG(FATAL) << "Failed to init unwinder object.";}// 生成墓碑文件中的内容std::string amfd_data;if (backtrace) {ATRACE_NAME("dump_backtrace");dump_backtrace(std::move(g_output_fd), &unwinder, thread_info, g_target_thread);} else {{ATRACE_NAME("fdsan table dump");populate_fdsan_table(&open_files, unwinder.GetProcessMemory(),process_info.fdsan_table_address);}{ATRACE_NAME("engrave_tombstone");// 这里,生成墓碑engrave_tombstone(std::move(g_output_fd), std::move(g_proto_fd), &unwinder, thread_info,g_target_thread, process_info, &open_files, &amfd_data);}}// return 0;
}

crash_dump64会连接tombstoned这个进程,通过tombstoned取得将要输出的墓碑文件的FD(因为墓碑文件有数量限制、达到上限时要删除旧的墓碑文件,所以专门用tombstoned这个守护进程管理)。然后使用unwindstack库生成函数堆栈,并调用
engrave_tombstone函数生成墓碑。

在engrave_tombstone函数中,我们会看到比较熟悉的墓碑文件中的文本内容。比如“***”这种字符。另外只有在ro.debuggable开启的状态下,才会调用dump_logs在墓碑文件中输出Log日志。

//system/core/debuggerd/libdebuggerd/tombstone.cpp
void engrave_tombstone(unique_fd output_fd, unique_fd proto_fd, unwindstack::Unwinder* unwinder,const std::map<pid_t, ThreadInfo>& threads, pid_t target_thread,const ProcessInfo& process_info, OpenFilesList* open_files,std::string* amfd_data) {// Don't copy log messages to tombstone unless this is a development device.Tombstone tombstone;engrave_tombstone_proto(&tombstone, unwinder, threads, target_thread, process_info, open_files);if (proto_fd != -1) {if (!tombstone.SerializeToFileDescriptor(proto_fd.get())) {async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to write proto tombstone: %s",strerror(errno));}}log_t log;log.current_tid = target_thread;log.crashed_tid = target_thread;log.tfd = output_fd.get();log.amfd_data = amfd_data;bool translate_proto = GetBoolProperty("debug.debuggerd.translate_proto_to_text", true);if (translate_proto) {tombstone_proto_to_text(tombstone, [&log](const std::string& line, bool should_log) {_LOG(&log, should_log ? logtype::HEADER : logtype::LOGS, "%s\n", line.c_str());});} else {bool want_logs = GetBoolProperty("ro.debuggable", false);_LOG(&log, logtype::HEADER,"*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***\n");dump_header_info(&log);_LOG(&log, logtype::HEADER, "Timestamp: %s\n", get_timestamp().c_str());auto it = threads.find(target_thread);if (it == threads.end()) {async_safe_fatal("failed to find target thread");}dump_thread(&log, unwinder, it->second, process_info, true);if (want_logs) {dump_logs(&log, it->second.pid, 50);}for (auto& [tid, thread_info] : threads) {if (tid == target_thread) {continue;}dump_thread(&log, unwinder, thread_info, process_info, false);}if (open_files) {_LOG(&log, logtype::OPEN_FILES, "\nopen files:\n");dump_open_files_list(&log, *open_files, "    ");}if (want_logs) {dump_logs(&log, it->second.pid, 0);}}
}

总结

墓碑初始化及生成流程中,可以通过属性控制是否注册墓碑、是否生成墓碑,以及墓碑文件的数量等功能。同时,也可以根据业务需求,在墓碑中加入自定义内容,比如给墓碑文件的名字追加特殊的时间戳、追加一些自定义日志到墓碑中等等。

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

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

相关文章

Apache-ActiveMQ 反序列化漏洞(CVE-2015-5254)复现

CVE-2016-3088 一、环境搭建 Java:jdk8 影响版本 Apache ActiveMQ < 5.13.0 二、用docker搭建漏洞环境 访问一下web界面 然后进入admin目录登录 账号:admin 密码:admin 三、工具准备 cd /opt wget https://github.com/matthiaskaiser/jmet/releases/download/0.1.0/jmet-0…

QT上位机开发(第一个应用)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 不管是软件&#xff0c;还是硬件&#xff0c;如果我们能够顺利启动第一个应用&#xff0c;点亮第一个电路的话&#xff0c;这对我们的信心来说会有…

如何恢复 iPhone 上永久删除的照片?

2007年&#xff0c;苹果公司推出了一款惊天动地的智能手机&#xff0c;也就是后来的iPhone。你会惊讶地发现&#xff0c;迄今为止&#xff0c;苹果公司已经售出了 7 亿部 iPhone 设备。根据最新一项调查数据&#xff0c;智能手机利润的 95% 都进了苹果公司的腰包。 如此受欢迎…

【Vue2+3入门到实战】(16)VUEVue路由的重定向、404、编程式导航、path路径跳转传参 详细代码示例

目录 一、Vue路由-重定向1.问题2.解决方案3.语法4.代码演示 二、Vue路由-4041.作用2.位置3.语法4.代码示例 三、Vue路由-模式设置1.问题2.语法 四、编程式导航-两种路由跳转方式1.问题2.方案3.语法4.path路径跳转语法5.代码演示 path跳转方式6.name命名路由跳转7.代码演示通过n…

2023十大编程语言及未来展望

2023十大编程语言及未来展望 1. 2023年十大编程语言排行榜2. 十大编程语言未来展望PythonCCJavaC#JavaScriptPHPVisual BasicSQLAssembly language 1. 2023年十大编程语言排行榜 TIOBE排行榜是根据互联网上有经验的程序员、课程和第三方厂商的数量&#xff0c;并使用搜索引擎&a…

阿里云PolarDB数据库优惠价格表11元一天起

阿里云数据库PolarDB租用价格表&#xff0c;云数据库PolarDB MySQL版2核4GB&#xff08;通用&#xff09;、2个节点、60 GB存储空间55元5天&#xff0c;云数据库 PolarDB 分布式版标准版2核16G&#xff08;通用&#xff09;57.6元3天&#xff0c;阿里云百科aliyunbaike.com分享…

ansible管理windows测试

一、环境介绍 Ansible管理主机&#xff1a; 系统: redhat7.6 Linux管理服务器需安装pywinrm插件 Windows客户端主机&#xff1a; 系统: Server2012R2 Windows机器需要安装或升级powershell4.0以上版本&#xff0c;Server2008R2默认的版本是2.0&#xff0c;因此必须升…

使用flutter开发windows桌面软件读取ACR22U设备的nfc卡片id,5分钟搞定demo

最近有个需求&#xff0c;要使用acr122u读卡器插入电脑usb口&#xff0c;然后读取nfc卡片的id&#xff0c;并和用户账号绑定&#xff0c;调研了很多方式&#xff0c;之前使用rust实现过一次&#xff0c;还有go实现过一次&#xff0c;然后使用electron的时候遇到安装pcsc-lite失…

MacBook查看本机IP

嘚吧嘚 其实这也不是什么困难的问题&#xff0c;但是今年刚刚入坑Mac&#xff0c;外加用的频率不是很高&#xff0c;每次使用的时候都查&#xff0c;用完就忘&#xff0c;下次用的时候再查&#x1f92e;。真的把自己恶心坏了&#x1f648;。 所以写篇文章记录一下&#x1f92…

[C++] : 贪心算法专题(第一部分)

1.柠檬水找零&#xff1a; 1.思路一&#xff1a; 柠檬水找零 class Solution { public:bool lemonadeChange(vector<int>& bills) {int file0;int ten 0;for(auto num:bills){if(num 5) file;else if(num 10){if(file > 0)file--,ten;elsereturn false;}else{i…

产品经理学习-策略产品指标

目录&#xff1a; 数据指标概述 通用指标介绍 Web端常用指标 移动端常用指标 如何选择一个合适的数据指标 数据指标概述 指标是衡量目标的一个参数&#xff0c;指一项活动中预期达到的指标、目标等&#xff0c;一般用数据表示&#xff0c;因此又称为数据指标&#xff1b;…

Flask笔记

一&#xff1a;模板渲染 一般的话都序列化成字符串 二&#xff1a;项目拆分 2.1 项目拆分 app.py init.py views.py models.py 模型数据 2.2 蓝图 三&#xff1a;路由参数 3.1 String 重点 3.2 int 3.3 path 3.4 UUID 3.5 any 四&#xff1a;请求方式 五&#xff1a;Requ…

苹果CMS超级播放器专业版无授权全开源,附带安装教程

源码介绍 超级播放器专业版v1.0.8&#xff0c;内置六大主流播放器&#xff0c;支持各种格式的视频播放&#xff0c;支持主要功能在每一个播放器内核中都相同效果。 搭建教程 1.不兼容IE浏览器 2.php版本推荐7.4 支持7.1~7.4 3.框架引入不支持同时引入多个播放器 json对接教…

新版 macos下安装python 2.7 python 3.x多版本简单方法 pyenv python多版本管理工具

在新版本的macos中已经将默认的python升级成了3.x , 今天介绍一个简单的方法在新版本的macos中快速安装 python 2.7的方法, 就是使用brew安装python版本管理工具 pyenv来安装python2.7 # 安装pyenv版本管理工具 brew install pyenv # 安装python2.7 可以安装多个版本的ptyhon…

C#进阶-IIS应用程序池崩溃的解决方案

IIS是微软开发的Web服务器软件&#xff0c;被广泛用于Windows平台上的网站托管。在使用IIS过程中&#xff0c;可能会遇到应用程序池崩溃的问题&#xff0c;原因可能有很多&#xff0c;包括代码错误、资源不足、进程冲突等。本文将为大家介绍IIS应用程序池崩溃的问题分析和解决方…

【计算机毕业设计】python+django数码电子论坛系统设计与实现

本系统主要包括管理员和用户两个角色组成&#xff1b;主要包括&#xff1a;首页、个人中心、用户管理、分类管理、数码板块管理、数码评价管理、数码论坛管理、畅聊板块管理、系统管理等功能的管理系统。 后端&#xff1a;pythondjango 前端&#xff1a;vue.jselementui 框架&a…

MySQL数据库的安装与环境配置

下载 下载MySQL8 安装 解压 配置MySQL环境变量 系统环境变量path D:\ProgramFiles\mysql-8.0.20-winx64\bin 1.点击属性 2.点击高级系统设置 3.点击环境变量 4.在系统变量中找到path 注意这里不是用户变量 5.新建后输入解压的地址 MySQL初始化和启动 以管理员身份运行cmd…

汽车制造厂批量使用成华制造弹簧平衡器

数年来&#xff0c;成华制造都在不断的向各行各界输出着自己的起重设备&#xff0c;与众多企业达成合作&#xff0c;不断供应优质产品。近些年&#xff0c;成华制造以其卓越的产品质量和高效的生产能力&#xff0c;成功实现了弹簧平衡器的大规模批量供应&#xff0c;为重庆数家…

STM32F407ZGT6定时器(学习笔记一)

定时器STM32非常重要的外设&#xff0c;也是比较复杂的外设&#xff0c;下面以STM32F407ZGT6为例记录学习内容&#xff1a;&#xff08;1&#xff09;基本定时功能&#xff0c;&#xff08;2&#xff09;PWM输出功能&#xff0c;&#xff08;3&#xff09;PWM互补死区、多通道移…

代数结构与图论

文章目录 图的基本概念欧拉图与哈密顿图树平面图代数系统群与环格与布尔代数 图的基本概念 图的阶&#xff1a;图中的顶点数 &#xff0c;n 个顶点被称为 n 阶图零图&#xff1a;一条边都没有 平凡图&#xff1a;一阶零图基图&#xff1a;将有向图的各条有向边改成无向边所得到…