【Android】NDK开发Crash分析
admin
2024-02-06 18:09:03
0

NDK开发Crash问题分析

手机user版本还是userdebug或是eng版本:adb shell getprop ro.build.type

因为使用的user版本的手机,所有没有权限读取到/data/tombstones日志,本次Crash case使用Logcat日志分析的问题;
可以看到,日志内容主要由下面几部分组成:(我们最主要的就是分析崩溃的过程和PID,终止的信号和故障地址和调用堆栈部分)

# 构建指纹
# 崩溃的过程和PID
# 终止信号和故障地址
# CPU寄存器
# 调用堆栈
2022-11-21 16:24:58.226 7985-7985/? A/libc: Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0 in tid 7985 (gce.ndkpractice), pid 7985 (gce.ndkpractice)
2022-11-21 16:24:58.265 8033-8033/? A/DEBUG: Softversion: PD2031I_A_5.12.1
2022-11-21 16:24:58.265 8033-8033/? A/DEBUG: Time: 2022-11-21 16:24:58
2022-11-21 16:24:58.265 8033-8033/? A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
2022-11-21 16:24:58.265 8033-8033/? A/DEBUG: Build fingerprint: 'vivo/PD2031E/PD2031:10/QP1A.190711.020/compiler02151207:user/release-keys'
2022-11-21 16:24:58.265 8033-8033/? A/DEBUG: Revision: '0'
2022-11-21 16:24:58.265 8033-8033/? A/DEBUG: ABI: 'arm64'
2022-11-21 16:24:58.265 8033-8033/? A/DEBUG: Timestamp: 2022-11-21 16:24:58+0800
2022-11-21 16:24:58.265 8033-8033/? A/DEBUG: pid: 7985, tid: 7985, name: gce.ndkpractice  >>> cn.com.codingce.ndkpractice <<<
2022-11-21 16:24:58.265 8033-8033/? A/DEBUG: uid: 10683
2022-11-21 16:24:58.265 8033-8033/? A/DEBUG: signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
2022-11-21 16:24:58.265 8033-8033/? A/DEBUG: Cause: null pointer dereference
2022-11-21 16:24:58.265 8033-8033/? A/DEBUG:     x0  0000007fe10e10ff  x1  0000007fe10e1054  x2  000000000000000e  x3  6a002b2b43206d6f
2022-11-21 16:24:58.265 8033-8033/? A/DEBUG:     x4  0000007b146f2636  x5  0000007fe10e10ff  x6  7266206f6c6c6548  x7  2b2b43206d6f7266
2022-11-21 16:24:58.265 8033-8033/? A/DEBUG:     x8  0000000000000000  x9  000000000000007b  x10 0000000000000000  x11 000000000000000e
2022-11-21 16:24:58.265 8033-8033/? A/DEBUG:     x12 0000000000000001  x13 409dd45575cb3d01  x14 0000000000000006  x15 ffffffffffffffff
2022-11-21 16:24:58.265 8033-8033/? A/DEBUG:     x16 0000007b146feec0  x17 0000007b146d91dc  x18 0000007babb60000  x19 0000007b25010800
2022-11-21 16:24:58.265 8033-8033/? A/DEBUG:     x20 0000000000000000  x21 0000007b25010800  x22 0000007fe10e13b0  x23 0000007baa6bb4c7
2022-11-21 16:24:58.265 8033-8033/? A/DEBUG:     x24 0000000000000004  x25 0000007baad6a020  x26 0000007b250108b0  x27 0000000000000001
2022-11-21 16:24:58.265 8033-8033/? A/DEBUG:     x28 0000007fe10e1140  x29 0000007fe10e1110
2022-11-21 16:24:58.265 8033-8033/? A/DEBUG:     sp  0000007fe10e10a0  lr  0000007b146d9230  pc  0000007b146d91f0
2022-11-21 16:24:58.414 8033-8033/? A/DEBUG: backtrace:
2022-11-21 16:24:58.414 8033-8033/? A/DEBUG:       #00 pc 000000000000f1f0  /data/app/cn.com.codingce.ndkpractice-6baWiatY2L09dp0lwyzETw==/lib/arm64/libndkpractice.so (crashTest()+20) (BuildId: fd24be4dce579e53d04e9f0623c45f4f67f02ef8)
2022-11-21 16:24:58.414 8033-8033/? A/DEBUG:       #01 pc 000000000000f22c  /data/app/cn.com.codingce.ndkpractice-6baWiatY2L09dp0lwyzETw==/lib/arm64/libndkpractice.so (Java_cn_com_codingce_ndkpractice_MainActivity_stringFromJNI+48) (BuildId: fd24be4dce579e53d04e9f0623c45f4f67f02ef8)
2022-11-21 16:24:58.414 8033-8033/? A/DEBUG:       #02 pc 0000000000140350  /apex/com.android.runtime/lib64/libart.so (art_quick_generic_jni_trampoline+144) (BuildId: 402a81ae33e07fe7479455c29fd19662)
2022-11-21 16:24:58.414 8033-8033/? A/DEBUG:       #03 pc 0000000000137334  /apex/com.android.runtime/lib64/libart.so (art_quick_invoke_stub+548) (BuildId: 402a81ae33e07fe7479455c29fd19662)
---------------------------------省略部分-----------------------------------

