Android開發中,在Java層可以方便的捕獲crashlog,但對於 Native 層的 crashlog 通常無法直接獲取,只能通過系統的logcat來分析crash日志。
做過 Linux 和 Win32 開發的都知道,在pc上程序crash時可以生成 core dump 文件通過相關的工具分析函數調用堆棧及崩潰時的內存信息。
那么作為軟件開發者有沒有方法自己獲取native層的crashlog呢?Android 系統是 Linux 內核,既然在Linux中crash時可以生成dump文件,那么在Android中也是有辦法的。
Linux系統的Crash dump
Linux 棧調用回溯
對 Linux 應用程序而言, 因為有 glibc 庫的支持, 所以構造程序的函數調用鏈相對容易。在 glibc 庫提供的關於堆棧回朔的一系列庫函數中,其核心函數是 backtrace()。它負責遍歷從程序入口點到當前調用點的所有堆棧幀,然后生成函數調用的地址序列。為了完成函數地址和函數名稱的轉換,函數backtrace_symbols() 負責將 backtrace()生成的地址序列轉換成一系列字符串列表,在每個字符串列表中包括了函數名稱,當前指令在函數中的偏移量和函數的返回地址。由於 backtrace_symbols() 需要動態申請空間以保存字符串列表,如果應用程序 crash 時破壞了系統內存,可能導致 backtrace_symbols()結果錯誤。為此,glibc庫還提供了一個更安全的地址轉換函數:backtrace_symbols_fd() 。該函數將生成的字符串直接輸出到外部文件,而不再需要申請新的內存空間。對於 backtrace() 的詳細使用方法可以通過man backtrace 查看。
在Andrid中,由於谷歌沒有使用glibc庫,而是使用了精簡版本的bionic庫,其中並沒有 backtrace() 可用。獲取調用堆棧還需要采用其他方法。
Linux 信號機制
信號機制是 Linux 進程間通信的一種重要方式,Linux 信號一方面用於正常的進程間通信和同步,如任務控制(SIGINT, SIGTSTP,SIGKILL, SIGCONT,……);另一方面,它還負責監控系統異常及中斷。 當應用程序運行異常時, Linux 內核將產生錯誤信號並通知當前進程。 當前進程在接收到該錯誤信號后,可以有三種不同的處理方式。 1. 忽略該信號。
2. 捕捉該信號並執行對應的信號處理函數(signal handler)。
3. 執行該信號的缺省操作(如 SIGTERM, 其缺省操作是終止進程)。
當 Linux 應用程序在執行時發生嚴重錯誤,一般會導致程序 crash。其中,Linux 專門提供了一類 crash 信號,在程序接收到此類信號時,缺省操作是將 crash 的現場信息記錄到 core 文件,然后終止進程。
Crash信號列表
| Signal | Description |
| SIGSEGV | Invalid memory reference. |
| SIGBUS | Access to an undefined portion of a memory object. |
| SIGFPE | Arithmetic operation error, like divide by zero. |
| SIGILL | Illegal instruction, like execute garbage or a privileged instruction |
| SIGSYS | Bad system call. |
| SIGXCPU | CPU time limit exceeded. |
| SIGXFSZ | File size limit exceeded. |
Linux 信號處理 sigaction
#include<signal.h>
int sigaction(int sig, struct sigaction *act , struct sigaction *oact) ;
struct sigaction{
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
}
這個函數可以: 1. 給一個signal安裝一個handler,並且在使用sigaction修改該handler之前,不用reinstall。 2. 使用sigaction結構,該結構包含handler,其中可以指定2個handler,一個是使用sigiinfo_t等參數的handler,即支持給handler更多的參數,使其可以知道自己是被什么進程,那個用戶,發來的什么信號,發來該信號的具體的原因是什么,當然要像這樣,得給sigaction的sa_flags設置SA_SIGINFO標記。 3.使用sigaction的sa_flags標記還可以指定系統調用被這個信號打斷后,是直接返回,還是自動restart. 一個典型就是,一般我們不讓SIGALRM信號將被打斷的系統調用restart,因為SIGALARM一般本來就是用來打斷一個block的調用的。 4. 為了模仿老的signal函數的作用,實現unreliable 的類似signal的操作,可以通過給sa_flags設置SA_RESETHAND使handler不會自動reinstall,以及SA_NODEFER標記來使在本信號的handler內部,本信號不被自動block,當然如果你手動在sa_mask中指定要block本信號的話就可以將其block了。 5. 通過使用sigaction結構中的sa_mask,可以在該handler執行的過程中,block一些信號,注意,這個mask是與我們使用sigprocmask設置的mask不同的mask,這個mask的作用范圍僅限於本handler函數,而且他不會將我們用sigprocmask設置的mask取消,而僅僅是在其基礎上再次將一些信號block掉,當handler結束時,系統會自動將mask恢復成以前的樣子,所以這個sigaction中的sa_mask只作用本信號的handler的執行時間。
一個使用 sigaction 進行信號處理的示例:
#include <signal.h>
void sig_handler_with_arg(int sig,siginfo_t *sig_info,void *unused){……}
int main(int argc,char **argv)
{
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_sigaction = sig_handler_with_arg;
sa.sa_flags = SA_RESETHAND;
sigaction(SIGSEGV, &sa, NULL);
...
}
Android tombstones 分析
Android系統中應用出現nativecrash時,會在 /data/tombstones 目錄下生成 tombstone_xx 的日志文件,記錄了應用crash發生時的內存、寄存器、堆棧信息等。並且通過logcat將其內容輸出。
Android 4.0中tombstones處理部分的源碼位於 /system/core/debuggerd 和 bonic/linker/debugger.c 中。
在 bonic/linker/debugger.c 中的 debugger_init() 中對7個Signal進行了注冊處理,debugger_signal_handler作為信號處理函數。
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_signal_handler 中,通過socket client 與 /system/core/debuggerd 中的socket server進行通信,在/system/core/debuggerd中進行crash進程的分析( handle_crashing_process 函數中),生成tombstones文件(dump_crash_report 函數)。
unwind_backtrace_with_ptrace 函數獲取backtrae,通過 ptrace 讀取寄存器和相關內存地址。
Google Breakpad 項目
Google Breakpad 是Google開源的跨平台崩潰轉儲和分析模塊,他支持Windows,Linux和Mac和Solaris系統,並可以編譯到Android工程中。Google-breakpad的好處在於可以屏蔽了不同平台的差異,使用統一的文件格式記錄和分析符號文件格式和崩潰棧信息。
在Linux系統上,google-breakpad也是通過信號機制來捕獲crash,大致過程可以通過源碼中的注釋了解:
// The signal flow looks like this:
// SignalHandler (uses a global stack of ExceptionHandler objects to find
// | one to handle the signal. If the first rejects it, try
// | the second etc...)
// V
// HandleSignal ----------------------------| (clones a new process which
// | | shares an address space with
// (wait for cloned | the crashed process. This
// process) | allows us to ptrace the crashed
// | | process)
// V V
// (set signal handler to ThreadEntry (static function to bounce
// SIG_DFL and rethrow, | back into the object)
// killing the crashed |
// process) V
// DoDump (writes minidump)
// |
// V
// sys_exit
客戶端中google-breakpad的使用也很簡單,可以參照官方wiki的教程文檔:How To Add Breakpad To Your Linux Application 。
