C C++最全Android tombstone文件是如何生成的_android配置生成墓碑文件,程序员35岁真的是分水岭吗

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

}


通过linker\_main函数返回可执行程序的开始地址,然后跳转过去执行。我们重点看下linker\_main函数



static ElfW(Addr) linker_main(KernelArgumentBlock& args, const char* exe_to_load) {
ProtectedDataGuard guard;

// Register the debuggerd signal handler.
#ifdef ANDROID
debuggerd_callbacks_t callbacks = {
.get_abort_message = {
return __libc_shared_globals()->abort_msg;
},
.post_dump = &notify_gdb_of_libraries,
};
debuggerd_init(&callbacks);
#endif


重点关注这里,当时android系统的话,则会去初始化debggerd\_init,此函数中会按照信号的默认处理函数的



void debuggerd_init(debuggerd_callbacks_t* callbacks) {

struct sigaction action;
memset(&action, 0, sizeof(action));
sigfillset(&action.sa_mask);
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;
debuggerd_register_handlers(&action);
}

static void attribute((unused)) debuggerd_register_handlers(struct sigaction* action) {
sigaction(SIGABRT, action, nullptr);
sigaction(SIGBUS, action, nullptr);
sigaction(SIGFPE, action, nullptr);
sigaction(SIGILL, action, nullptr);
sigaction(SIGSEGV, action, nullptr);
#if defined(SIGSTKFLT)
sigaction(SIGSTKFLT, action, nullptr);
#endif
sigaction(SIGSYS, action, nullptr);
sigaction(SIGTRAP, action, nullptr);
sigaction(DEBUGGER_SIGNAL, action, nullptr);
}


可以看到这里注册了一些异常信号,而信号的action处理函数是debuggerd\_signal\_handler。


## 当异常发生


比如当Native进程出现了null指针问题,则通过linux内核判断会发生信号,最终信号由debuggerd\_signal\_handler函数处理


#### debuggerd\_signal\_handler



static void debuggerd_signal_handler(int signal_number, siginfo_t* info, void* context) {

// Only allow one thread to handle a signal at a time.
int ret = pthread_mutex_lock(&crash_mutex);
if (ret != 0) {
async_safe_format_log(ANDROID_LOG_INFO, “libc”, “pthread_mutex_lock failed: %s”, strerror(ret));
return;
}

log_signal_summary(info);
}


使用pthread\_mutex\_lock防止同一时间多个线程同时来处理信号,后面则调用log\_signal\_summary函数来打印一些信息



async_safe_format_log(ANDROID_LOG_FATAL, “libc”,
“Fatal signal %d (%s), code %d (%s%s)%s in tid %d (%s), pid %d (%s)”,
info->si_signo, get_signame(info), info->si_code, get_sigcode(info),
sender_desc, addr_desc, __gettid(), thread_name, self_pid, main_thread_name)


这里这么做的目的是防止后面动作出错,最终不能确定是那个进程出错的,此处先打印一些关键信息。可以从logcat中找到对应的信息



libc : Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xdb3fb000 in tid 23051 (.tencent.qqlive), pid 23051 (.tencent.qqlive)


* 信号的num,比如信号11代表的是SIGSEGV
* 信号code,SEGV\_MAPERR,就代表映射出错了
* fault addr,出错时的地址
* tid: 对应的线程ID
* pid: 对应的进程ID,如果一个进程中有好多线程,则每个线程的id是不一样的。



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


接着是通过clone系统调用,clone出一个伪线程pseudothread线程,去dispatch处理信号,这里则等待子进程的开始以及结束


#### debuggerd\_dispatch\_pseudothread



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) {

async_safe_format_buffer(main_tid, sizeof(main_tid), "%d", thread_info->crashing_tid);
async_safe_format_buffer(pseudothread_tid, sizeof(pseudothread_tid), "%d",
                         thread_info->pseudothread_tid);
async_safe_format_buffer(debuggerd_dump_type, sizeof(debuggerd_dump_type), "%d",
                         get_dump_type(thread_info));

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;

}


在伪线程中通过fork去创建子线程,新创建的子线程中调动通过execle系统调用去执行crash\_dump64程序,而父进程则在这里等待crash\_dump进程退出