崩溃过程和PID信息

从上面日志中的第9行中我们可以看到崩溃进程的基本信息,如下所示:

2022-11-21 16:24:58.265 8033-8033/? A/DEBUG: pid: 7985, tid: 7985, name: gce.ndkpractice  >>> cn.com.codingce.ndkpractice <<<

如果pid等于tid,那么就说明这个程序是在主线程中Crash掉的,名称的属性则表示Crash进程的名称以及在文件系统中位置。

终止信号和故障地址信息

从上面日志中的第11、12行中可以看到程序是因为什么信号导致了Crash以及出现错误的地址,如下所示:

2022-11-21 16:24:58.265 8033-8033/? A/DEBUG: signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
2022-11-21 16:24:58.265 8033-8033/? A/DEBUG: Cause: null pointer dereference

第10行的信息说明出现进程Crash的原因是因为程序产生了段错误的信号,访问了非法的内存空间,而访问的非法地址是0x0。另外这个例子中直接给出来问题原因是因为空指针,其它问题并不一定会给出此信息。

调用堆栈信息

调用栈信息是分析程序崩溃的非常重要的一个信息,它主要记录了程序在Crash前的函数调用关系以及当前正在执行函数的信息,上面例中的backtrace的信息如下所示:

2022-11-21 16:24:58.414 8033-8033/? A/DEBUG: backtrace:
2022-11-21 16:24:58.414 8033-8033/? A/DEBUG:       #00 pc 000000000000f1f0  /data/app/cn.com.codingce.ndkpractice-6baWiatY2L09dp0lwyzETw==/lib/arm64/libndkpractice.so (crashTest()+20) (BuildId: fd24be4dce579e53d04e9f0623c45f4f67f02ef8)
2022-11-21 16:24:58.414 8033-8033/? A/DEBUG:       #01 pc 000000000000f22c  /data/app/cn.com.codingce.ndkpractice-6baWiatY2L09dp0lwyzETw==/lib/arm64/libndkpractice.so (Java_cn_com_codingce_ndkpractice_MainActivity_stringFromJNI+48) (BuildId: fd24be4dce579e53d04e9f0623c45f4f67f02ef8)
2022-11-21 16:24:58.414 8033-8033/? A/DEBUG:       #02 pc 0000000000140350  /apex/com.android.runtime/lib64/libart.so (art_quick_generic_jni_trampoline+144) (BuildId: 402a81ae33e07fe7479455c29fd19662)
2022-11-21 16:24:58.414 8033-8033/? A/DEBUG:       #03 pc 0000000000137334  /apex/com.android.runtime/lib64/libart.so (art_quick_invoke_stub+548) (BuildId: 402a81ae33e07fe7479455c29fd19662)

在上面的输出信息中,## 00,#01,#02 …等表示的都是函数调用栈中栈帧的编号,其中编号越小的栈帧表示着当前最近调用的函数信息,所以栈帧标号#00表示的就是当前正在执行并导致程序崩溃函数的信息。
在栈帧的每一行中,pc后面的16进制数值表示的是当前函数正在执行语句的在共享链接库或者可执行文件中的位置,然后/lib/arm64/libndkpractice.so则表示的是当前执行指令是在哪个文件当中,后面的小括号则是注明对应的是哪个函数。

addr2line

addr2line是NDK中用来获得指定动态链接库文件或者可执行文件中指定地址对应的源代码信息,它们位于NDK包中的如下位置中,以arm64架构为例:

$NDK_HOME/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/aarch64-linux-android-addr2line  

其中NDK_HOME表示NDK的安装路径,另外具体架构和目录的对应关系如下:

工具链位置
arm$TOOLCHAIN/arm-linux-androideabi/lib/
arm64$TOOLCHAIN/aarch64-linux-android/lib/
x86$TOOLCHAIN/i686-linux-android/lib/
x86_64$TOOLCHAIN/x86_64-linux-android/lib/

