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

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

其他 替換 接口 not big gnu cte job targe

本文地址: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():

[cpp] view plain copy
  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在任意線程註冊一個可選的棧,保留一下在緊急情況下使用的空間。(系統會在危險情況下把棧指針指向這個地方,使你得以在一個新的棧上運行信號處理函數)

[cpp] view plain copy
  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的信號處理函數裏邊,我們是最後才調用舊的信號處理函數)

[cpp] view plain copy
  1. <span style="font-size:14px;">static void my_handler(const int 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不是異步信號安全的函數,如果在信號處理函數中使用,可能有不可預知的問題。

[cpp] view plain copy
  1. <span style="font-size:14px;">static void my_handler(const int 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,檢查各個模塊加載在內存的地址範圍,也可以獲得起始地址)

[cpp] view plain copy
  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. const uintptr_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()。

[cpp] view plain copy
  1. <span style="font-size:14px;">/*
  2. * Describes a single frame of a backtrace.
  3. */
  4. typedef struct {
  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的作者實現如下:

[cpp] view plain copy
  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()。

[cpp] view plain copy
  1. <span style="font-size:14px;">void handler(int signo, siginfo_t *const info, void *const reserved);</span>

在arm和x86上信號處理程序的第三個參數reserved,都是ucontext_t結構體,包含了libunwind所需的信息。但是在Android的信號處理程序中,libunwind使用這個reserved指針填充unw_tdep_context_t的方式不正確。

我們先看下libunwind是大概怎麽獲得堆棧的。

[cpp] view plain copy
  1. <span style="font-size:14px;">//Nice simple way to get the stack trace from normal code.
  2. // Not for use in signal handlers
  3. std::string getStackTrace(){
  4. int stepCode;
  5. char funcName[1024];
  6. unw_cursor_t cursor;
  7. unw_context_t uc;
  8. unw_word_t ip,sp,offp;
  9. std::stringstream ss;
  10. <span style="color:#ff0000;">unw_getcontext(&uc);這裏有問題</span>
  11. int result=unw_init_local(&cursor, &uc);
  12. if(!result){
  13. if(result == -UNW_EBADREG){
  14. //register needed wasn‘t accessible
  15. }
  16. }
  17. //intentionally skip the first frame since
  18. //that‘s this function
  19. while((stepCode = unw_step(&cursor)) > 0){
  20. if (unw_is_signal_frame (&cursor)){
  21. ss << "signal frame: ";
  22. }
  23. unw_get_reg(&cursor, UNW_REG_IP, &ip);
  24. unw_get_reg(&cursor, UNW_REG_SP, &sp);
  25. unw_get_proc_name(&cursor, funcName, 1024, &offp);
  26. char* realName = abi::__cxa_demangle(funcName,0,0,0);
  27. ss << "pc = " << std::hex << ((void*)ip);
  28. ss << " sp = " << std::hex << ((void*)sp);
  29. ss << " : " <<(realName != NULL ? realName : funcName);
  30. ss << " + " << std::hex <<((void*)offp) << ‘\n‘;
  31. }
  32. return ss.str();
  33. }</span>

可以通過

[cpp] view plain copy
  1. <span style="font-size:14px;">git clone "https://android.googlesource.com/platform/external/libunwind"</span>

獲得libunwind的源碼,如果只想編譯arm架構的版本

[cpp] view plain copy
  1. <span style="font-size:14px;">out/target/product/generic/system/lib/libunwind.so</span>

可以自己寫一個Android.mk來編譯,然後就可以像上面那樣調用libunwind中的函數。

問題出在unw_getcontext這個函數,所以我們要重寫這個函數。如果內核的信號接口變了,下面的代碼也可能有問題,不過在我的測試中是可行的。

[cpp] view plain copy
  1. <span style="font-size:14px;">unw_context_t uc;
  2. //platform specific voodoo to build a context for libunwind
  3. #if defined(__arm__)
  4. //cast/extract the necessary structures
  5. ucontext_t* context = (ucontext_t*)reserved;
  6. unw_tdep_context_t *unw_ctx = (unw_tdep_context_t*)&uc;
  7. sigcontext* sig_ctx = &context->uc_mcontext;
  8. //we need to store all the general purpose registers so that libunwind can resolve
  9. // the stack correctly, so we read them from the sigcontext into the unw_context
  10. unw_ctx->regs[UNW_ARM_R0] = sig_ctx->arm_r0;
  11. unw_ctx->regs[UNW_ARM_R1] = sig_ctx->arm_r1;
  12. unw_ctx->regs[UNW_ARM_R2] = sig_ctx->arm_r2;
  13. unw_ctx->regs[UNW_ARM_R3] = sig_ctx->arm_r3;
  14. unw_ctx->regs[UNW_ARM_R4] = sig_ctx->arm_r4;
  15. unw_ctx->regs[UNW_ARM_R5] = sig_ctx->arm_r5;
  16. unw_ctx->regs[UNW_ARM_R6] = sig_ctx->arm_r6;
  17. unw_ctx->regs[UNW_ARM_R7] = sig_ctx->arm_r7;
  18. unw_ctx->regs[UNW_ARM_R8] = sig_ctx->arm_r8;
  19. unw_ctx->regs[UNW_ARM_R9] = sig_ctx->arm_r9;
  20. unw_ctx->regs[UNW_ARM_R10] = sig_ctx->arm_r10;
  21. unw_ctx->regs[UNW_ARM_R11] = sig_ctx->arm_fp;
  22. unw_ctx->regs[UNW_ARM_R12] = sig_ctx->arm_ip;
  23. unw_ctx->regs[UNW_ARM_R13] = sig_ctx->arm_sp;
  24. unw_ctx->regs[UNW_ARM_R14] = sig_ctx->arm_lr;
  25. unw_ctx->regs[UNW_ARM_R15] = sig_ctx->arm_pc;
  26. //s << "base pc: 0x" << std::hex << sig_ctx->arm_pc << std::endl;
  27. logstr("base pc: ");
  28. logptr((void*)sig_ctx->arm_pc);
  29. logstr("\n");
  30. #elif defined(__i386__)
  31. ucontext_t* context = (ucontext_t*)reserved;
  32. //on x86 libunwind just uses the ucontext_t directly
  33. uc = *((unw_context_t*)context);
  34. #else
  35. //We don‘t have platform specific voodoo for whatever we were built for
  36. // just call libunwind and hope it can jump out of the signal stack on it‘s own
  37. unw_getcontext(&uc);
  38. #endif</span>


用我們自己的實現替換掉libunwind中的那個unw_getcontext函數,然後就用我們自己填充context調用unw_init_local,接著循環調用unw_step,就可以在5.0以上打印出native的crash堆棧了。

然後使用get_backtrace_symbols函數去獲得堆棧中的函數符號和demangle函數名

(關於demangle,可以參看這裏:http://hipercomer.blog.51cto.com/4415661/855223)

[cpp] view plain copy
  1. <span style="font-size:14px;">/*
  2. * Describes the symbols associated with a backtrace frame.
  3. */
  4. typedef struct {
  5. uintptr_t relative_pc; /* relative frame PC offset from the start of the library, or the absolute PC if the library is unknown */
  6. uintptr_t relative_symbol_addr; /* relative offset of the symbol from the start of the library or 0 if the library is unknown */
  7. char* map_name; /* executable or library name, or NULL if unknown */
  8. char* symbol_name; /* symbol name, or NULL if unknown */
  9. char* demangled_name; /* demangled symbol name, or NULL if unknown */
  10. } backtrace_symbol_t;
  11. /*
  12. * Gets the symbols for each frame of a backtrace.
  13. * The symbols array must be big enough to hold one symbol record per frame.
  14. * The symbols must later be freed using free_backtrace_symbols.
  15. */
  16. void get_backtrace_symbols(const backtrace_frame_t* backtrace, size_t frames,
  17. backtrace_symbol_t* backtrace_symbols);</span>

C++支持的函數重載功能是需要Name Mangling技術的最直接的例子,C++還有很多其他的地方需要Name Mangling,如namespace, class, template等等

舉個例子:
int rect_area(int x1,int x2,int y1,int y2);
這個函數Name Mangling之後是_Z9rect_areaiiii

C++語言中規定 :以下劃線並緊挨著大寫字母開頭或者以兩個下劃線開頭的標識符都是C++語言中保留的標示符。所以_Z9rect_areaiiii是保留的標識符,g++編譯的目標文件中的符號使用_Z開頭(C99標準)。
接下來的部分和網絡協議很類似。9表示接下來的要表示的一個字符串對象的長度(現在知道為什麽不讓用數字作為標識符的開頭了吧?)所以rect_area這九個字符就作為函數的名稱被識別出來了。
接下來的每個小寫字母表示參數的類型,i表示int類型。小寫字母的數量表示函數的參數列表中參數的數量。
所以,在符號中集成了用於區分不同重載函數的足夠的語義信息。
如果要在C語言中調用C++中的函數該怎麽做?這時候可以使用C++的關鍵字extern “C”。

六、獲得java堆棧

如果想產生一個帶jni crash堆棧的RuntimeException,同時解開java棧幀,需要將這些信息都傳回java虛擬機,而不是調用回調函數。唯一辦法就是用setjmp存儲退出程序的正確位置(實際上是sigsetjmp,因為我們要恢復一些被屏蔽的信號),然後在信號處理函數裏直接跳到這個位置。不過這又是一個非async-signal-safe的函數。

進程捕捉到信號並對其處理時,進程正在執行的正常指令序列就被信號處理函數臨時中斷,它首先執行該信號處理程序中的指令。如果crash在malloc()的過程中發生(那就意味空閑塊的鏈表已經被破壞了),你可能引起另一個SIGSEGV,甚至是死鎖,那就需要用戶手動殺死進程了。

因此,alarm是第一個操作,以防死鎖發生(alarm()是async-signal-safe的,那我們就可以自己殺死自己)

可以參考http://man7.org/linux/man-pages/man7/signal.7.html,signal 的手冊把async-signal-safe的函數都列出來的,沒有列入的大多數函數是不可重入的

[cpp] view plain copy
  1. <span style="font-size:14px;">static void my_handler(const int code, siginfo_t *const si, void *const sc) {
  2. /* Call previous handler. */
  3. old_handler.sa_sigaction(code, si, sc);
  4. /* Trigger a time bomb. */
  5. (void) alarm(30);
  6. /* Get thread-specific context. */
  7. my_struct *s = (my_struct*) pthread_getspecific(my_thread_var);
  8. if (s != NULL) {
  9. /* Store crash context for later. */
  10. s->code = code;
  11. s->si = *si;
  12. s->uc = *(ucontext_t*) sc;
  13. /* Jump back to initial location. */
  14. siglongjmp(t->ctx, -1);
  15. }
  16. ...
  17. }</span>


七、結果展示

[cpp] view plain copy
  1. <span style="font-size:14px;">FATAL EXCEPTION: AsyncTask #5
  2. java.lang.RuntimeException: An error occured while executing doInBackground()
  3. at android.os.AsyncTask$3.done(AsyncTask.java:299)
  4. at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:352)
  5. at java.util.concurrent.FutureTask.setException(FutureTask.java:219)
  6. at java.util.concurrent.FutureTask.run(FutureTask.java:239)
  7. at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:230)
  8. at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
  9. at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
  10. at java.lang.Thread.run(Thread.java:841)
  11. Caused by: java.lang.Error: signal 11 (Address not mapped to object) at address 0x42 [at libhttrack.so:0xa024]
  12. at com.httrack.android.jni.HTTrackLib.main(Native Method)
  13. at com.httrack.android.HTTrackActivity$Runner.runInternal(HTTrackActivity.java:998)
  14. at com.httrack.android.HTTrackActivity$Runner.doInBackground(HTTrackActivity.java:919)
  15. at com.httrack.android.HTTrackActivity$Runner.doInBackground(HTTrackActivity.java:1)
  16. at android.os.AsyncTask$2.call(AsyncTask.java:287)
  17. at java.util.concurrent.FutureTask.run(FutureTask.java:234)
  18. ... 4 more
  19. Caused by: java.lang.Error: signal 11 (Address not mapped to object) at address 0x42 [at libhttrack.so:0xa024]
  20. at data.app_lib.com_httrack_android_2.libhttrack_so.0xa024(Native Method)
  21. at data.app_lib.com_httrack_android_2.libhttrack_so.0x705fc(hts_main2:0x8f74:0)
  22. at data.app_lib.com_httrack_android_2.libhtslibjni_so.0x4cc8(HTTrackLib_main:0xf8:0)
  23. at data.app_lib.com_httrack_android_2.libhtslibjni_so.0x52d8(Java_com_httrack_android_jni_HTTrackLib_main:0x64:0)
  24. at system.lib.libdvm_so.0x1dc4c(dvmPlatformInvoke:0x70:0)
  25. at system.lib.libdvm_so.0x4dcab(dvmCallJNIMethod(unsigned int const*, JValue*, Method const*, Thread*):0x18a:0)
  26. at system.lib.libdvm_so.0x385e1(dvmCheckCallJNIMethod(unsigned int const*, JValue*, Method const*, Thread*):0x8:0)
  27. at system.lib.libdvm_so.0x4f699(dvmResolveNativeMethod(unsigned int const*, JValue*, Method const*, Thread*):0xb8:0)
  28. at system.lib.libdvm_so.0x27060(Native Method)
  29. at system.lib.libdvm_so.0x2b580(dvmInterpret(Thread*, Method const*, JValue*):0xb8:0)
  30. at system.lib.libdvm_so.0x5fcbd(dvmCallMethodV(Thread*, Method const*, Object*, bool, JValue*, std::__va_list):0x124:0)
  31. at system.lib.libdvm_so.0x5fce7(dvmCallMethod(Thread*, Method const*, Object*, JValue*, ...):0x14:0)
  32. at system.lib.libdvm_so.0x54a6f(Native Method)
  33. at system.lib.libc_so.0xca58(__thread_entry:0x48:0)
  34. at system.lib.libc_so.0xcbd4(pthread_create:0xd0:0)</span>


利用addr2line和堆棧中的相對地址就可以得到文件名和行號。當然前提是你要有一個debug版本、帶有符號表的二進制文件。

還有另一個辦法:構建帶上所有debug信息,包括行號和宏信息的版本(-g3),把debug section分離到另外一個文件(比如一個.dbg文件)。最後通過

[plain] view plain copy
  1. <span style="font-size:14px;">.gnu_debuglink</span>

elf section告訴gdb或者addr2line這個.so有一個調試相關的文件.dbg

[plain] view plain copy
  1. <span style="font-size:14px;"># copy all debugging sections to dbg file
  2. objcopy --only-keep-debug mylib.so mylib.dbg
  3. # strip debug sections
  4. objcopy --strip-debug mylib.so
  5. # wipe any existing ELF .gnu_debuglink section if any
  6. objcopy --remove-section .gnu_debuglink mylib.so
  7. # set the .gnu_debuglink to the dbg file
  8. objcopy --add-gnu-debuglink=mylib.dbg mylib.so</span>

保留.dbg文件就可以用來debug了

[plain] view plain copy
  1. <span style="font-size:14px;">cd /build-archives/httrack/armv7/3.47.99.35
  2. ./toolchains/arm-linux-androideabi-4.7/prebuilt/linux-x86_64/bin/arm-linux-androideabi-addr2line -C -f -e libhttrack.so 0xa024
  3. fourty_two
  4. src/htscoremain.c:111</span>

最後要確保所有庫都是用-funwind-tables 編譯選項編譯的,這樣才能帶上解開棧幀的必要信息(可以在Android.mk裏加上LOCAL_CFLAGS := -funwind-tables)(-funwind-tables不會使庫增大很多大小)

使用的方法如下:

[cpp] view plain copy
    1. <span style="font-size:14px;">/** The potentially dangerous function. **/
    2. jint call_dangerous_function(JNIEnv* env, jobject object) {
    3. // ... do dangerous things!
    4. return 42;
    5. }
    6. /** Protected function stub. **/
    7. void foo_protected(JNIEnv* env, jobject object, jint *retcode) {
    8. /* Try to call ‘call_dangerous_function‘, and raise proper Java Error upon
    9. * fatal error (SEGV, etc.). **/
    10. COFFEE_TRY_JNI(env, *retcode = call_dangerous_function(env, object));
    11. }
    12. /** Regular JNI entry point. **/
    13. jint Java_com_example_android_MyNative_foo(JNIEnv* env, jobject object) {
    14. jint retcode = 0;
    15. foo_protected(env, object, &retcode);
    16. return retcode;
    17. }</span>

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