#### crash\_dump进程



pid_t forkpid = fork();
if (forkpid == -1) {
PLOG(FATAL) << “fork failed”;
} else if (forkpid == 0) {
fork_exit_read.reset();
} else {
// We need the pseudothread to live until we get around to verifying the vm pid against it.
// The last thing it does is block on a waitpid on us, so wait until our child tells us to die.
fork_exit_write.reset();
char buf;
TEMP_FAILURE_RETRY(read(fork_exit_read.get(), &buf, sizeof(buf)));
_exit(0);
}


crash\_dump进程则直接通过fork出一个新进程,父进程通过read去等待子进程,而子进程在继续执行crash\_dump的任务



// Get the process name (aka cmdline).
std::string process_name = get_process_name(g_target_thread);

// Collect the list of open files.
OpenFilesList open_files;
{
ATRACE_NAME(“open files”);
populate_open_files_list(&open_files, g_target_thread);
}


* 获取进程的name,通过/proc/PID/cmdline获取进程的名字
* 获取此进程总共打开了多个文件,通过/proc/PID/fd/就可以获取此进程打开了多少个文件,每个文件都有一个文件描述符fd



{
ATRACE_NAME(“ptrace”);
for (pid_t thread : threads) {
// Trace the pseudothread separately, so we can use different options.
if (thread == pseudothread_tid) {
continue;
}

  if (!ptrace_seize_thread(target_proc_fd, thread, &error)) {
    bool fatal = thread == g_target_thread;
    LOG(fatal ? FATAL : WARNING) << error;
  }

  ThreadInfo info;
  info.pid = target_process;
  info.tid = thread;
  info.uid = getuid();
  info.process_name = process_name;
  info.thread_name = get_thread_name(thread);

  if (!ptrace_interrupt(thread, &info.signo)) {
    PLOG(WARNING) << "failed to ptrace interrupt thread " << thread;
    ptrace(PTRACE_DETACH, thread, 0, 0);
    continue;
  }

  if (thread == g_target_thread) {
    // Read the thread's registers along with the rest of the crash info out of the pipe.
    ReadCrashInfo(input_pipe, &siginfo, &info.registers, &abort_msg_address,
                  &fdsan_table_address);
    info.siginfo = &siginfo;
    info.signo = info.siginfo->si_signo;
  } else {
    info.registers.reset(unwindstack::Regs::RemoteGet(thread));
    if (!info.registers) {
      PLOG(WARNING) << "failed to fetch registers for thread " << thread;
      ptrace(PTRACE_DETACH, thread, 0, 0);
      continue;
    }
  }

  thread_info[thread] = std::move(info);
}

}


for循环遍历这个进程中的所有线程,对每一个进程中的线程进程ptrace操作,对目标线程读取其crashinfo。



// Detach from all of our attached threads before resuming.
for (const auto& [tid, thread] : thread_info) {
int resume_signal = thread.signo == DEBUGGER_SIGNAL ? 0 : thread.signo;
if (wait_for_gdb) {
resume_signal = 0;
if (tgkill(target_process, tid, SIGSTOP) != 0) {
PLOG(WARNING) << "failed to send SIGSTOP to " << tid;
}
}

LOG(DEBUG) << "detaching from thread " << tid;
if (ptrace(PTRACE_DETACH, tid, 0, resume_signal) != 0) {
  PLOG(ERROR) << "failed to detach from thread " << tid;
}

}


读取crashinfo完毕后,则对每个thead做detach操作


#### tombstoned\_connect



{
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, dump_type);
}


连接到tombstone进程,通过socket连接的。



// si_value is special when used with DEBUGGER_SIGNAL.
// 0: dump tombstone
// 1: dump backtrace
if (!fatal_signal) {
int si_val = siginfo.si_value.sival_int;
if (si_val == 0) {
backtrace = false;
} else if (si_val == 1) {
backtrace = true;
} else {
LOG(WARNING) << "unknown si_value value " << si_val;
}
}


根据tombstone传递的参数做不同的判断,当参数=0时代表dump tombstone,等于1时,只dump backtrace



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(), fdsan_table_address);
}

