http://bbs.9ria.com/thread-200126-1-1.html

http://go3k.org/?p=258

通过崩溃捕获和收集,可以收集到已发布应用(游戏)的异常,以便开发人员发现和修改bug,对于提高软件质量有着极大的帮助。本文介绍了iOS和android平台下崩溃捕获和收集的原理及步骤,不过如果是个人开发应用或者没有特殊限制的话,就不用往下看了,直接把友盟sdk(一个统计分析sdk)加入到工程中就万事大吉了,其中的错误日志功能完全能够满足需求,而且不需要额外准备接收服务器。  但是如果你对其原理更感兴趣,或者像我一样必须要兼容公司现有的bug收集系统,那么下面的东西就值得一看了。

要实现崩溃捕获和收集的困难主要有这么几个:
      1、如何捕获崩溃(比如c++常见的野指针错误或是内存读写越界,当发生这些情况时程序不是异常退出了吗,我们如何捕获它呢)
      2、如何获取堆栈信息(告诉我们崩溃是哪个函数,甚至是第几行发生的,这样我们才可能重现并修改问题)
      3、将错误日志上传到指定服务器(这个最好办)

我们先进行一个简单的综述。会引发崩溃的代码本质上就两类,一个是c++语言层面的错误,比如野指针,除零,内存访问异常等等;另一类是未捕获异常(Uncaught Exception),iOS下面最常见的就是objective-c的NSException(通过@throw抛出,比如,NSArray访问元素越界),android下面就是java抛出的异常了。这些异常如果没有在最上层try住,那么程序就崩溃了。  无论是iOS还是android系统,其底层都是unix或者是类unix系统,对于第一类语言层面的错误,可以通过信号机制来捕获(signal或者是sigaction,不要跟qt的信号插槽弄混了),即任何系统错误都会抛出一个错误信号,我们可以通过设定一个回调函数,然后在回调函数里面打印并发送错误日志。

一、iOS平台的崩溃捕获和收集
1、设置开启崩溃捕获

[cpp]view plaincopyprint?

  1. static int s_fatal_signals[] = {

  2. SIGABRT,

  3. SIGBUS,

  4. SIGFPE,

  5. SIGILL,

  6. SIGSEGV,

  7. SIGTRAP,

  8. SIGTERM,

  9. SIGKILL,

  10. };

  11. static const char* s_fatal_signal_names[] = {

  12. "SIGABRT",

  13. "SIGBUS",

  14. "SIGFPE",

  15. "SIGILL",

  16. "SIGSEGV",

  17. "SIGTRAP",

  18. "SIGTERM",

  19. "SIGKILL",

  20. };

  21. static int s_fatal_signal_num = sizeof(s_fatal_signals) / sizeof(s_fatal_signals[0]);

  22. void InitCrashReport()

  23. {

  24. // 1     linux错误信号捕获

  25. for (int i = 0; i < s_fatal_signal_num; ++i) {

  26. signal(s_fatal_signals[i], SignalHandler);

  27. }

  28. // 2      objective-c未捕获异常的捕获

  29. NSSetUncaughtExceptionHandler(&HandleException);

  30. }

复制代码

在游戏的最开始调用InitCrashReport()函数来开启崩溃捕获。  注释1处对应上文所说的第一类崩溃,注释2处对应objective-c(或者说是UIKit Framework)抛出但是没有被处理的异常。
2、打印堆栈信息

[cpp] view plain copy print ?

  1. + (NSArray *)backtrace

  2. {

  3. void* callstack[128];

  4. int frames = backtrace(callstack, 128);

  5. char **strs = backtrace_symbols(callstack, frames);

  6. int i;

  7. NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];

  8. for (i = kSkipAddressCount;

  9. i < __min(kSkipAddressCount + kReportAddressCount, frames);

  10. ++i) {

  11. [backtrace addObject:[NSString stringWithUTF8String:strs[i]]];

  12. }

  13. free(strs);

  14. return backtrace;

  15. }

复制代码

