androiddebuggerd简要介绍和源码分析(转载)(代码片段)

yunshouhu yunshouhu     2022-12-13     325

关键词:

本文以android4.1为基础,分析debuggerd这个工具的使用方法和源码。

1.Debuggerd 简介

debuggerd是一个daemon进程,在系统启动时随着init进程启动。主要负责将进程运行时的信息dump到文件或者控制台中。

1.1 debuggerd的运行原理

  • 创建一个名为 “Android:debuggerd”的socket,作为server端等待其他client端进程的连接
  • 接收client端进程发送来的tid和action信息
  • 将由tid指定的那个进程的运行信息,按照由action指定的动作dump到文件或者控制台中

可以作为debuggerd的client端的进程主要有几种:

  • 异常的C/C++程序

这种程序由bionic的linker安装异常信号的处理函数,当程序产生异常信号时,进入信号处理函数,与debuggerd建立。

  • debuggerd程序

debuggerd可以在控制台中以命令debuggerd -b [<tid>]启动 ,然后与debuggerd daemon建立连接。这样debuggerd可以在不中断进程执行的情况下dump由tid指定的进程的信息。

  • callstack/dumpstate

控制台中运行命令callstack/dumpstate,并指定必要的参数,命令中会调用dump_backtrace_to_file与debuggerd交互

1.2 debuggerd的使用方法

  • 产生异常信号的C/C++程序与debuggerd建立连接后,debuggerd将进程信息dump到tombstone_XX文件中保存到/data/tombstone/文件夹下。可通过查看tombstone_XX分析异常进程的堆栈信息
  • 在控制台中以命令debuggerd -b [<tid>]启动。如果加上-b参数,则由tid指定的进程的信息将dump到控制台上,否则dump到tombstone文件中
  • 控制台中运行命令callstack/dumpstate,进程信息会写入这两个命令指定的文件中

1.3 tombstone文件的分析方法

使用prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.6/bin下的工具

  1. arm-linux-androideabi-addr2line  将类似libxxx.so 0x00012345的调用栈16进制值翻译成文件名和函数名 arm-linux-androideabi-addr2line -e libxxx.so 0x00012345
  2. arm-linux-androideabi-nm 列出文件的符号信息 arm-linux-androideabi-nm -l -C -n -S libdvm.so > dvm.data
  3. arm-linux-androideabi-objdump 列出文件的详细信息 arm-eabi-objdump -C -d libc.so > libc.s

通过以上工具的分析 ,可以得到较完整的调用栈以及调用逻辑的汇编码

tombstone文件的分析的实用工具和参考文章:

  1. tombstones.py Python script to retrieve line info from an android tombstone using ndk-stack and addr2line. https://github.com/feichh/android-tombstones
  2. android-ndk-stacktrace-analyzer A simple tool for analyzing the stack trace generated from crashing native code in the android system.  http://code.google.com/p/android-ndk-stacktrace-analyzer/
  3. How to read Android crash log and stack trace  This article briefly explains the structure of the log, how to read it. http://bootloader.wikidot.com/linux:android:crashlog

1.4 Debuggerd 框架图

2.linker中debugger.c源码分析

bionic libc,是一种 C 标准函式库, 用于 Android 嵌入式系统上。bionic库中的链接器会对以下七种信号设置Handler(debugger_signal_handler):

  • SIGILL(非法指令异常)
  • SIGABRT(abort退出异常)
  • SIGBUS(硬件访问异常)
  • SIGFPE(浮点运算异常)
  • SIGSEGV(内存访问异常)
  • SIGSTKFLT(协处理器栈异常)
  • SIGPIPE(管道异常)

链接到bionic库上的C/C++程序崩溃时,内核会发送相应的signal,进程收到异常信号后,会转入debugger_signal_handler函数中进行处理。

debugger_init函数(见文件bionic/linker/debugger.c)中设置信号处理函数

