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,代码量也少,改动起来也很容易。
 
一、信号机制
我们知道,函数运行在用户态,当遇到系统调用、中断或是异常的情况时,程序会进入内核态。信号涉及到了这两种状态之间的转换,过程可以先看一下下面的示意图:

Android平台Native代码的崩溃捕获机制及实现

文章图片


 
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.    
  5.     /*  Get  thread-specific  context.  */   
  6.     my_struct  *s  =  (my_struct*)  pthread_getspecific(my_thread_var);    
  7.     if  (s  !=  NULL)  {   
  8.         ...   
  9.     }     
  10. ...   
  11. }< /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.    
  10. ssize_t  unwind_backtrace_signal_arch(siginfo_t*  siginfo,  void*  sigcontext,   
  11.                 const  map_info_t*  map_info_list,   
  12.                 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.      
  11. < span  style="color:#ff0000; "> unw_getcontext(& uc); 这里有问题< /span>    
  12. int  result=unw_init_local(& cursor,  & uc);    
  13. if(!result){   
  14. if(result  ==  -UNW_EBADREG){   
  15. //register  needed  wasn‘t  accessible   
  16. }   
  17. }   
  18. //intentionally  skip  the  first  frame  since   
  19. //that‘s  this  function   
  20. while((stepCode  =  unw_step(& cursor))  >   0){   
  21. if  (unw_is_signal_frame  (& cursor)){   
  22. ss  < <   "signal  frame:  ";    
  23. }   
  24. unw_get_reg(& cursor,  UNW_REG_IP,  & ip);    
  25. unw_get_reg(& cursor,  UNW_REG_SP,  & sp);    
  26. unw_get_proc_name(& cursor,  funcName,  1024,  & offp);    
  27. char*  realName  =  abi::__cxa_demangle(funcName,0,0,0);    
  28. ss  < <   "pc  =  "  < <   std::hex  < <   ((void*)ip);    
  29. ss  < <   "  sp  =  "  < <   std::hex  < <   ((void*)sp);    
  30. ss  < <   "  :  "  < < (realName  !=  NULL  ?  realName  :  funcName);    
  31. ss  < <   "  +  "  < <   std::hex  < < ((void*)offp)  < <   ‘\n‘;    
  32. }   
  33. return  ss.str();    
  34. }< /span>    
可以通过
【Android平台Native代码的崩溃捕获机制及实现】 
 
[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. /* 
  13. *  Gets  the  symbols  for  each  frame  of  a  backtrace. 
  14. *  The  symbols  array  must  be  big  enough  to  hold  one  symbol  record  per  frame. 
  15. *  The  symbols  must  later  be  freed  using  free_backtrace_symbols. 
  16. */   
  17. void  get_backtrace_symbols(const  backtrace_frame_t*  backtrace,  size_t  frames,   
  18.                 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.    
  5.     /*  Trigger  a  time  bomb.  */   
  6.     (void)  alarm(30);    
  7.    
  8.     /*  Get  thread-specific  context.  */   
  9.     my_struct  *s  =  (my_struct*)  pthread_getspecific(my_thread_var);    
  10.     if  (s  !=  NULL)  {   
  11.         /*  Store  crash  context  for  later.  */   
  12.         s-> code  =  code;    
  13.         s-> si  =  *si;    
  14.         s-> uc  =  *(ucontext_t*)  sc;    
  15.    
  16.         /*  Jump  back  to  initial  location.  */   
  17.         siglongjmp(t-> ctx,  -1);    
  18.     }   
  19. ...   
  20. }< /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.    
    7. /**  Protected  function  stub.  **/   
    8. void  foo_protected(JNIEnv*  env,  jobject  object,  jint  *retcode)  {   
    9.     /*  Try  to  call  ‘call_dangerous_function‘,  and  raise  proper  Java  Error  upon   
    10.       *  fatal  error  (SEGV,  etc.).  **/   
    11.     COFFEE_TRY_JNI(env,  *retcode  =  call_dangerous_function(env,  object));    
    12. }   
    13.    
    14. /**  Regular  JNI  entry  point.  **/   
    15. jint  Java_com_example_android_MyNative_foo(JNIEnv*  env,  jobject  object)  {   
    16.     jint  retcode  =  0;    
    17.     foo_protected(env,  object,  & retcode);    
    18.     return  retcode;    
    19. }< /span>    
















    推荐阅读