幸好,苹果的iOS系统支持backtrace,通过这个函数可以直接打印出程序崩溃的调用堆栈。优点是,什么符号函数表都不需要,也不需要保存发布出去的对应版本,直接查看崩溃堆栈。缺点是,不能打印出具体哪一行崩溃,很多问题知道了是哪个函数崩的,但是还是查不出是因为什么崩的

3、日志上传,这个需要看实际需求,比如我们公司就是把崩溃信息http post到一个php服务器。这里就不多做声明了。
4、技巧---崩溃后程序保持运行状态而不退出

[cpp] view plain copy print ?

  1. CFRunLoopRef runLoop = CFRunLoopGetCurrent();

  2. CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);

  3. while (!dismissed)

  4. {

  5. for (NSString *mode in (__bridge NSArray *)allModes)

  6. {

  7. CFRunLoopRunInMode((__bridge CFStringRef)mode, 0.001, false);

  8. }

  9. }

  10. CFRelease(allModes);

复制代码

在崩溃处理函数上传完日志信息后,调用上述代码,可以重新构建程序主循环。这样,程序即便崩溃了,依然可以正常运行(当然,这个时候是处于不稳定状态,但是由于手持游戏和应用大多是短期操作,不会有挂机这种说法,所以稳定与否就无关紧要了)。玩家甚至感受不到崩溃。

这里要在说明一个感念,那就是“可重入(reentrant)”。简单来说,当我们的崩溃回调函数是可重入的时候,那么再次发生崩溃的时候,依然可以正常运行这个新的函数;但是如果是不可重入的,则无法运行(这个时候就彻底死了)。要实现上面描述的效果,并且还要保证回调函数是可重入的几乎不可能。所以,我测试的结果是,objective-c的异常触发多少次都可以正常运行。但是如果多次触发错误信号,那么程序就会卡死。  所以要慎重决定是否要应用这个技巧。

二、android崩溃捕获和收集

1、android开启崩溃捕获
     首先是java代码的崩溃捕获,这个可以仿照最下面的完整代码写一个UncaughtExceptionHandler,然后在所有的Activity的onCreate函数最开始调用

Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler(this));
     这样,当发生崩溃的时候,就会自动调用UncaughtExceptionHandler的public void uncaughtException(Thread thread, Throwable exception)函数,其中的exception包含堆栈信息,我们可以在这个函数里面打印我们需要的信息,并且上传错误日志
   然后是重中之重,jni的c++代码如何进行崩溃捕获。

[cpp] view plain copy print ?

  1. void InitCrashReport()

  2. {

  3. CCLOG("InitCrashReport");

  4. // Try

  5. to catch crashes...

  6. struct sigaction handler;

  7. memset(&handler, 0,

  8. sizeof(struct sigaction));

  9. handler.sa_sigaction =

  10. android_sigaction;

  11. handler.sa_flags

  12. = SA_RESETHAND;

  13. #define CATCHSIG(X) sigaction(X, &handler,

  14. &old_sa[X])

  15. CATCHSIG(SIGILL);

  16. CATCHSIG(SIGABRT);

  17. CATCHSIG(SIGBUS);

  18. CATCHSIG(SIGFPE);

  19. CATCHSIG(SIGSEGV);

  20. CATCHSIG(SIGSTKFLT);

  21. CATCHSIG(SIGPIPE);

  22. }

复制代码

通过singal的设置,当崩溃发生的时候就会调用android_sigaction函数。这同样是linux的信号机制。 此处设置信号回调函数的代码跟iOS有点不同,这个只是同一个功能的两种不同写法,没有本质区别。有兴趣的可以google下两者的区别。

2、打印堆栈
     java语法可以直接通过exception获取到堆栈信息,但是jni代码不支持backtrace,那么我们如何获取堆栈信息呢?    这里有个我想尝试的新方法,就是使用google breakpad,貌似它现在完整的跨平台了(支持windows, mac, linux, iOS和android等),它自己实现了一套minidump,在android上面限制会小很多。  但是这个库有些大,估计要加到我们的工程中不是一件非常容易的事,所以我们还是使用了简洁的“传统”方案。 思路是,当发生崩溃的时候,在回调函数里面调用一个我们在Activity写好的静态函数。在这个函数里面通过执行命令获取logcat的输出信息(输出信息里面包含了jni的崩溃地址),然后上传这个崩溃信息。  当我们获取到崩溃信息后,可以通过arm-linux-androideabi-addr2line(具体可能不是这个名字,在android ndk里面搜索*addr2line,找到实际的程序)解析崩溃信息。

