1. 程式人生 > >Android debuggerd 原始碼分析

Android debuggerd 原始碼分析

debuggerd 簡介

Android系統自帶一個實用的程式異常退出的診斷daemon debuggerd。此程序可以偵測到程式崩潰,並將崩潰時的程序狀態資訊輸出到檔案和串列埠中,以供開發人員分析除錯使用。Debuggerd的資料被儲存在/data/tombstone/目錄下,共可儲存10個檔案,當超過10個時,會覆蓋重寫最早生產的檔案。串列埠中,則直接用DEBUG的tag,輸出logcat資訊。 Linux kernel有自己的一套signal機制,在應用程式崩潰時,通常系統核心都會發送signal到出問題的程序,以通知程序出現什麼異常,這些程序可以捕獲這些signal並對其做相應的處理。通常對於程式異常訊號的處理,就是退出。Android在此機制上實現了一個更實用的功能:攔截這些訊號,dump程序資訊以供除錯。

debuggerd的執行原理

debuggerd建立一個名為 “Android:debuggerd”的socket,作為server端等待其他client端程序的連線,接收client端程序傳送來的tid和action資訊將由tid指定的那個程序的執行資訊,按照由action指定的動作dump到檔案或者控制檯中可以作為debuggerd的client端的程序主要有幾種:

1. 異常的C/C++程式

這種程式由bionic的linker安裝異常訊號的處理函式,當程式產生異常訊號時,進入訊號處理函式,與debuggerd建立。

2. debuggerd程式

debuggerd可以在控制檯中以命令debuggerd -b []啟動 ,然後與debuggerd daemon建立連線。這樣debuggerd可以在不中斷程序執行的情況下dump由tid指定的程序的資訊。

3. dumpstate

控制檯中執行命令dumpstate,並指定必要的引數,命令中會呼叫dump_backtrace_to_file與debuggerd互動。

debuggerd的使用方法

產生異常訊號的C/C++程式與debuggerd建立連線後,debuggerd將程序資訊dump到tombstone_XX檔案中儲存到/data/tombstone/資料夾下。可通過檢視tombstone_XX分析異常程序的堆疊資訊。

在控制檯中以命令debuggerd -b []啟動。如果加上-b引數,則由tid指定的程序的資訊將dump到控制檯上,否則dump到tombstone檔案中。控制檯中執行命令callstack/dumpstate,程序資訊會寫入這兩個命令指定的檔案中。

應用程式異常處理過程

應用程式入口屬於bionic實現的一部分,則對所有android的程式有效。在應用程式入口地址__start後,__linker_init中呼叫debugger_init()函式來註冊異常訊號處理handler,以實現攔截系統異常的幾個singal:SIGILL,SIGABRT, SIGBUS, SIGFPE,SIGSEGV和SIGPIPE:

linker/arch/arm/begin.S

start:          mov     r0, sp          mov     r1, # 0          bl      __linker_init

bionic\linker\ Linker.cpp

extern "C" Elf32_Addr __linker_init( void * raw_args) {       Elf32_Addr start_address = __linker_init_post_relocation(args, linker_addr);    set_soinfo_pool_protection(PROT_READ);    // Return the address that the calling assembly stub should jump to.    return start_address; } static Elf32_Addr __linker_init_post_relocation(KernelArgumentBlock& args, Elf32_Addr linker_base) {       ...      debuggerd_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); }

bionic庫中的連結器會對以下七種訊號設定Handler(debugger_signal_handler):

SIGILL(非法指令異常) SIGABRT(abort退出異常) SIGBUS(硬體訪問異常) SIGFPE(浮點運算異常) SIGSEGV(記憶體訪問異常) SIGSTKFLT(協處理器棧異常) SIGPIPE(管道異常)

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

1) SA_RESTART

如果指定該引數,表示若訊號中斷了程序的某個系統呼叫,則系統自動啟動該系統呼叫。如果不指定該引數,則被中斷的系統呼叫返回失敗,錯誤碼為EINTR。這個標誌位只要用於處理慢系統呼叫(可能會被阻塞的系統呼叫)。比如呼叫write系統呼叫寫某個裝置被阻塞,這時程序捕獲某個訊號且進入相應訊號處理函式返回時,該系統呼叫可能要返回ENINTR錯誤。指定這個引數後,系統呼叫會重啟,與RETRY_ON_EINTR巨集配合使用則可以保證寫操作的完成

