1. 程式人生 > >Android平臺Native程式碼的崩潰捕獲機制及實現

Android平臺Native程式碼的崩潰捕獲機制及實現

本文地址:http://blog.csdn.net/mba16c35/article/details/54178067

思路主要來源於這篇文章:http://blog.httrack.com/blog/2013/08/23/catching-posix-signals-on-android/

這篇文章的實現在這個地址程式碼但是還要對5.0以上做一些適配。

比較出名的Google Breakpad也提供了跨平臺捕獲native崩潰資訊的功能,但是這個庫太大太複雜了。而coffeecatch這個庫我編譯出來才22k,程式碼量也少,改動起來也很容易。

一、訊號機制

我們知道,函式執行在使用者態,當遇到系統呼叫、中斷或是異常的情況時,程式會進入核心態。訊號涉及到了這兩種狀態之間的轉換,過程可以先看一下下面的示意圖:


訊號處理機制示意圖

1.訊號的接收

接收訊號的任務是由核心代理的,當核心接收到訊號後,會將其放到對應程序的訊號佇列中,同時向程序傳送一箇中斷,使其陷入核心態。

注意,此時訊號還只是在佇列中,對程序來說暫時是不知道有訊號到來的。

2.訊號的檢測

程序陷入核心態後,有兩種場景會對訊號進行檢測:

  • 程序從核心態返回到使用者態前進行訊號檢測
  • 程序在核心態中,從睡眠狀態被喚醒的時候進行訊號檢測

當發現有新訊號時,便會進入下一步,訊號的處理。

3.訊號的處理

訊號處理函式是執行在使用者態的,呼叫處理函式前,核心會將當前核心棧的內容備份拷貝到使用者棧上,並且修改指令暫存器(eip)將其指向訊號處理函式。

接下來程序返回到使用者態中,執行相應的訊號處理函式。

訊號處理函式執行完成後,還需要返回核心態,檢查是否還有其它訊號未處理。如果所有訊號都處理完成,就會將核心棧恢復(從使用者棧的備份拷貝回來),同時恢復指令暫存器(eip)將其指向中斷前的執行位置,最後回到使用者態繼續執行程序。

至此,一個完整的訊號處理流程便結束了,如果同時有多個訊號到達,上面的處理流程會在第2步和第3步驟間重複進行。

所以第一步就是要用訊號處理函式捕獲到native crash(SIGSEGV, SIGBUS等)。在posix系統,可以用sigaction():

  1. <span style="font-size:14px;">struct sigaction sa;  
  2. struct
     sigaction sa_old;  
  3. memset(&sa, 0, sizeof(sa));  
  4. sigemptyset(&sa.sa_mask);  
  5. sa.sa_sigaction = my_handler;  
  6. sa.sa_flags = SA_SIGINFO;  
  7. if (sigaction(sig, &sa, &sa_old) == 0) {  
  8.   ...  
  9. }</span>  

二、如何處理堆疊溢位

一個錯誤來源是堆疊溢位。當棧滿了(太多次遞迴,棧上太多物件),系統會在同一個已經滿了的棧上呼叫SIGSEGV的訊號處理函式,又再一次引起同樣的訊號。幸運的是,你可以使用sigaltstack在任意執行緒註冊一個可選的棧,保留一下在緊急情況下使用的空間。(系統會在危險情況下把棧指標指向這個地方,使你得以在一個新的棧上執行訊號處理函式)

  1. <span style="font-size:14px;">stack_t stack;  
  2. memset(&stack, 0, sizeof(stack));  
  3. /* Reserver the system default stack size. We don't need that much by the way. */
  4. stack.ss_size = SIGSTKSZ;  
  5. stack.ss_sp = malloc(stack.ss_size);  
  6. stack.ss_flags = 0;  
  7. /* Install alternate stack size. Be sure the memory region is valid until you revert it. */
  8. if (stack.ss_sp != NULL && sigaltstack(&stack, NULL) == 0) {  
  9.   ...  
  10. }</span>  

三、相容舊的訊號處理函式

我們在java虛擬機器上執行,某些訊號可能在之前已經被安裝過訊號處理函式。例如,SIGSEGV經常用於處理NullPointerException

