【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这个符号(函数)

cpp 复制代码
//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。

cpp 复制代码
//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函数。

cpp 复制代码
//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。

cpp 复制代码
//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属性来控制注册过程。

cpp 复制代码
//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这个函数(

cpp 复制代码
//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函数。

cpp 复制代码
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

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);
    }
  }
}

总结

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

相关推荐
还鮟1 小时前
CTF Web的数组巧用
android
小蜜蜂嗡嗡2 小时前
Android Studio flutter项目运行、打包时间太长
android·flutter·android studio
aqi002 小时前
FFmpeg开发笔记(七十一)使用国产的QPlayer2实现双播放器观看视频
android·ffmpeg·音视频·流媒体
zhangphil4 小时前
Android理解onTrimMemory中ComponentCallbacks2的内存警戒水位线值
android
你过来啊你4 小时前
Android View的绘制原理详解
android
移动开发者1号7 小时前
使用 Android App Bundle 极致压缩应用体积
android·kotlin
移动开发者1号7 小时前
构建高可用线上性能监控体系:从原理到实战
android·kotlin
ii_best12 小时前
按键精灵支持安卓14、15系统,兼容64位环境开发辅助工具
android
美狐美颜sdk12 小时前
跨平台直播美颜SDK集成实录:Android/iOS如何适配贴纸功能
android·人工智能·ios·架构·音视频·美颜sdk·第三方美颜sdk
恋猫de小郭17 小时前
Meta 宣布加入 Kotlin 基金会,将为 Kotlin 和 Android 生态提供全新支持
android·开发语言·ios·kotlin