2) SA_SIGINFO

如果指定該引數,表示訊號附帶的引數(siginfo_t結構體)可以被傳遞到訊號處理函式中。

連結到bionic庫上的C/C++程式崩潰時,核心會發送相應的signal,程序收到異常訊號後,會轉入debugger_signal_handler函式中進行處理。

void debugger_signal_handler( int n, siginfo_t* info, void * unused) {      char msgbuf[ 128 ];      unsigned tid;      int s;        logSignalSummary(n, info);         tid = gettid();      //"android:debuggerd"      s = socket_abstract_client(DEBUGGER_SOCKET_NAME, SOCK_STREAM);         if (s >= 0 ) {          /* debugger knows our pid from the credentials on the           * local socket but we need to tell it our tid.  It           * is paranoid and will verify that we are giving a tid           * that's actually in our process           */          int  ret;          debugger_msg_t msg;          msg.action = DEBUGGER_ACTION_CRASH;          msg.tid = tid;          RETRY_ON_EINTR(ret, write(s, &msg, sizeof(msg)));          if (ret == sizeof(msg)) {              /* if the write failed, there is no point to read on               * the file descriptor. */              RETRY_ON_EINTR(ret, read(s, &tid, 1 ));              int savedErrno = errno;              notify_gdb_of_libraries();              errno = savedErrno;          }             if (ret < 0 ) {              /* read or write failed -- broken connection? */              format_buffer(msgbuf, sizeof(msgbuf),                  "Failed while talking to debuggerd: %s" , strerror(errno));              __libc_android_log_write(ANDROID_LOG_FATAL, "libc" , msgbuf);          }             close(s);      } else {          /* socket failed; maybe process ran out of fds */          format_buffer(msgbuf, sizeof(msgbuf),              "Unable to open connection to debuggerd: %s" , strerror(errno));          __libc_android_log_write(ANDROID_LOG_FATAL, "libc" , msgbuf);      }         /* remove our net so we fault for real when we return */      signal(n, SIG_DFL);         /*       * These signals are not re-thrown when we resume.  This means that       * crashing due to (say) SIGPIPE doesn't work the way you'd expect it       * to.  We work around this by throwing them manually.  We don't want       * to do this for *all* signals because it'll screw up the address for       * faults like SIGSEGV.       */      switch (n) {          case SIGABRT:          case SIGFPE:          case SIGPIPE:          case SIGSTKFLT:              ( void ) tgkill(getpid(), gettid(), n);              break ;          default :    // SIGILL, SIGBUS, SIGSEGV              break ;      } }

debugger_signal_handler函式處理流程:

1) 呼叫logSignalSummary將signal資訊寫入檔案;

static void logSignalSummary( int signum, const siginfo_t* info) {      char buffer[ 128 ];      char threadname[MAX_TASK_NAME_LEN + 1 ]; // one more for termination      char * signame;      switch (signum) {          case SIGILL:    signame = "SIGILL" ;     break ;          case SIGABRT:   signame = "SIGABRT" ;    break ;          case SIGBUS:    signame = "SIGBUS" ;     break ;          case SIGFPE:    signame = "SIGFPE" ;     break ;          case SIGSEGV:   signame = "SIGSEGV" ;    break ;          case SIGSTKFLT: signame = "SIGSTKFLT" break ;          case SIGPIPE:   signame = "SIGPIPE" ;    break ;          default :        signame = "???" ;        break ;      }        if (prctl(PR_GET_NAME, (unsigned long )threadname, 0 , 0 , 0 ) != 0 ) {          strcpy(threadname, "<name unknown=" ">" );      } else {          // short names are null terminated by prctl, but the manpage          // implies that 16 byte names are not.          threadname[MAX_TASK_NAME_LEN] = 0 ;      }      format_buffer(buffer, sizeof(buffer),          "Fatal signal %d (%s) at 0x%08x (code=%d), thread %d (%s)" ,          signum, signame, info->si_addr, info->si_code, gettid(), threadname);        __libc_android_log_write(ANDROID_LOG_FATAL, "libc" , buffer); } </name>

獲取異常訊號的名字和thread名字,並格式化字串,呼叫函式__libc_android_log_write函式寫入”/dev/log/main”中。

2) 呼叫socket_abstract_client函式與debuggerd建立socket連線;

