Android Debuggerd 簡要介紹和源碼分析(轉載)

來源:互聯網
上載者:User

標籤:

    轉載: http://dylangao.com/2014/05/16/android-debuggerd-%E7%AE%80%E8%A6%81%E4%BB%8B%E7%BB%8D%E5%92%8C%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/

 

碼字很辛苦,轉載請註明來自Dylan‘s Zone的《Android Debuggerd 簡要介紹和源碼分析》

 

本文以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/debuggerdclass 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:5039adb 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)

輸出

Android Debuggerd 簡要介紹和源碼分析(轉載)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.