或者作為普通的JIT處理(可執行的物理頁標記了“NO ACCESS”的保護,對任何一處非法操作都會喚起JIT編譯器的訊號處理函式)。所以,你必須先呼叫舊的訊號處理函式,以防把上下文環境搞亂。舊的訊號處理函式要麼不進行處理直接返回,要麼呼叫abort()(這樣我們就有最後一次機會通過SIGABRT的訊號處理函式處理這個訊號,所以在捕獲SIGABRT的訊號處理函式裡邊,我們是最後才呼叫舊的訊號處理函式)

  1. <span style="font-size:14px;">staticvoid my_handler(constint code, siginfo_t *const si, void *const sc) {  
  2.   /* Call previous handler. */
  3.   old_handler.sa_sigaction(code, si, sc);  
  4. ...  
  5. }</span>  

四、多執行緒環境

我們執行在一個多執行緒程序環境,我們不想捕獲不屬於我們的其他執行緒的crash。可以用pthread_getspecific得到一個執行緒相關的上下文來解決這個問題。不過,pthread_getspecific不是非同步訊號安全的函式,如果在訊號處理函式中使用,可能有不可預知的問題。

  1. <span style="font-size:14px;">staticvoid my_handler(constint code, siginfo_t *const si, void *const sc) {  
  2.   /* Call previous handler. */
  3.   old_handler.sa_sigaction(code, si, sc);  
  4.   /* Get thread-specific context. */
  5.   my_struct *s = (my_struct*) pthread_getspecific(my_thread_var);  
  6.   if (s != NULL) {  
  7.     ...  
  8.   }   
  9. ...  
  10. }</span>  

五、列印native堆疊

1. 相對地址

我們要收集crash的基本資訊,特別是出錯的地址。

sigaction回撥函式的第三個引數是一個指向ucontext_t的指標,ucontext_t收集了暫存器數值(還有各種處理器特定的資訊)。在x86-64架構,pc值是存在uc_mcontext.gregs[REG_RIP];在arm架構,則是uc_mcontext.arm_pc。不過在Android上,ucontext_t結構體沒有在任何系統標頭檔案定義,所以要自己去引入一份定義。另外還要找到當前pc在哪個二進位制檔案上執行,並且找到該檔案載入在記憶體的起始地址,因為一個隨機的執行地址對除錯是沒有用的。linux系統下的dladdr()函式可以獲得這個資訊(即最近這個地址的符號,以及可以用於計算相對偏移地址的模組的起始地址)。

(另外,你也可以通過讀取linux上的/proc/self/maps,檢查各個模組載入在記憶體的地址範圍,也可以獲得起始地址)

  1. <span style="font-size:14px;">Dl_info info;  
  2. if (dladdr(addr, &info) != 0 && info.dli_fname != NULL) {  
  3.   void * const nearest = info.dli_saddr;  
  4.   constuintptr_t addr_relative =  
  5.     ((uintptr_t) addr - (uintptr_t) info.dli_fbase);  
  6.   ...  
  7. }</span>  

2. 5.0以下使用libcorkscrew

為了獲得在crash上下文提供堆疊資訊,我們呼叫Android系統上的libcorkscrew庫。這個庫只在4.1.1以上,5.0以下的系統存在,可以通過動態載入(dlopen和dlsym())解決這個問題。為了獲得crash堆疊,引入函式unwind_backtrace_signal_arch和acquire_my_map_info_list()。

  1. <span style="font-size:14px;">/* 
  2. * Describes a single frame of a backtrace. 
  3. */
  4. typedefstruct {  
  5.     uintptr_t absolute_pc; /* absolute PC offset */
  6.     uintptr_t stack_top; /* top of stack for this frame */
  7.     size_t stack_size; /* size of this stack frame */
  8. } backtrace_frame_t;  
  9. ssize_t unwind_backtrace_signal_arch(siginfo_t* siginfo, void* sigcontext,  
  10.         const map_info_t* map_info_list,  
  11.         backtrace_frame_t* backtrace, size_t ignore_depth, size_t max_depth);</span>  

3. 5.0以上使用libunwind

那對5.0以上的系統怎麼辦呢?