jni的崩溃回调函数如下:

[cpp] view plain copy print ?

  1. void android_sigaction(int

  2. signal, siginfo_t *info, void

  3. *reserved)

  4. {

  5. if (!g_env) {

  6. return;

  7. }

  8. jclass classID =

  9. g_env->FindClass(CLASS_NAME);

  10. if (!classID) {

  11. return;

  12. }

  13. jmethodID

  14. methodID = g_env->GetStaticMethodID(classID, "onNativeCrashed", "()V");

  15. if (!methodID) {

  16. return;

  17. }

  18. g_env->CallStaticVoidMethod(classID, methodID);

  19. old_sa[signal].sa_handler(signal);

  20. }

复制代码

可以看到,我们仅仅是通过jni调用了java的一个函数,然后所有的处理都是在java层面完成。

java对应的函数实现如下:

[java] view plain copy print ?

  1. publicstaticvoid

  2. onNativeCrashed() {

  3. //

  4. http://stackoverflow.com/questions/1083154/how-can-i-catch-sigsegv-segmentation-fault-and-get-a-stack-trace-under-jni-on-a

  5. Log.e("handller", "handle");

  6. new RuntimeException("crashed here (native trace should follow after the Java

  7. trace)").printStackTrace();

  8. s_instance.startActivity(new

  9. Intent(s_instance, CrashHandler.class));

  10. }

复制代码

我们开启了一个新的activity,因为当jni发生崩溃的时候,原始的activity可能已经结束掉了。  这个新的activity实现如下:

[java] view plain copy print ?

  1. public class CrashHandler extends Activity

  2. {

  3. public static final String TAG = "CrashHandler";

  4. protected void onCreate(Bundle state)

  5. {

  6. super.onCreate(state);

  7. setTitle(R.string.crash_title);

  8. setContentView(R.layout.crashhandler);

  9. TextView v = (TextView)findViewById(R.id.crashText);

  10. v.setText(MessageFormat.format(getString(R.string.crashed), getString(R.string.app_name)));

  11. final Button b = (Button)findViewById(R.id.report),

  12. c = (Button)findViewById(R.id.close);

  13. b.setOnClickListener(new View.OnClickListener(){

  14. public void onClick(View v){

  15. final ProgressDialog progress = new ProgressDialog(CrashHandler.this);

  16. progress.setMessage(getString(R.string.getting_log));

  17. progress.setIndeterminate(true);

  18. progress.setCancelable(false);

  19. progress.show();

  20. final AsyncTask task = new LogTask(CrashHandler.this, progress).execute();

  21. b.postDelayed(new Runnable(){

  22. public void run(){

  23. if (task.getStatus() == AsyncTask.Status.FINISHED)

  24. return;

  25. // It's probably one of these devices where some fool broke logcat.

  26. progress.dismiss();

  27. task.cancel(true);

  28. new AlertDialog.Builder(CrashHandler.this)

  29. .setMessage(MessageFormat.format(getString(R.string.get_log_failed), getString(R.string.author_email)))

  30. .setCancelable(true)

  31. .setIcon(android.R.drawable.ic_dialog_alert)

  32. .show();

  33. }}, 3000);

  34. }});

  35. c.setOnClickListener(new View.OnClickListener(){

  36. public void onClick(View v){

  37. finish();

  38. }});

  39. }

  40. static String getVersion(Context c)

  41. {

  42. try {

  43. return c.getPackageManager().getPackageInfo(c.getPackageName(),0).versionName;

  44. } catch(Exception e) {

  45. return c.getString(R.string.unknown_version);

  46. }

  47. }

  48. }

  49. class LogTask extends AsyncTask<Void, Void, Void>

  50. {

  51. Activity activity;

  52. String logText;

  53. Process process;

  54. ProgressDialog progress;

  55. LogTask(Activity a, ProgressDialog p) {

  56. activity = a;

  57. progress = p;

  58. }

  59. @Override

  60. protected Void doInVoid... v) {

  61. try {

  62. Log.e("crash", "doInBackground begin");

  63. process = Runtime.getRuntime().exec(new String[]{"logcat","-d","-t","500","-v","threadtime"});

  64. logText = UncaughtExceptionHandler.readFromLogcat(process.getInputStream());

  65. Log.e("crash", "doInBackground end");

  66. } catch (IOException e) {

  67. e.printStackTrace();

  68. Toast.makeText(activity, e.toString(), Toast.LENGTH_LONG).show();

  69. }

  70. return null;

  71. }

  72. @Override

  73. protected void onCancelled() {

  74. Log.e("crash", "onCancelled");

  75. process.destroy();

  76. }

  77. @Override

  78. protected void onPostExecute(Void v) {

  79. Log.e("crash", "onPostExecute");

  80. progress.setMessage(activity.getString(R.string.starting_email));

  81. UncaughtExceptionHandler.sendLog(logText, activity);

  82. progress.dismiss();

  83. activity.finish();

  84. Log.e("crash", "onPostExecute over");

  85. }