void debugger_init()

    struct sigaction act;
    memset(&act, 0, sizeof(act));
    act.sa_sigaction = debugger_signal_handler;
    act.sa_flags = SA_RESTART | SA_SIGINFO;
    sigemptyset(&act.sa_mask);
    sigaction(SIGILL, &act, NULL);
    sigaction(SIGABRT, &act, NULL);
    sigaction(SIGBUS, &act, NULL);
    sigaction(SIGFPE, &act, NULL);
    sigaction(SIGSEGV, &act, NULL);
    sigaction(SIGSTKFLT, &act, NULL);
    sigaction(SIGPIPE, &act, NULL);

debugger_init中act.sa_flags = SA_RESTART | SA_SIGINFO的涵义:

  • SA_RESTART

如果指定该参数,表示若信号中断了进程的某个系统调用,则系统自动启动该系统调用。如果不指定该参数,则被中断的系统调用返回失败,错误码为 EINTR。这个标志位只要用于处理慢系统调用(可能会被阻塞的系统调用)。比如调用write系统调用写某个设备被阻塞,这时进程捕获某个信号且进入相 应信号处理函数返回时,该系统调用可能要返回ENINTR错误。指定这个参数后,系统调用会重启,与RETRY_ON_EINTR宏配合使用则可以保证写 操作的完成

  • SA_SIGINFO

如果指定该参数,表示信号附带的参数(siginfo_t结构体)可以被传递到信号处理函数中。

debugger_signal_handler函数处理流程

  • debugger_signal_handler函数分析:调用logSignalSummary将signal信息写入文件
  • 调用socket_abstract_client函数与debuggerd建立socket连接
  • 如果连接建立成功,则设置结构体debugger_msg_t,并发送给debuggerd
msg.action = DEBUGGER_ACTION_CRASH;//告诉debuggerd采取何种行
 
msg.tid = tid;//线程号
 
RETRY_ON_EINTR(ret, write(s, &msg, sizeof(msg)));
  • 等待debuggerd的回复,阻塞在下面的调用中,收到回复后接着执行下面的流程
RETRY_ON_EINTR(ret, read(s, &tid, 1));
  • 重新设置信号处理函数为SIG_DFL,即采取默认的动作
signal(n, SIG_DFL);
  • 重新发送信号,进程从当前信号处理函数返回后,会处理这个信号,进行默认的信号处理动作,即中断进程。

logSignalSummary函数分析:

  • 获取异常信号的名字和thread名字,并格式化字符串
  • 调用函数__libc_android_log_write函数,用相关信息填充struct iovec vec[3],此结构体数组的内容最终将写入log文件
  • 函数__libc_android_log_write中,log_id为LOG_ID_MAIN,因此
log_channels[LOG_ID_MAIN] =  __write_to_log_init, -1, "/dev/"LOGGER_LOG_MAIN 
  • log_channels[log_id].logger(log_id, vec)将调用__write_to_log_init
  • 在函数__write_to_log_init中,log文件将写入”/dev/log/main”中,执行写log操作的函数是__write_to_log_kernel

3.Debuggerd 源码分析

debuggerd有两种启动方式:

  • 在init进程中以deamon的方式启动,在init.rc中
service debuggerd /system/bin/debuggerd
class main

以这种方式启动的话,进入main函数后,将调用do_server函数,作为server端为其他进程提供dump进程信息的服务

  • 直接运行system/bin/debuggerd可执行文件,需要指定参数,用法为
debuggerd -b [<tid>] //参数-b表示在控制台中输出backtrace

以这种方式启动的话,进入main函数后,将调用do_explicit_dump函数,与debuggerd daemon通信,将指定进程的信息dump到文件或控制台

Debuggerd框架图

do_server函数流程分析

(以daemon方式启动的时候将会进入这个函数)

1.为异常信号安装处理函数SIG_DFL,即忽略自身出现的错误问题     signal(SIGILL, SIG_DFL);     signal(SIGABRT, SIG_DFL);

signal(SIGBUS, SIG_DFL);
signal(SIGFPE, SIG_DFL);
signal(SIGSEGV, SIG_DFL);
signal(SIGPIPE, SIG_DFL);
signal(SIGSTKFLT, SIG_DFL);

2. 调用下面的代码,建立socket通信的server端

s = socket_local_server(DEBUGGER_SOCKET_NAME,ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_STREAM);