s = socket_abstract_client(DEBUGGER_SOCKET_NAME, SOCK_STREAM);

3) 如果連線建立成功,則設定結構體debugger_msg_t,併發送給debuggerd

msg.action = DEBUGGER_ACTION_CRASH; //告訴debuggerd採取何種行 msg.tid = tid; //執行緒號 RETRY_ON_EINTR(ret, write(s, &msg, sizeof(msg)));

4) 等待debuggerd的回覆,阻塞在下面的呼叫中,收到回覆後接著執行下面的流程;


RETRY_ON_EINTR(ret, read(s, &tid, 1 ));5) 重新設定訊號處理函式為SIG_DFL,即採取預設的動作;

5) 重新設定訊號處理函式為SIG_DFL,即採取預設的動作;

signal(n, SIG_DFL);

6) 重新發送訊號,程序從當前訊號處理函式返回後,會處理這個訊號,進行預設的訊號處理動作,即中斷程序。

debuggerd的原始碼分析

1. 在init程序中以deamon的方式啟動,在init.rc中

service debuggerd /system/bin/debuggerd class main

以這種方式啟動的話,進入main函式後,將呼叫do_server函式,作為server端為其他程序提供dump程序資訊的服務。

2. 直接執行system/bin/debuggerd可執行檔案,需要指定引數,用法為:

debuggerd -b [<tid>] //引數-b表示在控制檯中輸出backtrace</tid>

以這種方式啟動的話,進入main函式後,將呼叫do_explicit_dump函式與debuggerd daemon通訊,將指定程序的資訊dump到檔案或控制檯。


當啟動debuggerd程序傳遞的引數個數為1時,此時啟動的debuggerd將作為一個後臺服務程序,專門接收應用程式異常退出訊息而產生tombstone。