addr2line的使用说明如下所示:

Usage: ./aarch64-linux-android-addr2line [option(s)] [addr(s)]Convert addresses into line number/file name pairs.If no addresses are specified on the command line, they will be read from stdinThe options are:@                Read options from -a --addresses         Show addresses-b --target=  Set the binary file format-e --exe=  Set the input file name (default is a.out)-i --inlines           Unwind inlined functions-j --section=    Read section-relative offsets instead of addresses-p --pretty-print      Make the output easier to read for humans-s --basenames         Strip directory names-f --functions         Show function names-C --demangle[=style]  Demangle function names-h --help              Display this information-v --version           Display the program's version

addr2line的基本用法如下所示:

➜./aarch64-linux-android-addr2line -f -e /Users/inke/AndroidStudioProjects/NDKPractice/app/build/intermediates/cmake/debug/obj/arm64-v8a/libndkpractice.so 000000000000f1f0
_Z9crashTestv
/Users/inke/AndroidStudioProjects/NDKPractice/app/src/main/cpp/native-lib.cpp:7

如上所示,通过addr2line工具,可以看到libndkpractice.so文件中地址000000000000f1f0对应的源码是什么了,它对应的是源码中app/src/main/cpp/native-lib.cpp:7处代码,查看上下文后,确定为空指针问题。

ndk-stack

Android NDK自从版本r6开始,提供了一个工具ndk-stack。这个工具能自动分析tombstone文件,能将崩溃时的调用内存地址和C++代码一行一行对应起来。

ndk-stack工具同样也位于NDK包中,它的路径如下所示:

$NDK_HOME/ndk-stack

ndk-stack的使用说明如下所示:

Usage: ndk-stack -sym PATH [-dump PATH]
Symbolizes the stack trace from an Android native crash.-sym PATH   sets the root directory for symbols-dump PATH  sets the file containing the crash dump (default stdin)See .

其中,dump参数很容易理解,即dump下来的log文本文件,可以是Logcat日志或者tombstones日志;sym参数就是你的android项目下,编译成功之后,obj目录下的文件。

ndk-stack的基本用法如下所示:

adb logcat | $NDK_HOME/ndk-stack -sym /Users/inke/AndroidStudioProjects/NDKPractice/app/build/intermediates/cmake/debug/obj/arm64-v8a/

执行后得到的结果如下:

********** Crash dump: **********
Build fingerprint: 'vivo/PD2031E/PD2031:10/QP1A.190711.020/compiler02151207:user/release-keys'
#00 0x000000000000f1f0 /data/app/cn.com.codingce.ndkpractice-6baWiatY2L09dp0lwyzETw==/lib/arm64/libndkpractice.so (crashTest()+20) (BuildId: fd24be4dce579e53d04e9f0623c45f4f67f02ef8)crashTest()/Users/inke/AndroidStudioProjects/NDKPractice/app/src/main/cpp/native-lib.cpp:7:8
#01 0x000000000000f22c /data/app/cn.com.codingce.ndkpractice-6baWiatY2L09dp0lwyzETw==/lib/arm64/libndkpractice.so (Java_cn_com_codingce_ndkpractice_MainActivity_stringFromJNI+48) (BuildId: fd24be4dce579e53d04e9f0623c45f4f67f02ef8)Java_cn_com_codingce_ndkpractice_MainActivity_stringFromJNI/Users/inke/AndroidStudioProjects/NDKPractice/app/src/main/cpp/native-lib.cpp:15:5
#02 0x0000000000140350 /apex/com.android.runtime/lib64/libart.so (art_quick_generic_jni_trampoline+144) (BuildId: 402a81ae33e07fe7479455c29fd19662)

objdump

上面两种工具都是将崩溃点对应到源码再进行分析,objdump 则是可以在汇编层对崩溃原因进行分析。所以这要求我们必须了解一些 arm/x86 汇编知识。

objdump也是ndk自带的一个工具,通常与addr2line在同一目录:

$NDK_HOME/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/aarch64-linux-android-objdump

objdump的使用说明如下所示:

Usage: ./aarch64-linux-android-objdump  Display information from object .At least one of the following switches must be given:-a, --archive-headers    Display archive header information-f, --file-headers       Display the contents of the overall file header-p, --private-headers    Display object format specific file header contents-P, --private=OPT,OPT... Display object format specific contents-h, --[section-]headers  Display the contents of the section headers-x, --all-headers        Display the contents of all headers-d, --disassemble        Display assembler contents of executable sections-D, --disassemble-all    Display assembler contents of all sections-S, --source             Intermix source code with disassembly-s, --full-contents      Display the full contents of all sections requested-g, --debugging          Display debug information in object file-e, --debugging-tags     Display debug information using ctags style-G, --stabs              Display (in raw form) any STABS info in the file-W[lLiaprmfFsoRt] or--dwarf[=rawline,=decodedline,=info,=abbrev,=pubnames,=aranges,=macro,=frames,=frames-interp,=str,=loc,=Ranges,=pubtypes,=gdb_index,=trace_info,=trace_abbrev,=trace_aranges,=addr,=cu_index]Display DWARF info in the file-t, --syms               Display the contents of the symbol table(s)-T, --dynamic-syms       Display the contents of the dynamic symbol table-r, --reloc              Display the relocation entries in the file-R, --dynamic-reloc      Display the dynamic relocation entries in the file@                  Read options from -v, --version            Display this program's version number-i, --info               List object formats and architectures supported-H, --help               Display this information

objdump的基本用法如下所示:

./aarch64-linux-android-objdump -D /Users/inke/AndroidStudioProjects/NDKPractice/app/build/intermediates/cmake/debug/obj/arm64-v8a/libndkpractice.so > ~/Desktop/libndkpractice.so.txt

最后产生的结果文件如下:

000000000000f1dc <_Z9crashTestv>:f1dc:	d10043ff 	sub	sp, sp, #0x10f1e0:	d2800008 	mov	x8, #0x0                   	// #0f1e4:	52800f69 	mov	w9, #0x7b                  	// #123f1e8:	f90007e8 	str	x8, [sp,#8]f1ec:	f94007e8 	ldr	x8, [sp,#8]f1f0:	b9000109 	str	w9, [x8]f1f4:	910043ff 	add	sp, sp, #0x10f1f8:	d65f03c0 	ret000000000000f1fc :f1fc:	d101c3ff 	sub	sp, sp, #0x70f200:	a9067bfd 	stp	x29, x30, [sp,#96]f204:	910183fd 	add	x29, sp, #0x60f208:	d53bd048 	mrs	x8, tpidr_el0f20c:	f9401508 	ldr	x8, [x8,#40]f210:	f81f83a8 	stur	x8, [x29,#-8]f214:	f81d83a0 	stur	x0, [x29,#-40]f218:	f9001be1 	str	x1, [sp,#48]f21c:	b00000c1 	adrp	x1, 28000 f220:	9118a021 	add	x1, x1, #0x628f224:	d10083a0 	sub	x0, x29, #0x20f228:	97ffff82 	bl	f030 <_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEC2IDnEEPKc@plt>f22c:	97ffffb1 	bl	f0f0 <_Z9crashTestv@plt>f230:	14000001 	b	f234 f234:	f85d83a0 	ldur	x0, [x29,#-40]f238:	d10083a8 	sub	x8, x29, #0x20f23c:	f9000fe0 	str	x0, [sp,#24]f240:	aa0803e0 	mov	x0, x8f244:	94000040 	bl	f344 <_ZNKSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE5c_strEv>f248:	f9400fe8 	ldr	x8, [sp,#24]f24c:	f9000be0 	str	x0, [sp,#16]f250:	aa0803e0 	mov	x0, x8

可以看到,0x000000000000f1f0这个地址的相关两个汇编指令如下:

f1ec:	f94007e8 	ldr	x8, [sp,#8]
f1f0:	b9000109 	str	w9, [x8]

LDR R0, [R1]
LDR是把R1中的值取出放到寄存器R0中LDR:load R0 from register R1

STR R0, [R1]
STR是把R0中的值存入寄存器R1中,STR:store R0 to register R1

结合Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0 in tid 7985信息,配合崩溃信号列表:

信号描述
SIGSEGV内存引用无效。
SIGBUS访问内存对象的未定义部分。
SIGFPE算术运算错误,除以零。
SIGILL非法指令,如执行垃圾或特权指令
SIGSYS糟糕的系统调用
SIGXCPU超过CPU时间限制。
SIGXFSZ文件大小限制。

大体可以猜出来这一个空指针的问题。

项目代码

C++

void crashTest() {int *p = NULL;*p = 666;
}extern "C" JNIEXPORT jstring JNICALL
Java_cn_com_codingce_ndkpractice_MainActivity_stringFromJNI(JNIEnv* env,jobject /* this */) {std::string hello = "Hello from C++";//crashTest(); // 空指针Crash案例//    return env->NewStringUTF(hello.c_str()); // 无返回值 Crash案例
}

Java

package cn.com.codingce.ndkpractice;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;import cn.com.codingce.ndkpractice.databinding.ActivityMainBinding;public class MainActivity extends AppCompatActivity {// Used to load the 'ndkpractice' library on application startup.static {System.loadLibrary("ndkpractice");}private ActivityMainBinding binding;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);binding = ActivityMainBinding.inflate(getLayoutInflater());setContentView(binding.getRoot());// Example of a call to a native methodTextView tv = binding.sampleText;tv.setText(stringFromJNI());try {nativeThrowException();} catch (IllegalArgumentException e) {Log.e("NativeMethod", e.getMessage());}
}public native void nativeThrowException();/*** A native method that is implemented by the 'ndkpractice' native library,* which is packaged with this application.*/public native String stringFromJNI();}

