简介
NE,全称Native Exception,在Android中主要指在用户空间运行的native程序或者natvie库发生异常。NE问题通常带来程序奔溃现象,导致功能模块不稳定。
本文主要介绍有关NE的基本知识、NE问题出现后的基本分析方法、常见的NE问题和常用调试工具。
Native内存布局
这里主要介绍Native进程的虚拟地址空间,分32bit和64bit进程,cameraserver就是32bit的,而android.hardware.camera.provider@2.4-service_64是64bit的,主要是寻址范围不一样,32bit的进程寻址范围是4G,而64bit进程寻址范围是2的64次方,实际可用的是2的48次方,也就是256TB。
进程的内存空间按低地址到高地址通常分为代码段、数据段、BSS段、堆、栈和内核数据区这几个部分:
- 代码段:存放可执行的二进制指令,也就是代码占用的空间
- 数据段:存放初始化过的全局变量、静态变量
- BSS段:Block Started by Symbol,存放程序中未初始化的全局变量和静态变量,默认初始化时全部为0
- 堆:存放进程运行中被动态分配的内存段,向上增长
- 栈:存放程序临时创建的局部变量,向下增长
(下文两张图来源于网络,出处忘记了…,见谅)
32位的虚拟地址空间
64位的虚拟地址空间
虚拟地址空间文件解读
通过adb shell cat /proc/$PID/maps或者adb shell pmap -x $PID可获取进程的虚拟地址空间映射文件,示例内容如:
00000070'ebb15000-00000070'ebc8ffff r-- 0 17b000 /vendor/lib64/libmegface_denselmk.so (BuildId: 5bb83f57711ebbf34c416d3feb706e)
00000070'ebc90000-00000070'ebc94fff --- 0 5000
00000070'ebc95000-00000070'ec2e8fff r-x 180000 654000 /vendor/lib64/libmegface_denselmk.so (BuildId: 5bb83f57711ebbf34c416d3feb706e)
00000070'ec2e9000-00000070'ec2f4fff --- 0 c000
00000070'ec2f5000-00000070'ec328fff rw- 7e0000 34000 /vendor/lib64/libmegface_denselmk.so (BuildId: 5bb83f57711ebbf34c416d3feb706e)
00000070'ec329000-00000070'ec3aefff rw- 0 86000 [anon:.bss]
00000070'fadd3000-00000070'fae4afff rw- 0 78000 anon_inode:dmabuf4044
00000071'7fa00000-00000071'801fffff rw- 0 800000 [anon:libc_malloc]
0000007f'f1c61000-0000007f'f1d18fff rw- 0 b8000 [stack]
每一条记录对应一个VMA(虚拟内存区域)结构。
第一段 “r–” 则是这个lib 使用的只读变量段,第三段 “r-x” 则是只读并可执行的主体代码段,第五段 “rw-” 则是这个lib 使用的数据段,第六段则是bss段。
第8行是ion memory 段,ion buffer 的 vma name 标注成dmabuf,即已经mmap 的ion memory 可以从这个直接统计算出。
第10行是malloc通过jemalloc 所管控的空间, 常见的malloc leaks 都会可以看到这种libc_malloc段空间显著增长。
第12行是栈空间。
NE问题常见类型
下面介绍几种常见的Native Crash情况:
-
主动抛出异常,代码中调用了abort(),系统会给进程发送信号SIGABRT(6),这种问题通常是代码执行到了异常分支,需要检查看看什么条件导致的,CamX代码使用的比较多。
-
进程被信号SIGKILL(9)杀死,比如通过adb shell kill -9 $PID或者某进程管理机制杀掉,APP进程可能进程会碰到这个问题,一般不会生成tombstone文件。
-
空指针,在进程的地址空间中,从0开始的第一个页面的权限被设置为不可读也不可写,当进程的指令试图访问该页面中的地址时(如读取空指针指向的内存),处理器就会产生一个异常,然后Linux内核会给该进程发送一个段错误信号SIGSEGV(11)。
-
野指针,指向的是一个无效的地址,该地址如果是不可读不可写的,那么会马上Crash(内核给进程发送段错误信号SIGSEGV),这时bug会很快被发现。
如果访问的地址为可写,而且通过野指针修改了该处的内存,那么很有可能会等一段时间(其它的代码使用了该处的内存后)才发生Crash。这时查看Crash时显示的调用栈,和野指针所在的代码部分,有可能基本上没有任何关联。是踩内存的一种。
-
数组越界/缓冲区溢出,情况与野指针类似,访问了无效的地址,踩了别人的内存区域。
常用调试工具
-
GNU C/C++工具,如addr2line、readelf、objdump、gdb等
prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/bin/
-
Malloc Debug,检测内存踩踏、内存泄漏等问题
bionic/libc/malloc_debug/README.md
-
AddressSanitizer
AddressSanitizer (ASan) 是一种基于编译器的快速检测工具,用于检测原生代码中的内存错误。
ASan 可以检测以下问题:
- 堆栈和堆缓冲区上溢/下溢
- 释放之后的堆使用情况
- 超出范围的堆栈使用情况
- 重复释放/错误释放
ASan 可在 32 位和 64 位 ARM 以及 x86 和 x86-64 上运行。ASan 的 CPU 开销约为 2 倍,代码大小开销在一半到 2 倍之间,并且内存开销很大(具体取决于您的分配模式,但约为 2 倍)。
Android 10 和 AArch64 上的 AOSP master 分支支持硬件加速 ASan (HWASan),这是一种 RAM 开销更小、检测到的错误范围更大的类似工具。除了 ASan 可以检测到的错误之外,HWASan 还可以检测返回之后的堆栈使用情况。
HWASan 具有类似的 CPU 和代码大小开销,但 RAM 开销要小得多 (15%)。HWASan 具有不确定性。只有 256 个可能的标记值,因此忽略任何错误的概率为 0.4%。ASan 对检测溢出规定了有限大小的红色区域,并对检测释放后使用情况规定了有限容量隔离区,而 HWAsan 没有这些规定,因此溢出大小或多久之前内存解除分配对 HWAsan 而言并不重要。这使得 HWASan 优于 ASan。您可以详细了解 HWAsan 的设计或