3.进入无限循环中,等待连接请求,相关代码如下

 for(;;) 
        struct sockaddr addr;
        socklen_t alen;
        int fd;
        alen = sizeof(addr);
        fd = accept(s, &addr, &alen);
        if(fd < 0) 
            continue;
        
        fcntl(fd, F_SETFD, FD_CLOEXEC);
        handle_request(fd);
    

最终调用handle_request处理socket上的请求

handle_request函数流程分析

1.调用read_request处理socket上由client端进程发送来的数据,处理流程为

<1.1>首先调用函数getsockopt给结构体变量ucred cr赋值

     
getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cr, &len);

ucred存储了socket的client端进程的pid uid gid

<1.2>从socket上读取debugger_msg_t结构体

<1.3> 给debugger_request_t* out_request 赋值

out_request->action = msg.action;
out_request->tid = msg.tid;
out_request->pid = cr.pid;
out_request->uid = cr.uid;
out_request->gid = cr.gid;

<1.4>如果debugger_msg_t中设置的action为DEBUGGER_ACTION_CRASH,说明是crash的C/C++进程发来的请求,则判断传进来的tid是否有效

<1.5>如果debugger_msg_t中设置的action为

DEBUGGER_ACTION_DUMP_BACKTRACE或者    
DEBUGGER_ACTION_DUMP_BACKTRACE_TO_LOG

说明是其他方式(debuggerd/callstack)发来的请求,则要求必须为root权限或者system权         限,然后再判断tid是否有效

2.从read_request返回后,调用ptrace函数attach由tid指定的进程,此时debuggerd将变为被attache进程 的父进程,然后ptrace函数会向子进程发送SIGSTOP信号将子进程停下来。此时,父进程有机会检查子进程核心image和寄存器的值

3.调用下面的语句给client端子进程回复消息,使clinet端的进程能从read调用中返回