5.0以上的系統使用了libunwind來代替libcorkscrew,coffee的作者實現如下:

  1. <span style="font-size:14px;">/* Use libunwind to get a backtrace inside a signal handler. 
  2.    Will only return a non-zero code on Android >= 5 (with libunwind.so 
  3.    being shipped) */
  4. #ifdef USE_LIBUNWIND
  5. static ssize_t coffeecatch_unwind_signal(siginfo_t* si, void* sc,   
  6.                                          void** frames,  
  7.                                          size_t ignore_depth,  
  8.                                          size_t max_depth) {  
  9.   void *libunwind = dlopen("libunwind.so", RTLD_LAZY | RTLD_LOCAL);  
  10.   if (libunwind != NULL) {  
  11.     int (*backtrace)(void **buffer, int size) =  
  12.       dlsym(libunwind, "unw_backtrace");  
  13.     if (backtrace != NULL) {  
  14.       int nb = backtrace(frames, max_depth);  
  15.       if (nb > 0) {  
  16.       }  
  17.       return nb;  
  18.     } else {  
  19.       DEBUG(print("symbols not found in libunwind.so\n"));  
  20.     }  
  21.     dlclose(libunwind);  
  22.   } else {  
  23.     DEBUG(print("libunwind.so could not be loaded\n"));  
  24.   }  
  25.   return -1;  
  26. }  
  27. #endif</span>

3.直接使用libunwind的unw_backtrace只能列印呼叫者堆疊,需要額外處理

解決方法來自:

https://lightinginthedark.wordpress.com/2015/05/15/native-stack-traces-on-android-lollipop-with-libunwind/