复制代码

最主要的地方是doInBackground函数,这个函数通过logcat获取了崩溃信息。 不要忘记在AndroidManifest.xml添加读取LOG的权限

[html] view plain copy print ?

  1. <uses-permissionandroid:name="android.permission.READ_LOGS"/>

复制代码

3、获取到错误日志后,就可以写到sd卡(同样不要忘记添加权限),或者是上传。  代码很容易google到,不多说了。  最后再说下如何解析这个错误日志。

我们在获取到的错误日志中,可以截取到如下信息:
[plain] view plain copy print ?

  1. 12-12 20:41:31.807 24206

  2. 24206 I DEBUG :

  3. 12-12

  4. 20:41:31.847 24206 24206 I DEBUG : #00 pc 004931f8

  5. /data/data/org.cocos2dx.wing/lib/libhelloworld.so

  6. 12-12 20:41:31.847 24206 24206 I

  7. DEBUG : #01 pc 005b3a5e /data/data/org.cocos2dx.wing/lib/libhelloworld.so

  8. 12-12

  9. 20:41:31.847 24206 24206 I DEBUG : #02 pc 005aab68

  10. /data/data/org.cocos2dx.wing/lib/libhelloworld.so

  11. 12-12 20:41:31.847 24206 24206 I

  12. DEBUG : #03 pc 005ad8aa /data/data/org.cocos2dx.wing/lib/libhelloworld.so

  13. 12-12

  14. 20:41:31.847 24206 24206 I DEBUG : #04 pc 005924a4

  15. /data/data/org.cocos2dx.wing/lib/libhelloworld.so

  16. 12-12 20:41:31.847 24206 24206 I

  17. DEBUG : #05 pc 005929b6 /data/data/org.cocos2dx.wing/lib/libhelloworld.so

复制代码

[plain] view plain copy print ?

  1. 004931f8

复制代码

这个就是我们崩溃函数的地址,  libhelloworld.so就是崩溃的动态库。我们要使用addr2line对这个动态库进行解析(注意要是obj/local目录下的那个比较大的,含有符号文件的动态库,不是Libs目录下比较小的,同时发布版本时,这个动态库也要保存好,之后查log都要有对应的动态库)。命令如下:

arm-linux-androideabi-addr2line.exe -e 动态库名称  崩溃地址
例如:
[plain] view plain copy print ?

  1. $

  2. /cygdrive/d/devandroid/android-ndk-r8c-windows/android-ndk-r8c/toolchains/arm-linux-androideabi-4.6/prebuilt/windows/bin/arm-linux-androideabi-addr2line.exe

  3. -e obj/local/armeabi-v7a/libhelloworld.so 004931f8

复制代码

得到的结果就是哪个cpp文件第几行崩溃。  如果动态库信息不对,返回的就是 ?:0

原文来自: http://www.cnblogs.com/lancidie/archive/2013/04/13/3019349.html

