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():
- <span style="font-size:14px;">struct sigaction sa;
- struct
- memset(&sa, 0, sizeof(sa));
- sigemptyset(&sa.sa_mask);
- sa.sa_sigaction = my_handler;
- sa.sa_flags = SA_SIGINFO;
- if (sigaction(sig, &sa, &sa_old) == 0) {
- ...
- }</span>
二、如何處理堆疊溢位
一個錯誤來源是堆疊溢位。當棧滿了(太多次遞迴,棧上太多物件),系統會在同一個已經滿了的棧上呼叫SIGSEGV的訊號處理函式,又再一次引起同樣的訊號。幸運的是,你可以使用sigaltstack在任意執行緒註冊一個可選的棧,保留一下在緊急情況下使用的空間。(系統會在危險情況下把棧指標指向這個地方,使你得以在一個新的棧上執行訊號處理函式)
- <span style="font-size:14px;">stack_t stack;
- memset(&stack, 0, sizeof(stack));
- /* Reserver the system default stack size. We don't need that much by the way. */
- stack.ss_size = SIGSTKSZ;
- stack.ss_sp = malloc(stack.ss_size);
- stack.ss_flags = 0;
- /* Install alternate stack size. Be sure the memory region is valid until you revert it. */
- if (stack.ss_sp != NULL && sigaltstack(&stack, NULL) == 0) {
- ...
- }</span>
三、相容舊的訊號處理函式
我們在java虛擬機器上執行,某些訊號可能在之前已經被安裝過訊號處理函式。例如,SIGSEGV經常用於處理NullPointerException
或者作為普通的JIT處理(可執行的物理頁標記了“NO ACCESS”的保護,對任何一處非法操作都會喚起JIT編譯器的訊號處理函式)。所以,你必須先呼叫舊的訊號處理函式,以防把上下文環境搞亂。舊的訊號處理函式要麼不進行處理直接返回,要麼呼叫abort()(這樣我們就有最後一次機會通過SIGABRT的訊號處理函式處理這個訊號,所以在捕獲SIGABRT的訊號處理函式裡邊,我們是最後才呼叫舊的訊號處理函式)
- <span style="font-size:14px;">staticvoid my_handler(constint code, siginfo_t *const si, void *const sc) {
- /* Call previous handler. */
- old_handler.sa_sigaction(code, si, sc);
- ...
- }</span>
四、多執行緒環境
我們執行在一個多執行緒程序環境,我們不想捕獲不屬於我們的其他執行緒的crash。可以用pthread_getspecific得到一個執行緒相關的上下文來解決這個問題。不過,pthread_getspecific不是非同步訊號安全的函式,如果在訊號處理函式中使用,可能有不可預知的問題。
- <span style="font-size:14px;">staticvoid my_handler(constint code, siginfo_t *const si, void *const sc) {
- /* Call previous handler. */
- old_handler.sa_sigaction(code, si, sc);
- /* Get thread-specific context. */
- my_struct *s = (my_struct*) pthread_getspecific(my_thread_var);
- if (s != NULL) {
- ...
- }
- ...
- }</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,檢查各個模組載入在記憶體的地址範圍,也可以獲得起始地址)
- <span style="font-size:14px;">Dl_info info;
- if (dladdr(addr, &info) != 0 && info.dli_fname != NULL) {
- void * const nearest = info.dli_saddr;
- constuintptr_t addr_relative =
- ((uintptr_t) addr - (uintptr_t) info.dli_fbase);
- ...
- }</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()。
- <span style="font-size:14px;">/*
- * Describes a single frame of a backtrace.
- */
- typedefstruct {
- uintptr_t absolute_pc; /* absolute PC offset */
- uintptr_t stack_top; /* top of stack for this frame */
- size_t stack_size; /* size of this stack frame */
- } backtrace_frame_t;
- ssize_t unwind_backtrace_signal_arch(siginfo_t* siginfo, void* sigcontext,
- const map_info_t* map_info_list,
- backtrace_frame_t* backtrace, size_t ignore_depth, size_t max_depth);</span>
3. 5.0以上使用libunwind
那對5.0以上的系統怎麼辦呢?
5.0以上的系統使用了libunwind來代替libcorkscrew,coffee的作者實現如下:
- <span style="font-size:14px;">/* Use libunwind to get a backtrace inside a signal handler.
- Will only return a non-zero code on Android >= 5 (with libunwind.so
- being shipped) */
- #ifdef USE_LIBUNWIND
- static ssize_t coffeecatch_unwind_signal(siginfo_t* si, void* sc,
- void** frames,
- size_t ignore_depth,
- size_t max_depth) {
- void *libunwind = dlopen("libunwind.so", RTLD_LAZY | RTLD_LOCAL);
- if (libunwind != NULL) {
- int (*backtrace)(void **buffer, int size) =
- dlsym(libunwind, "unw_backtrace");
- if (backtrace != NULL) {
- int nb = backtrace(frames, max_depth);
- if (nb > 0) {
- }
- return nb;
- } else {
- DEBUG(print("symbols not found in libunwind.so\n"));
- }
- dlclose(libunwind);
- } else {
- DEBUG(print("libunwind.so could not be loaded\n"));
- }
- return -1;
- }
- #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()。
- <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的全過程。