在我的測試中這樣呼叫libunwind只能打印出呼叫coffeecatch_unwind_signal函式的呼叫者的堆疊,而不是crash的堆疊。因為在Android系統上我們並不在一個真正的訊號處理函式裡。ART已經提前安裝了訊號處理程式並hook了sigaction()。

  1. <span style="font-size:14px;">void handler(int signo, siginfo_t *const info, void *

    相關推薦

    Android平臺Native程式碼崩潰捕獲機制實現

    本文地址:http://blog.csdn.net/mba16c35/article/details/54178067思路主要來源於這篇文章:http://blog.httrack.com/blog/2013/08/23/catching-posix-signals-on-a

    Android平臺Native代碼的崩潰捕獲機制實現

    其他 替換 接口 not big gnu cte job targe 本文地址:http://blog.csdn.net/mba16c35/article/details/54178067 思路主要來源於這篇文章:http://blog.httrack.com/blog

    Android平臺Native開發與JNI機制

    JNI的出現使得開發者既可以利用Java語言跨平臺、類庫豐 富、開發便捷等特點,又可以利用Native語言的高效。 JNI是JVM實現中的一部分,因此Native語言和Java程式碼都執行在JVM的宿主環境。 JNI是一個雙向的介面:開發者不僅可以通過JNI在Java程式

    嵌入式平臺執行程式碼,CPU佔用記憶體佔用

    檢視程式碼執行過程中,嵌入式平臺的CPU佔用情況,以及memeory情況。 開兩個命令視窗,一個執行程式碼,另外一個輸入命令檢視。 1.CPU佔用情況 top  //持續重新整理CPU的佔用情況。 儲存到文字:top | tee top.txt 2.memory fr

    Android 分析Native庫的載入過程x86系統執行arm庫的原理

    本文主要講述Android 載入動態連結庫的過程,為了分析工作中遇到的一個問題 x86的系統是如何執行arm的動態連結庫的。 參考部落格: https://pqpo.me/2017/05/31/system-loadlibrary/ 深入理解 System.loadLibrary

    Android TCP長連線 心跳機制實現

    維護任何一個長連線都需要心跳機制,客戶端傳送一個心跳給伺服器,伺服器給客戶端一個心跳應答, 這樣雙方都知道他們之間的連線是沒有斷開。【客戶端先發送給服務端】 如果超過一個時間的閾值,客戶端沒有收到伺服器的應答,或者伺服器沒有收到客戶端的心跳, 那麼對

    Linux平臺下使用者基本管理機制原理剖析(二)

    Linux平臺下使用者基本管理機制及原理剖析(二) 在前面的部落格中,我們對虛擬環境下使用者的新建、檢視、刪除以及使用者組的新建和管理有了一定的瞭解,這篇文章我們 接著瞭解虛擬環境下的使用者資訊的更改、使用者認證資訊的檢視,使用者密碼的管理以及使用者授權。   1.使用者資

    Linux平臺下使用者基本管理機制原理剖析(一)

    Linux平臺下使用者基本管理機制及原理剖析(一)  Linux是一個多使用者、多工的作業系統,具有很好的安全性和穩定性。 使用者和群組存在的意義 使用者的存在是為了使你的工作環境更加安全,就是有一個隱私空間,這個空間只有使用者本人有許可權。 而群組存在的意義是為了讓大家有

    Android 5.0使用android:onClick屬性出現崩潰的原因解決方案

    問題及表現 在專案中,對Button設定點選事件監聽時,大多數情況下還是習慣使用setOnClickListener設定監聽,但是最近發現當在佈局檔案中同時使用了android:theme和android:onClick屬性時,在響應點選事件時程式會發生cra

    Android app native程式碼效能分析

    0.79      2.88     0.03     4391     0.01     0.01  std::_Rb_tree<int, std::pair<int const, CUDTSocket*>, std::_Select1st<std::pair<int co

    淺談express 中介軟體機制實現原理

    中介軟體機制可以讓我們在一個給定的流程中新增一個處理步驟,從而對這個流程的輸入或者輸出產生影響,或者產生一些中作用、狀態,或者攔截這個流程。中介軟體機制和tomcat的過濾器類似,這兩者都屬於責任鏈模式的具體實現。 express 中介軟體使用案例 1 2

    redis sentinel的指令碼機制實現twemproxy主從自動切換

    redis sentinel 指令碼機制 1).sentinel notification-script   通知型指令碼:當sentinel有任何警告級別的事件發生時(比如說redis例項的主觀失效和客觀失效等等),將會去呼叫這個指令碼,這時這個指令碼應該通

    Android四種補間動畫介紹實現

    一.Android的animation由四種類型組成:alpha、scale、translate、rotate alpha 漸變透明度動畫效果 scale 漸變尺寸伸縮動畫效果 translate 畫面轉換位置移

    Android平臺程式崩潰的型別原因列舉

    Android平臺程式崩潰大家都應該遇到過,force close和ANR應該是大家遇到較多的。 這裡把Android平臺程式崩潰的各種型別做一個簡述和原因列舉。 1.ANR(可見ANR): 發生場景:應用發生ANR。 崩潰症狀:系統彈出視窗詢問使用者選擇“Force Close”或

    android和iOS平臺崩潰捕獲和收集

            通過崩潰捕獲和收集,可以收集到已釋出應用(遊戲)的異常,以便開發人員發現和修改bug,對於提高軟體質量有著極大的幫助。本文介紹了iOS和android平臺下崩潰捕獲和收集的原理及步驟,不過如果是個人開發應用或者沒有特殊限制的話,就不用往下看了,直接把友盟sd

    cocos2dx android和iOS平臺崩潰捕獲和收集

    收集了兩三天andoird都沒有出來,崩潰的時候沒有能捕捉到崩潰的堆疊資訊。 後來接入了騰訊的bugly,很快就完事了。 android配置很簡單。說下iOS的。 發現ios真賤,搞啥都麻煩,oc還醜的一比,草!!! frameWorkd 庫選擇Bugly_libc++ 把

    Android Framework 分析---2消息機制Native

    jnienv car 下一個 sas tracking zed 高效 方法 java 在Android的消息機制中。不僅提供了供Application 開發使用的java的消息循環。事實上java的機制終於還是靠native來實現的。在native不僅提供一套消息傳

    深入理解spring的事務管理機制程式碼實現

    Spring的事務管理機制 Spring事務管理高層抽象主要包括3個介面,Spring的事務主要是由他們共同完成的: PlatformTransactionManager:事務管理器—主要用於平臺相關事務的管理 TransactionDefinition: 事務定義資訊(隔

    【轉】Android 平臺語音通話迴音消除、噪音消除研究

    本文轉自部落格:https://www.cnblogs.com/jianglijs/p/8583603.html -------------------------------------------------------------------------------------------

    大資料教程(8.8)MR內部的shuffle過程詳解&combiner的執行機制程式碼實現

            之前的文章已經簡單介紹過mapreduce的運作流程,不過其內部的shuffle過程並未深入講解;本篇部落格將分享shuffle的全過程。