转载于:https://blog.51cto.com/zgame/1222624

crash report相关推荐

  1. CentOS 6.2 虚拟机 mail邮件信息: crash:[abrt] full crash report

    如下看到的mail的日志: [root@aldap1 openldap]# mailx >U  1 user@localhost.sysu.  Mon Jun 18 22:35 214/1271 ...

  2. 分析Crash report

    分析Crash report Apple的官方文档:Understanding and Analyzing iOS Application Crash Reports iOS设备上,当app崩溃时会创 ...

  3. 了解和分析iOS Crash Report

    翻译自苹果官方文档:Understanding and Analyzing Application Crash Reports nimo: 这篇长达1w多字的文章,大概前后翻译了一个月,"写 ...

  4. Ubuntu开机【OK】 Started LSBautomatic crash report generation卡死--已解决

    Ubuntu开机[OK] Started LSB:automatic crash report generation卡死–已解决 [OK] Started Disk Manager [OK]Start ...

  5. [Android crash report]Windows部署ACRA

    [Android crash report]Windows部署ACRA ACRA全称为Application Crash Report for Android 安装 安装Erlang,下载地址:htt ...

  6. Kobe Bryant crash report

    时间:太平洋时间2020年1月26日上午10点左右(北京时间1月27日凌晨两点左右) 地点:美国加利福尼亚州卡拉巴萨斯市洛杉矶县郊区Las Virgenes(拉斯维加斯)路 飞机飞行轨迹及时间 失事飞 ...

  7. iOS平台一套完善的Crash Report解决方案

    原文:http://blog.csdn.net/langresser_king/article/details/8593845 最开始想iOS和Android都做了,后面尝试了下android上面的崩 ...

  8. get crash report binary image adress on ios

    [  http://stackoverflow.com/questions/5567215/how-to-determine-binary-image-architecture-at-runtime  ...

  9. 调试Release发布版程序的Crash错误

    订阅 调试Release发布版程序的Crash错误 http://dingchaoqun12.blog.163.com/blog/static/116062504201152834814661/ 在W ...

最新文章

  1. 理解 ajax、fetch和axios
  2. 【渝粤题库】陕西师范大学200401 初等代数研究 作业(专升本)
  3. 苹果发布 macOS 12——Monterey
  4. tkinter Scale滑块
  5. Oracle之pl/sql编程(一)函数,过程,包
  6. 树莓派 wiringpi 读取引脚_树莓派DHT11温湿度传感器 Python应用实例
  7. Ableton Live 11 Suite for Mac(专业音乐创作软件)
  8. 小程序 房租水电费记录管理_收租小程序开发有哪些功能和优势?
  9. 蓝牙5.0芯片NRF52840详细参数介绍
  10. java 中文转gb2312_Java将GB2312编码转化为汉字
  11. 20200807-玻璃涨停,隔夜低开,纯碱高开,今天晚上就是空头盛宴,43500了哈哈哈,赚钱了,别做了
  12. 中国正从法律入手编织公民信息保护网
  13. camera一些常见名词缩写
  14. C++程序设计课程主页-2015级
  15. 列表(list)使用方法详解
  16. 如何加减单元格指定数字_如何把单元格的数值每位数字进行相加?又学会一个Excel技巧...
  17. RK3126 人体感应模块驱动
  18. 在 CSDN 博客 100 天技术日更的 Flag,我做到了!
  19. 自动发帖程序的一些总结
  20. 转载 | 自动驾驶开源数据集总结

热门文章

  1. c语言 指针 越界,关于指针错误使用带来的问题――数组越界
  2. 前言:关于作者吴秋生博士与此书简介
  3. linux下载edk2链接文件
  4. java设计功能怎么实现代码_Java中的门面设计模式及如何用代码实现
  5. shell脚本回到当前目录
  6. AD进阶操作,利用AD17自带的3Dbody绘制简单的PCB3D封装
  7. 安卓手机驱动安装不上怎么办
  8. 凤姐讲述为美国绿卡奋斗的十年
  9. FAR planner浅尝试
  10. docker笔记之部署安装