Crash Report,这在大型软件开发领域是很常见的功能,就是可以当程序崩溃退出后,可以将崩溃时的信息,最好是携带dmp文件发送给服务器,这样开发人员既能够得到分发出去的客户端的崩溃率统计,也能够针对出现的错误进行及时的纠正,以前在PC的端游时代,这是很常见的作法,最近进行了在手游上的关于crash report的相关研究,而且为项目编写了一个相对完善的CrashReport模块。java
这个模块的来源于手游项目正式上线,可是不少玩家反馈闪退,可是咱们只能听到反馈闪退,却不能找到缘由,只能凭脑壳去猜,是否是内存不够,机器配置太差,而后去尽量优化性能,因而老大开始喊咱们须要一个Crash Report, 因而就花2个星期完善了一个能够正式使用的Crash Repoter,项目基于Unity3D,在Android和Ios上作crash report 对我仍是第一次,因此仍是抱有了极大的兴趣。linux
1 Android 平台。
其实CrashReport也不该该是只有Crash了才Report,各类错误和潜在会致使Crash的问题也应该report上去。对于基于Unity3D的Android应用来讲,自底到上能够分为三层:C++,Java和C#。 android是基于linux的系统,最底下的各类so a库就是C++的部分,android系统自己的相关逻辑则是java,U3D则使用了C#开发逻辑,因此咱们采集问题也要从这三块分别着手。
C#
c#的错误很好处理,这层U3D彻底封装好了,C#层会出现warning error和exception,在android下这几种状况都不会致使crash,都会被UNITY3d接住,可是咱们须要知道并报告给服务器,U3D有接口Application.RegisterLogCallback(),可让C#层发生上面的问题时被我知道,咱们只要写这个callback,而后在里面给服务器就好了。
Java
java层的错误就是各类java exception,对于java,若是对于咱们catch了的exception,不会致使crash,会按照咱们的catch行为执行,对于那些咱们没有catch的exception,是会crash的,还会在adblog上打印出来,咱们须要获知这些exception,咱们能够采用java中的接口Thread.setDefaultUncaughtExceptionHandler来从新设置这个对未catch的exeption的处理,在咱们本身的handler中基本作的事情就是首先把这个exceptio报告给服务器,而后并不让程序退出,让程序尽量活下去。
C++
C++中出现的问题一般就是很严重的了,这里也分两种,一种是普通的一些异常,这取决于你是否catch了,若是没有catch,默认就是abort的,也就是crash了,还有一些好比对内存的非法访问,就直接在linux中产生了一个结束信号,把进程结束了,也是crash。对于C++咱们前后尝试了两种方案,第一种就是采用捕获linux的信号量,程序异常退出老是有信号的,可使用linux 下的sigaction来设置对这些信号的捕获处理,好比咱们捕获了SIGILL SIGABRT SIGFPE SIGSEGV SIGPIPE SIGBUS SIGSTKFLT,这样对于异常的程序退出咱们是知道的,能够在下次进入游戏时告知服务器,可是这样作有一个明显的问题就是咱们只是知道程序crash了,可是没有trace back,不知道在哪挂了,咱们想要dump文件。因而后来采起的方法就是使用了google的breakpad框架,关于google breakpad,这是它的主页,https://chromium.googlesource.com/breakpad/breakpad/,关于他的基本原理,你们能够去看他的wiki和文档,很长,基原本说它是一个平台无关的C++的crash reporter,能够在crash后,生成dmp文件,而后利用它的一些工具获取堆栈的符号信息。
google breakpad在android的简单集成方法以下:
1.从http://google-breakpad.googlecode.com/svn/trunk拿到源码
2.创建你本身的jni工程
3.将google breakpad的android 和src两个文件夹放到你的工程里
4.配置你的Application.mk,里面要加入
APP_STL := stlport_static
APP_CPPFLAGS := -std=gnu++11 -D__STDC_LIMIT_MACROS
5.配置你的Android.mk,里面要加入如下的src文件
google_breakpad/src/client/linux/crash_generation/crash_generation_client.cc \
google_breakpad/src/client/linux/handler/exception_handler.cc \
google_breakpad/src/client/linux/handler/minidump_descriptor.cc \
google_breakpad/src/client/linux/log/log.cc \
google_breakpad/src/client/linux/dump_writer_common/thread_info.cc \
google_breakpad/src/client/linux/dump_writer_common/ucontext_reader.cc \
google_breakpad/src/client/linux/microdump_writer/microdump_writer.cc \
google_breakpad/src/client/linux/minidump_writer/linux_dumper.cc \
google_breakpad/src/client/linux/minidump_writer/linux_ptrace_dumper.cc \
google_breakpad/src/client/linux/minidump_writer/minidump_writer.cc \
google_breakpad/src/client/minidump_file_writer.cc \
google_breakpad/src/common/android/breakpad_getcontext.S \
google_breakpad/src/common/convert_UTF.c \
google_breakpad/src/common/md5.cc \
google_breakpad/src/common/string_conversion.cc \
google_breakpad/src/common/linux/elfutils.cc \
google_breakpad/src/common/linux/file_id.cc \
google_breakpad/src/common/linux/guid_creator.cc \
google_breakpad/src/common/linux/linux_libc_support.cc \
google_breakpad/src/common/linux/memory_mapped_file.cc \
google_breakpad/src/common/linux/safe_readlink.cc \
还要加入LOCAL_STATIC_LIBRARIES += breakpad_client
以及include google_breakpad/android/google_breakpad/Android.mk
6.在你的crashreport模块初始化中(固然一般也能够在JNI_OnLoad中)初始化google breakpad,
google_breakpad::MinidumpDescriptor descriptor(path);
handler = new google_breakpad::ExceptionHandler(descriptor, NULL, NULL, NULL, true, -1);
这里的path是你手机上存放dmp文件的文件夹,crash发生后,它会在这个文件夹内生成以UUID命名的dmp文件,固然前提你要保证这个文件夹真实存在。
7.最后编出你的so库,给程序使用。
这样咱们就经过google breakpad实现了C++层的dump文件生成,当发生后,咱们把dump文件传给服务器就好了。
最后关于这个dmp文件的解读,这里你们能够参考这份文档:https://www.chromium.org/developers/decoding-crash-dumps。这个解析要在linux环境下作,没有win的工具,基本就是两步骤,第一步是用工具将dmp的二进制文件转成能够看懂的文本格式,能够看到出错的地址,可是若是要想详细知道这些地址所表明的符号,还须要用里面的一个工具以及带有符号版本的so库才能知道,unity本身的库应该没有这种so库,可是也能大体看出问题处在哪了。
其余
彷佛到这里咱们可以堵到android上全部可能崩溃的地方,然而并非。至少有两种状况是还不行的,一是watch dog超时,二是内存资源不足。
watch dog超时:当你的主线程超过一段时间没有相应,android系统会将你的程序退出,内存资源不足:当android系统认为这个程序使用的内存太高时,会选择将这个退出,以释放内存。
这两种状况都是android系统的管理器按照必定的策略调度的,虽然玩家看到闪退了,可是这两种状况逻辑上都不属于异常退出,和你本身退出android进程是同样的,只不过是系统帮你退出了,因此用google breakpad或者信号都不能知道,由于这实际上是正常退出,可是对于咱们程序设计来讲,这是异常。因此从捕获异常退出来讲没有办法(固然也许真的有,我不知道,那欢迎你们批评指正),因此对这两种状况咱们退而求其次使用当感知到有潜在的退出危险时报告给服务器警告的策略。
对于watch dog 超时,咱们在java层开一个新的线程,不断的去探测主线程,当较长时间发现主线程没回应,咱们给一个警告给服务器,并带上如今的内存状况,告知服务器这台机器主线程卡住好久了,极可能一会就被系统退出了,可是也可能运气好一会又好了。这种探测的方法咱们能够正好用unity的在native层的UnitySendMessage机制,由于这个就是异步的,咱们用另外一个线程不断的给主线程的Unity用这个发送心跳包,unity收到后回复,好久没回复就是主线程卡住了(缘由多了,好比某个逻辑特别特别耗时。。。)
对于内存过高,咱们会在程序里按期检测一下内存,当发现使用的内存明显高过咱们的设计时发给服务器,好比说咱们认为PSS内存超过600M,固然若是有机器每到这个就崩了,那也不是咱们的目标机型。在android系统下动态获得系统的总内和当前可用内存能够用activityManager.getMemoryInfo(),获取当前的进程的使用内存的接口能够用activityManager.getProcessMemoryInfo(new int[]{Process.myPid()})
经过这两种策略咱们能够预防式的获得这两种状况的一个crash统计。
IOS
IOS只有C++和C#两层,对于C#来讲,和Android是如出一辙的,不用多说。android
对于C++这层,咱们固然仍是能够继续使用google breakpad,由于它是跨平台的,可是其实Unity(至少从4.6开始)为ios已经提供了一个crash report模块,他须要咱们将工程生成好后,将Crashreport.h里面的ENABLE_CUSTOM_CRASH_REPORTER设置为1,或者你能够直接在unity安装路径下找到这个文件直接改。这样在c++ crash后,unity会为咱们生存crash文件,等下次启动后,能够经过crashreport这个模块访问这些dmp文件,这些dmp文件都是ios上的标准dmp文件,可使用ios的开发工具symbolicatecrash来查看。unity内置的crash report其实也是采用了第三方的库plcrashreport来实现的,这个库在ios上的应用不少。ios
另外对于ios,其实也提供了一个函数NSSetUncaughtExceptionHandler ,用来当那些未捕捉的异常发生时,进入这个处理,能够拦截一些东西,可是一些好比内存访问错误直接就退出了,不会进到这里,另外进到这里以后程序仍是会退出,只是让咱们能够记录一下,不过有了unity自带的crashreport 这个也就没啥用了。c++
还有在unity对ios的c#运行中,在player setting里面有个对代码的运行优化,选择slow but safe 仍是fast but no exception,若是选择前者,全部c#的执行错误都会像脚本同样被catch住,会报c#的exception,若是后者就不会,就直接不catch了,会形成程序退出,可是运行效率是高的,咱们一般选择slow but safe。这是mono架构的特性。c#
经过对ios 和android的崩溃的采集和报告,有助于了解咱们程序在用户手中的稳定性和及时改进。服务器