{
  ATRACE_NAME("engrave_tombstone");
  engrave_tombstone(std::move(g_output_fd), &unwinder, thread_info, g_target_thread,
                    abort_msg_address, &open_files, &amfd_data);
}

}


最终tombstone是通过engrave\_tombstone来进程生成的。


#### engrave\_tombstone



void engrave_tombstone(unique_fd output_fd, unwindstack::Unwinder* unwinder,
const std::map<pid_t, ThreadInfo>& threads, pid_t target_thread,
uint64_t abort_msg_address, OpenFilesList* open_files,
std::string* amfd_data) {
// don’t copy log messages to tombstone unless this is a dev device
bool want_logs = android::base::GetBoolProperty(“ro.debuggable”, false);

log_t log;
log.current_tid = target_thread;
log.crashed_tid = target_thread;
log.tfd = output_fd.get();
log.amfd_data = amfd_data;

_LOG(&log, logtype::HEADER, “*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***\n”);
dump_header_info(&log);
dump_timestamp(&log, time(nullptr));

auto it = threads.find(target_thread);
if (it == threads.end()) {
LOG(FATAL) << “failed to find target thread”;
}
dump_thread(&log, unwinder, it->second, abort_msg_address, 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, 0, 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);
}


## tombstone文件实例分析


tombstone标志性log开始: "\*\*\* \*\*\* \*\*\* \*\*\* \*\*\* \*\*\* \*\*\* \*\*\* \*\*\* \*\*\* \*\*\* \*\*\* \*\*\* \*\*\* \*\*\* \*\*\*"





#### dump\_header\_info打印头信息



static void dump_header_info(log_t* log) {
auto fingerprint = GetProperty(“ro.build.fingerprint”, “unknown”);
auto revision = GetProperty(“ro.revision”, “unknown”);

_LOG(log, logtype::HEADER, “Build fingerprint: ‘%s’\n”, fingerprint.c_str());
_LOG(log, logtype::HEADER, “Revision: ‘%s’\n”, revision.c_str());
_LOG(log, logtype::HEADER, “ABI: ‘%s’\n”, ABI_STRING);
}


示例如下:



Build fingerprint: ‘dev/good_dev/2020.3.6_china_dev_test:user/test-keys’
Revision: ‘0’
ABI: ‘arm’


#### dump\_timestamp打印时间信息



static void dump_timestamp(log_t* log, time_t time) {
struct tm tm;
localtime_r(&time, &tm);

char buf[strlen(“1970-01-01 00:00:00+0830”) + 1];
strftime(buf, sizeof(buf), “%F %T%z”, &tm);
_LOG(log, logtype::HEADER, “Timestamp: %s\n”, buf);
}


打印出差的时间,示例如下:



Timestamp: 2020-03-07 02:46:27+0800


#### dump\_thread\_info打印thread信息



static void dump_thread_info(log_t* log, const ThreadInfo& thread_info) {
// Blacklist logd, logd.reader, logd.writer, logd.auditd, logd.control …
// TODO: Why is this controlled by thread name?
if (thread_info.thread_name == “logd” ||
android::base::StartsWith(thread_info.thread_name, “logd.”)) {
log->should_retrieve_logcat = false;
}

_LOG(log, logtype::HEADER, “pid: %d, tid: %d, name: %s >>> %s <<<\n”, thread_info.pid,
thread_info.tid, thread_info.thread_name.c_str(), thread_info.process_name.c_str());
_LOG(log, logtype::HEADER, “uid: %d\n”, thread_info.uid);
}


示例如下:



pid: 23051, tid: 23051, name: .tencent.qqlive >>> com.tencent.qqlive <<<
uid: 10256


#### dump\_signal\_info打印信号信息




![img](https://img-blog.csdnimg.cn/img_convert/70772fc4bc32439841462d42dca277ab.png)
![img](https://img-blog.csdnimg.cn/img_convert/7b45de1bc4b95109731e595afc974987.png)

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618668825)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

id);
}

示例如下:

pid: 23051, tid: 23051, name: .tencent.qqlive  >>> com.tencent.qqlive <<<
uid: 10256
dump_signal_info打印信号信息

[外链图片转存中…(img-dXWJJEza-1715681080664)]
[外链图片转存中…(img-ub4ySEob-1715681080664)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 22
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值