static int do_server() {      int s;      struct sigaction act;      int logsocket = - 1 ;         /*       * debuggerd crashes can't be reported to debuggerd.  Reset all of the       * crash handlers.       */      signal(SIGILL, SIG_DFL);      signal(SIGABRT, SIG_DFL);      signal(SIGBUS, SIG_DFL);      signal(SIGFPE, SIG_DFL);      signal(SIGSEGV, SIG_DFL);      signal(SIGPIPE, SIG_IGN);      signal(SIGSTKFLT, SIG_DFL);         logsocket = socket_local_client( "logd" ,              ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_DGRAM);      if (logsocket < 0 ) {          logsocket = - 1 ;      } else {          fcntl(logsocket, F_SETFD, FD_CLOEXEC);      }         act.sa_handler = SIG_DFL;      sigemptyset(&act.sa_mask);      sigaddset(&act.sa_mask,SIGCHLD);      act.sa_flags = SA_NOCLDWAIT;      sigaction(SIGCHLD, &act, 0 );         s = socket_local_server(DEBUGGER_SOCKET_NAME,              ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_STREAM);      if (s < 0 ) return 1 ;      fcntl(s, F_SETFD, FD_CLOEXEC);         LOG( "debuggerd: " __DATE__ " " __TIME__ "\n" );         //check corefile limit.      ( void )check_corefile_limit();         for (;;) {          struct sockaddr addr;          socklen_t alen;          int fd;          alen = sizeof(addr);          XLOG( "waiting for connection\n" );          fd = accept(s, &addr, &alen);          if (fd < 0 ) {              XLOG( "accept failed: %s\n" , strerror(errno));              continue ;          }             fcntl(fd, F_SETFD, FD_CLOEXEC);             handle_request(fd);      }      return 0 ; }

1. 忽略debuggerd自身crash的處理;

2. 建立socket通訊的server端;

3. 進入無限迴圈中,等待並接收客戶端程序連線請求,並通過handle_request()函式處理請求;

static void handle_request( int fd) {      XLOG( "handle_request(%d)\n" , fd);         debugger_request_t request;      int status = read_request(fd, &request);      if (!status) {          XLOG( "BOOM: pid=%d uid=%d gid=%d tid=%d\n" ,              request.pid, request.uid, request.gid, request.tid);             /* At this point, the thread that made the request is blocked in           * a read() call.  If the thread has crashed, then this gives us           * time to PTRACE_ATTACH to it before it has a chance to really fault.           *           * The PTRACE_ATTACH sends a SIGSTOP to the target process, but it           * won't necessarily have stopped by the time ptrace() returns.  (We           * currently assume it does.)  We write to the file descriptor to           * ensure that it can run as soon as we call PTRACE_CONT below.           * See details in bionic/libc/linker/debugger.c, in function           * debugger_signal_handler().           */          if (ptrace(PTRACE_ATTACH, request.tid, 0 , 0 )) {              LOG( "ptrace attach failed: %s\n" , strerror(errno));          } else {              bool detach_failed = false ;              bool attach_gdb = should_attach_gdb(&request);              if (TEMP_FAILURE_RETRY(write(fd, "\0" , 1 )) != 1 ) {                  LOG( "failed responding to client: %s\n" , strerror(errno));              } else {                  char * tombstone_path = NULL;                     if (request.action == DEBUGGER_ACTION_CRASH) {                      close(fd);                      fd = - 1 ;                  }                     int total_sleep_time_usec = 0 ;                  for (;;) {                      int signal = wait_for_signal(request.tid, &total_sleep_time_usec);                      if (signal < 0 ) {                          break ;                      }                         switch (signal) {                      case SIGSTOP:                          if (request.action == DEBUGGER_ACTION_DUMP_TOMBSTONE) {                              XLOG( "stopped -- dumping to tombstone\n" );                              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) {                              XLOG( "stopped -- dumping to fd\n" );                              dump_backtrace(fd, request.pid, request.tid, &detach_failed,                                      &total_sleep_time_usec);                          } else {                              XLOG( "stopped -- continuing\n" );                              status = ptrace(PTRACE_CONT, request.tid, 0 , 0 );                              if (status) {                                  LOG( "ptrace continue failed: %s\n" , strerror(errno));                              }                              continue ; /* loop again */                          }

相關推薦

Android debuggerd 原始碼分析

debuggerd 簡介 Android系統自帶一個實用的程式異常退出的診斷daemon debuggerd。此程序可以偵測到程式崩潰,並將崩潰時的程序狀態資訊輸出到檔案和串列埠中,以供開發人員分析除錯使用。Debuggerd的資料被儲存在/data/tombstone/目錄下,共可儲存10個檔案

Android ADB 原始碼分析(三)

前言 之前分析的兩篇文章 Android Adb 原始碼分析(一) 嵌入式Linux:Android root破解原理(二)   寫完之後,都沒有寫到相關的實現程式碼,這篇文章寫下ADB的通訊流程的一些細節 看這篇文章之前,請先閱讀 Linux的SOCKET

Android Adb 原始碼分析

扭起屁股得意洋洋 最近,我負責的專案因為臨近量產,把之前的userdebug版本關閉,轉成了user版本,增加selinux的許可權,大家都洋溢在專案準備量產的興奮和喜悅之中不能自拔 誰知,好景不長,user版本釋出之後,各種bug接踵而來,但是因為user版本許可權的原因,我們之前保留

【輸出文件】 Android MountService 原始碼分析

Android 儲存裝置管理框架 在android之VOLD程序啟動原始碼分析一文中介紹了儲存裝置的管控中心Vold程序,Vold屬於native後臺程序,通過netlink方式接收kernel的uevent訊息,並通過socket方式將uevent訊息傳送給MountService,同時實時接

Android原始碼分析 - LRUCache快取實現原理

一、Android中的快取策略 一般來說,快取策略主要包含快取的新增、獲取和刪除這三類操作。如何新增和獲取快取這個比較好理解,那麼為什麼還要刪除快取呢?這是因為不管是記憶體快取還是硬碟快取,它們的快取大小都是有限的。當快取滿了之後,再想其新增快取,這個時候就需要刪除一些舊的快取

Android原始碼分析 - View事件分發機制

事件分發物件 (1)所有 Touch 事件都被封裝成了 MotionEvent 物件,包括 Touch 的位置、時間、歷史記錄以及第幾個手指(多指觸控)等。 (2)事件型別分為 ACTION_DOWN, ACTION_UP,ACTION_MOVE,ACTION_POINTER_D

Android原始碼分析 - Activity啟動流程

啟動Activity的方式 Activity有2種啟動的方式,一種是在Launcher介面點選應用的圖示、另一種是在應用中通過Intent進行跳轉。我們主要介紹與後者相關的啟動流程。 Intent intent = new Intent(this, TestActivity

Android系統原始碼分析之-ContentProvider

距離上一次寫部落格已經半年多了,這半年發生了很多事情,也有了很多感觸,最主要是改變了忙碌了工作,更加重視身體的健康,為此也把工作地點從深圳這個一線城市換到了珠海,工作相對沒有那麼累,身體感覺也好了很多。所以在工作完成之餘,也有了更多的時間來自我學習和提高,後續會用更多時間來寫更多實用的東西,幫助我們理解

Android CardView原始碼分析

轉載來源:https://www.jianshu.com/p/02c4cdb27109 首先放一張CardView的結構圖 對cardview做一個解析: 首先介紹一下CardView經常使用的屬性 cardview_cardBackgroundC

Android IntentService 原始碼分析

IntentService也算比較常用的一個元件,所以有必要增加對它的瞭解。 首先看看原始碼 public abstract class IntentService extends Service { private volatile Looper mServiceLooper;

Android startActivity原始碼分析

靜下心來打算看看android原始碼,就從startActivity這個最常見的函式開始一步步分析下去。 @Override public void startActivity(Intent intent) { this.startActivity(int

android okhttp原始碼分析

okhttp是Square公司開發貢獻的,其功能強大,現在是安卓主流網路框架。 現在我們就看看這個框架牛逼之處,探究其原始碼。 compile 'com.squareup.okhttp3:okhttp:3.8.0' 我們分析的版本是3.8.0. public class

Android系統原始碼分析--View繪製流程之-setContentView

上一篇分析了四大元件之ContentProvider,這也是四大元件最後一個。因此,從這篇開始我們分析新的篇章--View繪製流程,View繪製流程在Android開發中佔有非常重要的位置,只要有檢視的顯示,都離不開View的繪製,所以瞭解View繪製原理對於應用開發以及系統的學習至關重要。由於View

Android wpa_supplicant原始碼分析--啟動之全域性初始化

1. wpa_supplicant簡介 wpa_supplicant是用來用來支援無線中各種加密方式的,包括WEP、WPA/WPA2和WAPI(中國特有)、EAP(8021x)。wpa_s通過socket與上層(framework)和底層(driver)通訊,向上接收命令和傳

開源中國APP Android原始碼分析系列(一)

簡述 這篇文章是基於OSCHINA Android客戶端4.1.7版本的分析,之前很多人都分析過原始碼,但是都是幾年前的程式碼分析,隨著時間的推移,開源中國的原始碼也在變化,接下來的一段時間我將分享我通過學習開源中國的程式碼所獲得東西。 啟動頁面 研究一個A

android setContentView原始碼分析

         Activity是android的四大元件之一,其重要性不言而喻,而且在我們的開發過程中打交道最多的也是它。在設定檢視的時候,我們一般都是通過setContentView來載入我們的佈局資源的,看起來很簡單的一行程式碼setContentView(),但是實

Android Choreographer原始碼分析

/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file

Android recyclerview原始碼分析(一)

原始碼分析基於22.2.1版本 先預覽一下recyclerview 相關的類   今天先分析SortedList 和SortedListAdapterCallback 先看下這兩個類的用法  SortedList<Object> mDataList=new

Android 7 原始碼分析系列導讀

關於作者 郭孝星,程式設計師,吉他手,主要從事Android平臺基礎架構方面的工作,歡迎交流技術方面的問題,可以去我的Github提issue或者發郵件至[email protected]與我交流。 文章目錄 一 基礎篇 二 工具篇 三

Android EventBus原始碼分析,基於最新3.1.1版本,看這一篇就夠了!!

Android EventBus原始碼分析,基於最新3.1.1版本,看這一篇就夠了!! 前言 上一篇文章對EventBus進行了一個介紹,並且對它的使用方式作了一個較全面的分析,建議在閱讀本文之前,先看看上篇文章的內容:EventBus使用(全面分析,細節提醒) 本篇文章主要