相关内容

热门资讯

安卓系统如何玩渠道服,渠道服游... 你有没有想过,在安卓系统上玩渠道服,那感觉简直就像是在游戏世界里开挂一样?没错,今天就要来给你揭秘,...
安卓系统等级在哪里查看,安卓系... 你有没有好奇过,你的安卓手机里那些神秘的系统等级到底在哪里可以查看呢?别急,今天就来给你揭秘这个小小...
自己制作安卓系统教程,自制安卓... 亲爱的读者们,你是否曾梦想过摆脱安卓系统的束缚,亲手打造一个只属于你自己的操作系统?别再羡慕那些技术...
安卓系统调整器下载,轻松优化手... 你有没有发现,手机用久了,系统总是有点小问题,比如卡顿啦,电池续航不给力啦,这些小烦恼是不是让你头疼...
怎样升级安卓系统视频,安卓系统... 亲爱的手机控们,你是否也和我一样,对手机系统升级充满了好奇和期待?想象你的安卓手机在经过一番“变身”...
鸿蒙系统和安卓系统哪个广告少,... 你有没有发现,现在手机市场上的操作系统真是五花八门,让人挑花了眼。不过,最近有个话题特别火,那就是鸿...
安卓系统openrec怎么注册... 你有没有想过,想要在安卓系统上体验一把OpenRec的乐趣,却发现注册步骤有点让人摸不着头脑?别急,...
怎样更新车机安卓系统,车机安卓... 亲爱的车主朋友们,你是不是也和我一样,对车机系统里的安卓系统充满了好奇?想要让它焕然一新,变得更加强...
安卓机换系统后照片,照片如何完... 你有没有遇到过这种情况:手机里的照片突然消失得无影无踪,心里那个急啊,就像热锅上的蚂蚁。别担心,今天...
安卓手机定位系统软件,技术原理... 你有没有想过,你的安卓手机里那些神奇的定位系统软件是怎么工作的呢?它们就像你的私人侦探,随时随地告诉...
安卓制作win系统盘,打造Wi... 亲爱的读者,你是否曾想过,将安卓系统的魅力与Windows系统的强大功能完美结合?今天,就让我带你一...
系统警告_您的安卓手机,揭秘潜... 亲爱的手机主人,最近你的安卓手机是不是突然跳出来一个系统警告,让你心头一紧?别慌,今天就来给你详细解...
投屏安卓系统版本,揭秘不同版本... 你有没有想过,家里的电视屏幕那么大,却只能用它来看电视?现在,有了安卓系统,你就可以把手机上的精彩内...
安卓官方系统升级软件,畅享智能... 你有没有发现,你的安卓手机最近是不是变得有点儿“年轻”了?没错,这就是安卓官方系统升级的魅力所在!今...
安卓系统铃声长度是多少,时长差... 你有没有想过,为什么你的手机每次收到消息时,都会响起那熟悉的铃声?是不是好奇过,安卓系统的铃声长度到...
酷派电脑系统安卓,深度解析与全... 亲爱的读者们,你是否曾对那些在电脑世界里游刃有余的酷派电脑系统安卓版心生好奇?今天,就让我带你一起揭...
什么系统可以装安卓软件,基于A... 你有没有想过,你的手机里那些好玩又实用的安卓软件,其实也可以在其他设备上运行呢?没错,这就是今天我们...
制作安卓系统主题软件,安卓系统... 你有没有想过,给你的安卓手机换一个全新的面貌?没错,就是那种一打开手机,就能感受到完全不同的风格和氛...
安卓系统平板怎么截屏,操作指南... 亲爱的平板用户,你是不是也和我一样,有时候想记录下平板上的精彩瞬间,却发现截屏功能有点神秘?别担心,...
安卓系统不推送更新,揭秘背后的... 最近是不是发现你的安卓手机有点儿“懒”啊?更新推送总是慢吞吞的,让人等得花儿都谢了。别急,今天就来给...