TEMP_FAILURE_RETRY(write(fd, "\\0", 1)

4.在for循环中等待子进程停止

int signal = wait_for_signal(request.tid, &total_sleep_time_usec);

子进程收到SIGSTOP信号时,根据不同的action进行动作

if (request.action == DEBUGGER_ACTION_DUMP_TOMBSTONE) 
         tombstone_path = engrave_tombstone(request.pid, request.tid,
                                    signal, true, true, &detach_failed,
                                    &total_sleep_time_usec);
 else if (request.action == DEBUGGER_ACTION_DUMP_BACKTRACE) 
         dump_backtrace(fd, request.pid, request.tid, &detach_failed,
                                    &total_sleep_time_usec);
 else if (request.action == DEBUGGER_ACTION_DUMP_BACKTRACE_TO_LOG) 
         dump_backtrace_for_thread(fd, request.pid, request.tid, &detach_failed,
                                    &total_sleep_time_usec);

子进程收到七种异常信号时,调用函数engrave_tombstone进行处理 Notes:收到SIGSTOP说明与debuggerd通信的不是crash的进程,根据action不同将进程信息写入tombstone文件,或者 写入socket文件并由相应的客户端进程会读取socket文件进行处理。收到七种异常信号说明是crash的进程,调用 engrave_tombstone直接将dump的信息写到tombstone文件中

5.调用ptrace(PTRACE_DETACH, request.tid, 0, 0)解除对子进程的追踪

6.调用kill(request.pid, SIGCONT)恢复被停止的子进程,并让其自然终止

关于attach_gdb的说明

1.如果运行了类似以下指令:

adb shell setprop debug.db.uid 10000

则所有uid<10000的进程crash的时候attach_gdb为true,crash的进程将停止

2.调用ptrace(PTRACE_DETACH, request.tid, 0, 0) 解除对子进程的追踪后,开始等待gdb的连接

adb forward tcp:5039 tcp:5039
adb shell gdbserver :5039 --attach pid &

3.用户按下HOME或者VOLUME DOWN按键,可以使进程继续进行,自然crash 4.attach_gdb为false时,只会解除对子进程的追踪

engrave_tombstone函数流程分析

对于crash的C/C++进程,主要通过这个函数dump进程信息

1.创建”/data/tombstones”文件夹并修改权限

2.调用函数find_and_open_tombstone,tombstone_XX文件最多10个,超过则覆盖最早的

3.调用dump_crash将所有信息dump到tombstone文件

4.如果isSystemServerCrash(crash的进程是gaiad)或isSystemFinalizerTimeout为 true,则运行logcat命令将log缓冲区(/dev/log下的system,events ,radio,main文件)中的内容输出到tombstone文件中

dump_crash函数流程分析

1.dump_build_info(log)

dump “Build fingerprint”

2.dump_fault_addr(log, tid, signal) 调用ptrace(PTRACE_GETSIGINFO, tid, 0, &si)dump siginfo信息

3.dump_thread(context, log, tid, true, total_sleep_time_usec)

dump进程的上下文信息

4.dump_maps(log, pid)

dump /proc/pid/maps 中的信息

5.dump_smaps(log, pid)

dump /proc/pid/smaps 中的信息

6.dump_status(log, pid)

dump /proc/pid/status中的信息

7.dump_sibling_thread_report(context, log, pid, tid, total_sleep_time_usec)

do_explicit_dump函数流程分析

(在命令行中加参数启动的时候将进入这个函数)

判断dump_backtrace为true,调用dump_backtrace_to_file

(1) 调用socket_local_client与debuggerd deamon建立连接

(2) 给debugger_msg_t变量赋值,并发送给debuggerd

     debugger_msg_t msg;
     msg.tid = tid;
     msg.action = DEBUGGER_ACTION_DUMP_BACKTRACE;

(3)等待debuggerd 回复,并读取dump的信息,写入控制台中

 while ((n = TEMP_FAILURE_RETRY(read(s, buffer, sizeof(buffer)))) > 0) 
                if (TEMP_FAILURE_RETRY(write(fd, buffer, n)) != n) 
                    result = -1;
                    break;
                
     

判断dump_backtrace为false,调用dump_tombstone

(1) 调用socket_local_client与debuggerd deamon建立连接

(2) 给debugger_msg_t变量赋值,并发送给debuggerd

     debugger_msg_t msg;
     msg.tid = tid;
     msg.action = DEBUGGER_ACTION_DUMP_TOMBSTONE;

(3)等待debuggerd 回复,调用

TEMP_FAILURE_RETRY(read(s, pathbuf, pathlen - 1))

这里和

     
handle_request的write(fd, tombstone_path, strlen(tombstone_path))

相互呼应 read读到的是tombstone_path,赋值给pathbuf后由do_explicit_dump调用

fprintf(stderr, "Tombstone written to: %s\\n", tombstone_path)

扩散模型(diffusionmodel)简要介绍与源码分析(代码片段)

扩散模型(DiffusionModel)简要介绍与源码分析前言近期同事分享了DiffusionModel,这才发现生成模型的发展已经到了如此惊人的地步,OpenAI推出的Dall-E2可以根据文本描述生成极为逼真的图像,质量之高直让人惊呼哇塞.今早公众号给我推送... 查看详情

关联分析简要介绍

关联分析 概念: 关联分析该方法是以长期重组后保留下来的基因(位点)间连锁不平衡(LD)为基础,在获得群体表型数据和基因型数据之后,采用统计方法检测遗传多态性和性状可遗传变异之间的关联,目标是寻找性... 查看详情

大数据漏斗分析之简要介绍

查看详情

elasticsearch之client源码简要分析

问题让我们带着问题去学习,效率会更高1 es集群只配置一个节点,client是否能够自动发现集群中的所有节点?是如何发现的?2 esclient如何做到负载均衡?3 一个esnode挂掉之后,esclient如何摘掉该节点?4 esclientnode... 查看详情

threadlocal介绍以及源码分析(代码片段)

...数据库事务操作,还有MVC框架中数据跨层传递。这里我们简要探讨下 ThreadLocal 的内部实现及可能存在的问题。首先问自己一 查看详情

eventbus3.0源码简要分析

EvenBus可以在不同模块间传递信息,减少接口的使用。一、使用例子<spanstyle="font-size:18px;">publicclassMainActivityextendsAppCompatActivity{@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(s 查看详情

防火墙之安全技术和防火墙简要介绍

1安全技术.入侵检测与管理系统(IntrusionDetectionSystems):特点是不阻断任何网络访问,量化、定位来自内外网络的威胁情况,主要以提供报告和事后监督为主,提供有针对性的指导措施和安全决策依据。一般采用旁路部署方式,... 查看详情

java8集合框架——hashset源码分析

本文的目录结构:一、HashSet的Javadoc文档注释和简要说明二、HashSet的内部实现:内部属性和构造函数三、HashSet的add操作和扩容四、HashSet的remove操作 一、HashSet的Javadoc文档注释和简要说明  截个图,然后来观摩HashSet的javadoc... 查看详情

转帖webrtc回声抵消模块简要分析

webrtc 的回声抵消(aec、aecm)算法主要包括以下几个重要模块:回声时延估计;NLMS(归一化最小均方自适应算法);NLP(非线性滤波);CNG(舒适噪声产生)。一般经典aec算法还应包括双端检测(DT)。考虑到webrtc使用的NLMS、NLP和CNG都... 查看详情

《简易新闻》源码分析(代码片段)

...liuling开发的基于MaterialDesign和MVP的《简易新闻》源码进行简要分析,通过本文你将学到:阅读应用源码的步骤RecyclerViewNavigationView下拉刷新和上拉加载Material过渡动画CollapsingToolbarLayout1.寻找入口分析一个应用就是从MainActivity下... 查看详情

u-boot源码汇编段简要分析

  Hi,大家好!我是CrazyCatJack,你们可以叫我CCJ或者疯猫。今天我给大家带来的是u-boot的源代码汇编段分析,以后还会给大家讲解后续的C代码,请持续关注哦^_^  先简单说一下u-boot,在嵌入式开发中,u-boot起着至关重要的作用... 查看详情

vue2源码框架和流程分析(代码片段)

...出这篇文章。。。。本文对vue的整体框架和整体流程进行简要的分析,不对某些具体的细节进行分析,所有需要对vue有初步的认识,包括对Object.defineProperty、虚拟DOM有一定了解,本文不会对Object.defineProper 查看详情

ffmpeg之ffplay源码简要分析(代码片段)

1ffplay基本架构1.1视频解码播放的基本流程  ffmpeg视频解码播放的基本流程如下图所示:首先对网络媒体数据流进行解封装得到一般的视频封装格式比如MP4等,如果是本地播放的媒体文件就不需要解协议;然后对视... 查看详情

activemq笔记:源码分析

...sportConnector和NetworkConnector等几个重要的模块的代码做一个简要的分析。启动过程如果要快速地了解系统的主要模块,最好的办法是熟悉该系统的启动过程。本文首先分析ActiveMQ的启动过程。 ActiveMQ可以作为一个独立的Java程序... 查看详情

stack和vector源码分析

...ck继承关系4.Stack的主要方法5.Stack源码Vector源码分析1.vector介绍2.vector的关系图3.vector和ArrayList的关系4.Vector的大致图像5.Vector的源码stack源码分析1.Stack是什么Stack是栈。它的 查看详情

libevent源码分析-介绍安装使用

Libevent介绍安装样例Libevent介绍在includeevent2event.h中有关于Libevent的介绍,这里简单翻译介绍一下:Libevent是以事件为驱动的开发可扩展的网络服务端的库。开放的API设置事件的回调函数,当事件来暂时调用这个回调函数。它还支... 查看详情

最新android大厂高频面试题解析大全

...一章Android相关1.Android进程间通信(IPC)机制Binder简要介绍和学习计划2.Activity的启动方式和flag详解3.Android源码分析-资源加载机制4.Android中Thread、Handler、Looper、MessageQueue的原理分析5.Android源码解析之setContentView6.AndroidAsyncTa... 查看详情

ffmpeg源码分析与命令实战和代码实战(代码片段)

...推流/拉流的相关处理,其播放器相关开发。本文件将简要介绍一下FFMPEG库的基本目录结构及其功能,然后详细介绍一下我们在日常工作中,如何使用ffmpeg提供的工具处理音视频文件最后我们将以一个Xcode环境下利用FFM... 查看详情