【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();}

相关内容

热门资讯

【MySQL】锁 锁 文章目录锁全局锁表级锁表锁元数据锁(MDL)意向锁AUTO-INC锁...
【内网安全】 隧道搭建穿透上线... 文章目录内网穿透-Ngrok-入门-上线1、服务端配置:2、客户端连接服务端ÿ...
GCN的几种模型复现笔记 引言 本篇笔记紧接上文,主要是上一篇看写了快2w字,再去接入代码感觉有点...
数据分页展示逻辑 import java.util.Arrays;import java.util.List;impo...
Redis为什么选择单线程?R... 目录专栏导读一、Redis版本迭代二、Redis4.0之前为什么一直采用单线程?三、R...
【已解决】ERROR: Cou... 正确指令: pip install pyyaml
关于测试,我发现了哪些新大陆 关于测试 平常也只是听说过一些关于测试的术语,但并没有使用过测试工具。偶然看到编程老师...
Lock 接口解读 前置知识点Synchronized synchronized 是 Java 中的关键字,...
Win7 专业版安装中文包、汉... 参考资料:http://www.metsky.com/archives/350.htm...
3 ROS1通讯编程提高(1) 3 ROS1通讯编程提高3.1 使用VS Code编译ROS13.1.1 VS Code的安装和配置...
大模型未来趋势 大模型是人工智能领域的重要发展趋势之一,未来有着广阔的应用前景和发展空间。以下是大模型未来的趋势和展...
python实战应用讲解-【n... 目录 如何在Python中计算残余的平方和 方法1:使用其Base公式 方法2:使用statsmod...
学习u-boot 需要了解的m... 一、常用函数 1. origin 函数 origin 函数的返回值就是变量来源。使用格式如下...
常用python爬虫库介绍与简... 通用 urllib -网络库(stdlib)。 requests -网络库。 grab – 网络库&...
药品批准文号查询|药融云-中国... 药品批文是国家食品药品监督管理局(NMPA)对药品的审评和批准的证明文件...
【2023-03-22】SRS... 【2023-03-22】SRS推流搭配FFmpeg实现目标检测 说明: 外侧测试使用SRS播放器测...
有限元三角形单元的等效节点力 文章目录前言一、重新复习一下有限元三角形单元的理论1、三角形单元的形函数(Nÿ...
初级算法-哈希表 主要记录算法和数据结构学习笔记,新的一年更上一层楼! 初级算法-哈希表...
进程间通信【Linux】 1. 进程间通信 1.1 什么是进程间通信 在 Linux 系统中,进程间通信...
【Docker】P3 Dock... Docker数据卷、宿主机与挂载数据卷的概念及作用挂载宿主机配置数据卷挂载操作示例一个容器挂载多个目...