背景:
支付SDK面向游戏提供支付服务,高效的游戏引擎一般都会C++编写的,通过NDK编译成so文件在Android系统上运行,
但是CP经常会在接入过程中,经常遇到因自身原因会出现NDK层的闪退(backtrace指向他们闪退的so和地址),然后CP经常会说这么一句话:
“不接入你们的SDK,游戏是正常的,可能是哪里冲突了”
让我无言以对,好吧,下面简单说一下如何通过backtrace信息定位导致崩溃代码行
介绍:
如果还记得Android系统架构图,你可以发现Android底层是基于Linux内核的,
当 NDK 程序在发生 Crash 时,它会发出一个信号给应用程序处理,下面简单介绍一下Linux的信号机制
Linux 信号机制
信号机制是 Linux 进程间通信的一种重要方式,Linux 信号一方面用于正常的进程间通信和同步,如任务控制(SIGINT, SIGTSTP,SIGKILL, SIGSEGV……);
另一方面,它还负责监控系统异常及中断。 当应用程序运行异常时, Linux 内核将产生错误信号并通知当前进程。 当前进程在接收到该错误信号后,可以有三种不同的处理方式:
忽略该信号。
捕捉该信号并执行对应的信号处理函数(signal handler)。-------比如google的breakpad就是通过一个handler抓取dump然后保存
执行该信号的缺省操作(如 SIGSEGV, 其缺省操作是终止进程)。
当 Linux 应用程序在执行时发生严重错误,就会导致程序 crash。其中,Linux 专门提供了一类 crash 信号,在程序接收到此类信号时,缺省操作是将 crash 的现场信息记录到 core 文件,然后终止进程。
Crash 信号列表:
SIGSEGV
Invalid memory reference.
SIGBUS
Access to an undefined portion of a memory object.
SIGFPE
Arithmetic operation error, like divide by zero.
SIGILL
Illegal instruction, like execute garbage or a privileged instruction
SIGSYS
Bad system call.
SIGXCPU
CPU time limit exceeded.
SIGXFSZ
File size limit exceeded.
什么 Tombstone?
Android Native 程序本质上就是一个 Linux 程序,因此当它在执行时发生严重错误,也会导致程序 crash,然后产生一个记录 crash 的现场信息的文件,而这个文件在 Android 系统中就是 tombstone 文件。
Tombstone 英文的本意是墓碑,我觉得用这个单词来表示程序 Crash 之后产生的现场死亡信息真的再恰当不过了,tombstone 文件的确就像墓碑一样记录了死亡了的进程的基本信息(例如进程的进程号,线程号),
死亡的地址(在哪个地址上发生了 Crash),死亡时的现场是什么样的(记录了一系列的堆栈调用信息)等等。
分析:
获取Crash日志
Logcat输出:在某些手机(google nexus),会在闪退后通过logcat日志输出
tombstone 文件:在Android手机的/data/tombstones目录下,会报存进程死亡的基本信息
比如,《火柴人》Crash的时候我这边Logcat抓到的日志:
02-23 16:10:15.173: I/DEBUG(130): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0000000802-23 16:10:15.181: I/OMXCodec(135): [OMX.google.mp3.decoder] allocated buffer 0x410afee8 on input port02-23 16:10:15.181: I/OMXCodec(135): [OMX.google.mp3.decoder] allocated buffer 0x410d0768 on input port02-23 16:10:15.181: I/OMXCodec(135): [OMX.google.mp3.decoder] allocated buffer 0x410d0808 on input port02-23 16:10:15.181: I/OMXCodec(135): [OMX.google.mp3.decoder] allocated buffer 0x410d08c0 on input port02-23 16:10:15.181: I/OMXCodec(135): [OMX.google.mp3.decoder] allocating 4 buffers of size 9216 on output port02-23 16:10:15.181: I/OMXCodec(135): [OMX.google.mp3.decoder] allocated buffer 0x410d0a48 on output port02-23 16:10:15.181: I/OMXCodec(135): [OMX.google.mp3.decoder] allocated buffer 0x40061890 on output port02-23 16:10:15.181: I/OMXCodec(135): [OMX.google.mp3.decoder] allocated buffer 0x400619b0 on output port02-23 16:10:15.189: I/OMXCodec(135): [OMX.google.mp3.decoder] allocated buffer 0x40061ad8 on output port02-23 16:10:15.189: D/AudioPlayer(135): start of Playback, useOffload 002-23 16:10:16.736: I/DEBUG(130): r0 00000000 r1 65976834 r2 65976834 r3 0044256002-23 16:10:16.736: I/DEBUG(130): r4 65979cc8 r5 659cb7b0 r6 66b1d620 r7 659b96b002-23 16:10:16.736: I/DEBUG(130): r8 66b1dae8 r9 66a1ff24 sl 659d1ac0 fp 66b1dafc02-23 16:10:16.736: I/DEBUG(130): ip 00000075 sp 66b1d5c0 lr 64fdfb9b pc 6527cd76 cpsr 000f003002-23 16:10:16.736: I/DEBUG(130): d0 7275676e6265646c d1 000000000000000002-23 16:10:16.736: I/DEBUG(130): d2 0000000000000000 d3 000000000000000002-23 16:10:16.736: I/DEBUG(130): d4 ef87a4e585a3e8b2 d5 9ebfe8818de58cbc02-23 16:10:16.736: I/DEBUG(130): d6 bee585bfe5bd8ae6 d7 3f80000094bae49702-23 16:10:16.736: I/DEBUG(130): d8 0000000000000000 d9 000000000000000002-23 16:10:16.736: I/DEBUG(130): d10 0000000000000000 d11 000000000000000002-23 16:10:16.736: I/DEBUG(130): d12 0000000000000000 d13 000000000000000002-23 16:10:16.736: I/DEBUG(130): d14 0000000000000000 d15 000000000000000002-23 16:10:16.736: I/DEBUG(130): d16 3ff0000000000000 d17 3f03d92d4000000002-23 16:10:16.736: I/DEBUG(130): d18 42374876e8000000 d19 402400000000000002-23 16:10:16.736: I/DEBUG(130): d20 4023fffeb074a772 d21 412e847e0000000002-23 16:10:16.736: I/DEBUG(130): d22 3f11565ab512bf53 d23 4000ccccc400000002-23 16:10:16.736: I/DEBUG(130): d24 408f400000000000 d25 3ff000000000000002-23 16:10:16.736: I/DEBUG(130): d26 412e847e00000000 d27 4000ccccc400000002-23 16:10:16.736: I/DEBUG(130): d28 3ff0000000000000 d29 3e2d38fc57f240cc02-23 16:10:16.736: I/DEBUG(130): d30 bf984923ecc28000 d31 400000000000000002-23 16:10:16.736: I/DEBUG(130): scr 6800001002-23 16:10:16.736: I/DEBUG(130): backtrace:02-23 16:10:16.736: I/DEBUG(130): #00 pc 006cad76 /data/app-lib/com.DBGame.DiabloLOL-1/libcocos2dcpp.so (cocos2d::extension::Json_getItem(cocos2d::extension::Json, char const)+5)02-23 16:10:16.736: I/DEBUG(130): #01 pc 0042db97 /data/app-lib/com.DBGame.DiabloLOL-1/libcocos2dcpp.so (DebugLocalSys::getJsonIntById(std::string)+6)02-23 16:10:16.744: I/DEBUG(130): #02 pc 00431291 /data/app-lib/com.DBGame.DiabloLOL-1/libcocos2dcpp.so (StringJsonManager::getStringUrlWithKey(std::string const&)+96)02-23 16:10:16.744: I/DEBUG(130): stack:02-23 16:10:16.744: I/DEBUG(130): 66b1d580 655a57ab /data/app-lib/com.DBGame.DiabloLOL-1/libcocos2dcpp.so(以下省略...)
定位问题
有了日志,libcocos2dcpp.so是他们的游戏核心so,本来给到他们就可以了,但是他们又说“不接你们的SDK,游戏是正常的”,那么就来分析一下。当然,要CP提供带调试信息的so文件,即obj/local/[abi] 下面的文件。Google 在 NDK 包中为我们提供了一系列的调试工具,例如 addr2line、ndk-stack。
下面介绍一下对addr2line这个工具的用法:
addr2line -e [.../obj/local/{ABI类型}/带调试信息的so文件] [pc指针地址]
这个命令会找到导致崩溃的代码行数,把导致闪退的代码行数告诉CP后,CP得到后就能比较快速的解决了。
ndk-stack的用法也十分简单:
ndk-stack -sym [.../obj/local/{ABI类型}/] -dump crash.log