Android NE问题分析方法介绍

本文详细介绍了Android Native Exception(NE)问题,包括NE的基本概念、Native内存布局、常见NE问题类型、调试工具的使用,如GNU C/C++工具、Malloc Debug、AddressSanitizer等,并提供了tombstone抓取流程及实例分析,帮助开发者有效定位和解决NE问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简介

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 的设计

### 在 Android 平台上使用 GDB 进行调试 在 Android 上使用 GDB 调试应用程序涉及几个特定步骤来设置环境。由于 Android 是基于 Linux 的操作系统,因此可以在一定程度上利用标准的 GNU 工具链,不过需要针对移动设备做适当调整。 对于 Android 应用开发而言,通常会通过 Android NDK (Native Development Kit) 来构建本地库,并且可以借助 `ndk-gdb` 或者更现代的方式如 LLDB 配合 Android Studio 使用[^1]。然而,在某些情况下仍然可能需要用到传统的 GDB 方法来进行深入分析或解决复杂问题。 为了能够在 Android 设备上运行 GDB: - 安装适用于 ARM 架构或其他目标架构(取决于所使用的 SoC 类型)的交叉编译版 GDB; - 确保应用已启用调试模式并安装有符号表信息 (`-g` 编译选项),这可以通过修改 build.gradle 文件中的配置实现; - 利用 ADB 命令推送必要的二进制文件到手机端,并启动远程调试服务器; - 设置主机上的 GDB 与之连接,加载相应的符号文件以便查看变量值、调用栈等重要数据结构。 ```bash adb push gdbserver /data/local/tmp/ adb shell "chmod +x /data/local/tmp/gdbserver" adb shell "/data/local/tmp/gdbserver :5039 ./your_application_binary" ``` 接着在同一台机器上打开另一个终端窗口执行如下操作: ```bash arm-linux-androideabi-gdb your_application_binary (gdb) target remote tcp:localhost:5039 ``` 以上过程假设读者已经熟悉基本的 Linux Shell 操作以及具备一定的嵌入式系统知识背景。 ### OBJDUMP 在 Android 中的应用 至于 Objdump,在 Android 开发环境中同样扮演着不可或缺的角色。它可以被用来检查 ELF 格式的可执行文件和共享对象(.so),这对于理解程序行为非常有用。特别是当遇到难以解释崩溃报告时,能够快速定位错误位置。 要使 objdump 正确处理来自不同体系结构的目标文件,则需下载对应版本的工具链。例如,如果项目主要面向 armv7a 处理器优化过的话,那么就应该获取相应平台支持下的 binutils 包含该指令集模拟器。 下面是一些实用的例子展示如何运用此工具辅助日常编码工作: #### 查看函数列表及其地址范围 ```bash $ aarch64-linux-android-objdump -t libexample.so | grep ' T ' ``` 这里 `-t` 参数指示只打印符号表内的条目;而后面的过滤条件则限定了我们感兴趣的全局(text segment)定义项。 #### 显示反汇编后的机器码加上源代码对照(假设有) ```bash $ aarch64-linux-android-objdump -S -d app-debug.apk.unaligned.extracted/classes.dex.oat ``` 注意这里的输入不再是普通的 .exe 或者 .out 形态,而是经过 ART(Dalvik)虚拟机即时编译之后存储于 APK 内部的数据流映像副本。此外还需要额外提取 OAT(Object Allocation Table)部分作为参数传递给 objdump 实用程序[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值