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

news2025/1/19 11:09:46

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 x30

  mov x0, sp
  bl __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_main
  ElfW(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 TIMING
  struct 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就是用来处理异常信号的Handler
  action.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;
  // 为各个异常信号注册Handler
  debuggerd_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.coloradmin.cn/o/1344944.html

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

相关文章

华为hcia之ipv6实验手册

R3: dhcp enable ipv6 dhcpv6 pool test address prefix 2000:23::/64 excluded-address 2000:23::2 dns-server 2000:23::2 interface GigabitEthernet0/0/0 ipv6 enable ipv6 address 2000:12::2/64 ipv6 address auto link-local undo ipv6 nd ra halt //无状态配置 inter…

基于Java网上点餐系统设计与实现

博主介绍&#xff1a; ✌至今服务客户已经1000、专注于Java技术领域、项目定制、技术答疑、开发工具、毕业项目实战 ✌ &#x1f345; 文末获取源码联系 &#x1f345; &#x1f447;&#x1f3fb; 精彩专栏 推荐订阅 &#x1f447;&#x1f3fb; 不然下次找不到 Java项目精品实…

Jmeter吞吐量控制器总结

吞吐量控制器(Throughput Controller) 场景&#xff1a; 在同一个线程组里, 有10个并发, 7个做A业务, 3个做B业务,要模拟这种场景,可以通过吞吐量模拟器来实现。 添加吞吐量控制器 用法1: Percent Executions 在一个线程组内分别建立两个吞吐量控制器, 分别放业务A和业务B …

行车记录仪变清晰,变高清的办法一定要收藏

有时候我们会发现行车记录仪拍摄的视频不够清晰&#xff0c;特别是出现事故需要视频为证的时候&#xff0c;如果视频太模糊&#xff0c;很难获得交警的支持&#xff0c;那么如何让行车记录仪拍摄的视频变得更加清晰呢&#xff1f; 小编给大家分享几个办法&#xff0c;建议收藏…

山西电力市场日前价格预测【2023-12-31】

日前价格预测 预测说明&#xff1a; 如上图所示&#xff0c;预测明日&#xff08;2023-12-31&#xff09;山西电力市场全天平均日前电价为445.23元/MWh。其中&#xff0c;最高日前电价为791.27元/MWh&#xff0c;预计出现在08:15。最低日前电价为270.52元/MWh&#xff0c;预计…

SpringIOC之ApplicationObjectSupport

博主介绍&#xff1a;✌全网粉丝5W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

LT8612UX-HDMI2.0 to HDMI2.0 and VGA Converter with Audio,支持三通道视频DAC

HDMI2.0 to HDMI2.0 and VGA Converter with Audio 1. 描述 LT8612UX是一个HDMI到HDMI和vga转换器&#xff0c;它将HDMI2.0数据流转换为HDMI2.0信号和模拟RGB信号。 它还输出8通道I2S和SPDIF信号&#xff0c;使高质量的7.1通道音频。 LT8612UX支持符合HDMI2.0/ 1.4规范的…

k8s搭建(五、k8s可视化管理工具Dashboard配置)

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

【Java开发岗面试】八股文—数据库MySQLRedis

声明&#xff1a; 背景&#xff1a;本人为24届双非硕校招生&#xff0c;已经完整经历了一次秋招&#xff0c;拿到了三个offer。本专题旨在分享自己的一些Java开发岗面试经验&#xff08;主要是校招&#xff09;&#xff0c;包括我自己总结的八股文、算法、项目介绍、HR面和面试…

一起玩儿物联网人工智能小车(ESP32)——20. PWM(脉冲宽度调制)技术初探

摘要&#xff1a;本文主要介绍脉冲宽度调制技术的基本知识。 今天继续完善智能小车的功能。前面&#xff0c;已经通过控制ESP32的GPIO引脚输出高低电平来实现了小车向各个方向行走的运动。但能走只是最基础的功能&#xff0c;下面就来学习在智能小车行走的时候&#xff0c;如何…

元旦特辑:Note5---插入排序

目录 前言&#x1faa9; 1. 排序的概念运用&#x1f7e3; 1.1 排序的概念&#x1f7ea; 1.2 排序的运用&#x1f49c; 2. 直接插入排序&#x1f7e2; 2.1 基本思想&#x1f7e9; 2.2 思路分析&#x1f49a; 2.3 代码实现✅ 2.3.1 sort.h 2.3.2 sort.c 2.3.3 test.c …

Linux网络编程学习心得.4

1.epoll工作模式 水平触发 LT 边沿触发 ET 因为设置为水平触发,只要缓存区有数据epoll_wait就会被触发,epoll_wait是一个系统调用,尽量少调用 所以尽量使用边沿触发,边沿出触发数据来一次只触发一次,这个时候要求一次性将数据读完,所以while循环读,读到最后read默认带阻塞…

设计模式——行为型模式

模板方法模式 行为型模式用于描述程序在运行时复杂的流程控制&#xff0c;即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务&#xff0c;它涉及算法与对象间职责的分配。 行为型模式分为类行为模式和对象行为模式&#xff0c;前者采用继承机制来在类间…

专访大华智慧运营:利用物联网技术,加快融合智能

近日&#xff0c;指令集智能科技到访浙江大华智慧物联运营服务有限公司&#xff0c;对产品研发部总经理孙旭先生进行了深度采访。孙旭先生在采访中表示“现在行业对于运营服务的诉求越来越高&#xff0c;而针对物理空间的运营&#xff0c;最典型的特征就是要靠物联网产品支撑……

嵌入式科普(8)ESP-IDF newlib相关介绍和对比分析

一、目的/概述 二、资料来源 三、ESP-IDF简介 3.1 ESP-IDF FreeRTOS 3.2 ESP-IDF heap_caps 3.3 ESP-IDF newlib 四、对比 嵌入式科普(8)ESP-IDF newlib相关介绍和对比分析 一、目的/概述 1、在我的嵌入式科普(6)你听说过FreeRTOS heap6吗&#xff1f;…

命令模式-举例

开关和电灯之间并不存在直接耦合关系&#xff0c;在命令模式中&#xff0c;发送者与接收者之间引入了新的命令对象&#xff0c;将发送者的请求封装在命令对象中&#xff0c;再通过命令对象来调用接收者的方法。 命令模式的主要缺点如下&#xff1a; 使用命令模式可能会导致某…

Halcon纹理分析texture_laws/trans_from_rgb

Halcon纹理分析 文章目录 Halcon纹理分析1. 纹理滤波器2. 织物折痕检测 纹理是图像表面的一种灰度变化。有的纹理很规则&#xff0c;会以局部小区域为单元重复出现&#xff0c;而有的纹理则呈现出随机性。对于规则的纹理&#xff0c;可以很容易地从中分辨出重复的区域&#xff…

【BERT】深入理解BERT模型1——模型整体架构介绍

前言 BERT出自论文&#xff1a;《BERT&#xff1a;Pre-training of Deep Bidirectional Transformers for Language Understanding》 2019年 近年来&#xff0c;在自然语言处理领域&#xff0c;BERT模型受到了极为广泛的关注&#xff0c;很多模型中都用到了BERT-base或者是BE…

一个静态网站可以增加什么第三方功能/服务

一个静态网站&#xff0c;无后台功能&#xff0c;怎么增加一些实用功能呢&#xff1f;我们来看看一些免费的第三方服务。 静态页面寄存 Gitee pages/Github pages 都可以&#xff0c;绑定一个域名&#xff0c;版本一提交&#xff0c;直接发布有效果。 评论 每个 URL 页面都…

从零开始学Python系列课程第17课:容器型数据类型之列表(上)

前言 列表算是 Python 中比较常用的一种容器型数据类型&#xff0c;那么什么是列表&#xff0c;列表有什么样的作用致使它在 Python 中这么受欢迎呢&#xff1f;这便是接下来我们要一起讨论的问题。 在不久之前我们讲过变量&#xff0c;我们将数据使用变量保存&#xff0c;但是…