评论

Android开发太难了,Native Crash的一切!

本文作者

作者: 陈冠有-小米

Native Crash一直是各大app的痛,本文非常有深度,是小米公司的陈冠有同学的一个内部分享,无私的分享给大家, 相关资料在互联网上非常少,有精通Native Crash分析的同学分享实属不易,因为知识深入的原因可能非常难理解,我也只能略懂一二,但是本文非常值得收藏,时常查阅。

下面开始正文。

1

常见的Native Crash类型

SIGSEGV

SEGV_MAPERR

地址不在 /proc/self/maps 映射中

SEGV_ACCERR

没有访问权限

SEGV_MTESERR

MTE特有类型

SIGABRT

程序主动退出,常见调用函数abort,raise等

SIGILL

ILL_ILLOPC

非法操作码(opcode)

ILL_ILLOPN

非法操作数(operand)

ILL_ILLADR

非法寻址

ILL_ILLTRP

非法trap,如_builtintrap主动崩溃

ILL_PRVOPC

非法特权操作码(privileged opcode)

ILL_PRVREG

非法特权寄存器(privileged register)

ILL_COPROC

协处理器错误

ILL_BADSTK

内部堆栈错误

SIGBUS

BUS_ADRALN

访问地址未对齐

BUS_ADRERR

访问不存在的物理地址

BUS_OBJERR

特定对象的硬件错误

SIGFPE

FPE_INTDIV

整数除以0

FPE_INTOVF

整数溢出

FPE_FLTDIV

浮点数除以0

FPE_FLTOVF

浮点数上溢(overflow)

FPE_FLTUND

浮点数下溢(underflow)

FPE_FLTRES

浮点数结果不精确

FPE_FLTINV

无效的浮点运算

FPE_FLTSUB

越界

2

Android日志

当程序发生了 Native Crash 错误,Android 的日志会输出到 log crash buffer 上,因此我们通过

adb logcat -b crash 抓取到相应的错误报告,而日志本身能提供的信息是有限的,仅仅是错误堆栈,与当前线程的寄存器信息。

--------- beginning of crash

06-07 01:53:32.465 12027 12027 F DEBUG : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

06-07 01:53:32.465 12027 12027 F DEBUG : Revision: '0'

06-07 01:53:32.466 12027 12027 F DEBUG : ABI: 'arm64'

06-07 01:53:32.466 12027 12027 F DEBUG : Process uptime: 0s

06-07 01:53:32.466 12027 12027 F DEBUG : Cmdline: mediaserver64

06-07 01:53:32.466 12027 12027 F DEBUG : pid: 1139, tid: 11981, name: NPDecoder >>> mediaserver64 <<<

06-07 01:53:32.466 12027 12027 F DEBUG : uid: 1013

06-07 01:53:32.466 12027 12027 F DEBUG : tagged_addr_ctrl: 0000000000000001

06-07 01:53:32.466 12027 12027 F DEBUG : signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0x7c02d886f0

06-07 01:53:32.467 12027 12027 F DEBUG : x8 0000000000000080 x9 0000007ca126fed7 x10 0000000000000006 x11 0000007bfd0a81fc

06-07 01:53:32.467 12027 12027 F DEBUG : x12 9ef8a95ca9649dbe x13 e44782d5ac38720e x14 0000007bfd0a8030 x15 0000001e56307b5c

06-07 01:53:32.467 12027 12027 F DEBUG : x28 0000007ca13c2c28 x29 0000007c02d886f0

06-07 01:53:32.467 12027 12027 F DEBUG : backtrace:

06-07 01:53:32.467 12027 12027 F DEBUG : #00 pc 00000000000f86f0 [anon:stack_and_tls:11981]

当只有日志堆栈不能进行更详细的分析时,我们还需要程序的部分内存信息以及寄存器信息,而Android 的错误机制会相应的会生成一份 tombstone 文件保存到 /data/tombstones/tombstone_xx ,对于没有 Root 权限的机器则可以通过 adb bugreport 抓取出 tombstone 文件。

3

Tombstone

tombstone 文件保存的信息有错误程序的体系结构,通俗的说 arm、arm64 等,发生时间点,程序名,错误类型,错误程序的进程号、线程号,错误现场寄存器,堆栈和部分寄存器地址附近的内存信息,程序内存映射表 /proc/self/maps ,FD 信息以及发生错误时该程序输出的日志。

ABI: 'arm64' 【 arm64的程序 】

Process uptime: 0s

Cmdline: mediaserver64 【 程序名 】

pid: 1139, tid: 11981, name: NPDecoder >>> mediaserver64 <<< 【 进程号、线程号 】

uid: 1013

错误类型

signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0x7c02d886f0

【 错误类型是 SIGSEGV,子类是 SEGV_ACCERR,错误地址0x7c02d886f0 】

SIGSEGV 也是我们最常见的 Native Crash 类型,大部分时候我们称其为段错误,而错误意思是在 PC=0x7c02d886f0 上发生拒绝访问的段错误。

寄存器信息

x8 0000000000000080 x9 0000007ca126fed7 x10 0000000000000006 x11 0000007bfd0a81fc

x12 9ef8a95ca9649dbe x13 e44782d5ac38720e x14 0000007bfd0a8030 x15 0000001e56307b5c

x28 0000007ca13c2c28 x29 0000007c02d886f0

堆栈信息

backtrace:

#00 pc 00000000000f86f0 [anon:stack_and_tls:11981] 【PC刚好落在线程栈地址上】

这种情况很少见,虽然它只有的一条堆栈,并不代表程序是从这里开始运行,出现的这种情况仅仅 unwind 无法正确的回溯。

我们是可以通过栈地址空间内存进行恢复调用栈,用户态主线程栈(红色部分)结构如下:

而线程栈位于 mmap segmemt上,我们可以在 /proc/self/maps 上找到该线程栈的地址空间范围。

大多数 arm64 的 Linux Android 程序,它的线程调用栈结构样例如下:

/ / bionic/libc/arch-arm64/bionic/__bionic_clone.S#include <private/bionic_asm.h>

//pid_t __bionic_clone(int flags, void* child_stack, pid_t* parent_tid, void* tls, pid_t* child_tid, int (*fn)(void*), void* arg);

ENTRY_PRIVATE(__bionic_clone)

# Push 'fn' and 'arg' onto the child stack.

stp x5, x6, [x1, #-16]!

# Make the system call.

mov x8, __NR_clone

svc #0

# Are we the child?

cbz x 0, .L_bc_child

# Set errno if something went wrong.

cmn x 0, #(MAX_ERRNO + 1)

cneg x 0, x 0, hi

b.hi __set_errno_internal

ret

. L_bc_child:

# We're in the child now. Set the end of the frame record chain.

mov x29, #0

# Setting x30 to 0 will make the unwinder stop at __start_thread.

mov x3 0, #0

# Call __start_thread with the 'fn' and 'arg' we stored on the child stack.

ldp x 0, x1, [sp], #16

b __start_thread

END(__bionic_clone)

NOTE_GNU_PROPERTY

内存信息

tombstone 会记录当前有效地址的寄存器附近内存信息,大小为0x100,这个可以修改文件

system/core/debuggerd/libdebuggerd/utility.cpp 中的宏定义 MEMORY_BYTES_TO_DUMP 参数

像这种一条堆栈的情况下,栈的内存信息配合下边的映射表可以帮助到我们对栈进行恢复。

memory near x1 (/system/lib64/libstagefright.so):

0000007ca13c35f0 0000000000000000 0000000000000000 ................

0000007ca13c3600 0000000000000000 0000000000000000 ................

0000007ca13c3630 0000007ca10552e8 0000007ca10552ec .R..|....R..|...

0000007ca13c3650 0000000000000000 0000000000000000 ................

0000007ca13c3660 0000000000000000 0000000000000000 ................

0000007ca13c3670 0000000000000000 0000007ca134ea84 ..........4.|...

0000007ca13c3680 0000007ca134ecec 0000007ca10552e4 ..4.|....R..|...

0000007ca13c3690 0000007ca10552e8 0000007ca10552ec .R..|....R..|...

0000007ca13c36a0 0000007ca10552f4 0000007ca134ed10 .R..|.....4.|...

0000007ca13c36b0 0000000000000000 0000000000000000 ................

0000007ca13c36c0 0000000000000000 0000000000000000 ................

0000007ca13c36d0 0000000000000000 0000007ca135d02c ........,.5.|...

0000007ca13c36e0 0000007ca135d4b8 0000007ca10552e4 ..5.|....R..|...

memory near x29 ([anon:stack_and_tls:11981]):

0000007c02d886e0 0000007c02d88700 6f2ab3b40fa2f8ef ….|………o

0000007c02d886f0 0000007c02d88750 0000007ca133f8e0 P…|…..3.|… 【x29 = 0x0000007c02d886f0】

0000007c02d88700 0000000000000002 0000000000000000 …………….

0000007c02d88720 0000000000000000 0000007c02d88808 …………|…

0000007c02d88740 0000007c02d89000 6f2ab3b40fa2f8ef ….|………o

0000007c02d88750 0000007c02d88830 0000007ca796ee7c 0…|…|…|…

0000007c02d88760 0000007ca79f3dd8 0000007ca79edb80 .=..|…….|…

0000007c02d88780 0000000000000000 0000000000000002 …………….

0000007c02d887b0 0000007c02d88830 0000007ca796d420 0…|… …|…

0000007c02d887c0 0000007c02d88830 0000007ca796d460 0…|…`…|…

memory near lr ([anon:stack_and_tls:11981]):

0000007c02d886e0 0000007c02d88700 6f2ab3b40fa2f8ef ….|………o

0000007c02d886f0 0000007c02d88750 0000007ca133f8e0 P…|…..3.|…

0000007c02d88700 0000000000000002 0000000000000000 …………….

0000007c02d88720 0000000000000000 0000007c02d88808 …………|…

0000007c02d88740 0000007c02d89000 6f2ab3b40fa2f8ef ….|………o

0000007c02d88750 0000007c02d88830 0000007ca796ee7c 0…|…|…|…

0000007c02d88760 0000007ca79f3dd8 0000007ca79edb80 .=..|…….|…

0000007c02d88780 0000000000000000 0000000000000002 …………….

0000007c02d887b0 0000007c02d88830 0000007ca796d420 0…|… …|…

0000007c02d887c0 0000007c02d88830 0000007ca796d460 0…|…`…|…

memory near sp ([anon:stack_and_tls:11981]):

0000007c02d886b0 0000000000000018 0000007caae98c58 ……..X…|…

0000007c02d886c0 0000007c02d886f0 0000007c95de6754 ….|…Tg..|…

0000007c02d886e0 0000007c02d88700 6f2ab3b40fa2f8ef ….|………o

0000007c02d886f0 0000007c02d88750 0000007ca133f8e0 P…|…..3.|…

0000007c02d88700 0000000000000002 0000000000000000 …………….

0000007c02d88720 0000000000000000 0000007c02d88808 …………|…

0000007c02d88740 0000007c02d89000 6f2ab3b40fa2f8ef ….|………o

0000007c02d88750 0000007c02d88830 0000007ca796ee7c 0…|…|…|…

0000007c02d88760 0000007ca79f3dd8 0000007ca79edb80 .=..|…….|…

0000007c02d88780 0000000000000000 0000000000000002 …………….

memory near pc ([anon:stack_and_tls:11981]):

0000007c02d886e0 0000007c02d88700 6f2ab3b40fa2f8ef ….|………o

0000007c02d886f0 0000007c02d88750 0000007ca133f8e0 P…|…..3.|…

0000007c02d88700 0000000000000002 0000000000000000 …………….

0000007c02d88720 0000000000000000 0000007c02d88808 …………|…

0000007c02d88740 0000007c02d89000 6f2ab3b40fa2f8ef ….|………o

0000007c02d88750 0000007c02d88830 0000007ca796ee7c 0…|…|…|…

0000007c02d88760 0000007ca79f3dd8 0000007ca79edb80 .=..|…….|…

0000007c02d88780 0000000000000000 0000000000000002 …………….

0000007c02d887b0 0000007c02d88830 0000007ca796d420 0…|… …|…

0000007c02d887c0 0000007c02d88830 0000007ca796d460 0…|…`…|…

内存映射表

memory map (1146 entries):

FD信息

open files:

fd 0: /dev/null (unowned)

fd 1: /dev/null (unowned)

fd 2: /dev/null (unowned)

fd 3: socket:[62562] (unowned)

fd 4: /dev/binderfs/binder (unowned)

fd 5: /dev/binderfs/hwbinder (unowned)

fd 6: /sys/kernel/tracing/trace_marker (unowned)

fd 7: /dev/ashmem4945d9b6-db30-413c-88c5-e50674f154c7 (unowned)

fd 8: /dmabuf: (unowned)

fd 9: /dev/ashmem4945d9b6-db30-413c-88c5-e50674f154c7 (unowned)

fd 10: /storage/emulated/0/zapya/folder/华语音乐/IN-K&王忻辰&苏星婕 - 落日与晚风.mp3 (owned by unique_fd 0x7c13c7a498)

fd 11: /dev/ashmem4945d9b6-db30-413c-88c5-e50674f154c7 (unowned)

...

4

Coredump

前面 tombstone 文件内容,可以了解到它的信息很有限,当我们需要更多的内存信息时,它无法满足我们,这时 coredump 就尤为重要了,它可以按我们配置抓取相应的内存信息,关于 core 的介绍,见:

https://man7.org/linux/man-pages/man5/core.5.html

AOSP方法

# build/envsetup.sh# coredump_setup - enable core dumps globally for any process

# that has the core-file-size limit set correctly

#

# NOTE:You must call also coredump_enable for a specific process

# if its core-file-size limit is not set already.

# NOTE:Core dumps are written to ramdisk; they will not survive a reboot!

functioncoredump_setup

{

echo"Getting root...";

adb root;

adb wait-for-device;

echo"Remounting root partition read-write...";

adb shell mount -w -o remount -t rootfs rootfs;

sleep 1;

adb wait-for-device;

adb shell mkdir -p /cores;

adb shell mount -t tmpfs tmpfs /cores;

adb shell chmod 0777 /cores;

echo"Granting SELinux permission to dump in /cores...";

adb shell restorecon -R /cores;

echo"Set core pattern.";

adb shell 'echo /cores/core.%p > /proc/sys/kernel/core_pattern';

echo"Done."

}

# coredump_enable - enable core dumps for the specified process

# $1 = PID of process (e.g., $(pid mediaserver))

#

# NOTE:coredump_setup must have been called as well for a core

# dump to actually be generated.

functioncoredump_enable

{

localPID= $1;

if[ -z " $PID" ]; then

printf"Expecting a PID!\n";

return;

fi;

echo"Setting core limit for $PIDto infinite..." ;

adb shell /system/bin/ ulimit-P $PID-c unlimited

}

常用方法

给 system_server 配置 coredump 参数,由于目标进程的 coredump 生成的目录受 selinux 权限限制,因此这种方法配置抓 coredump 的方法要注意目标进程对哪些目录文件有读写的 selinux 权限,再配置相应的目录。

adb wait- for-device

adb root

adb shell mkdir/data/cores

adb shell chmod777/data/cores

#adb shell setenforce 0

adb shell restorecon -R /data/cores

adb shell 'echo /data/cores/core.%e.%p > /proc/sys/kernel/core_pattern'

adb shell 'system/bin/ulimit -P `pidof system_server` -c unlimited'

#adb shell 'echo 2 > /proc/sys/fs/suid_dumpable'

注意:确定问题与selinux权限无关,可以通过adb shell setenforce 0关闭selinux权限

给 com.android.settings 配置抓取 coredump 的参数,由于前面的配置中 /data/cores 目录恢复 selinux 权限后如下:

drwxrwxrwx 2 root root u:object_r:system_data_file:s0 3452 2022-07-04 15:08 cores

我们知道 app 一定有权限对自身的 /data/data/$PACKAGE/ 目录下的文件具有读写权限,于是可以配置成如下参数:

adb wait- for-device

adb root

adb shell mkdir / data/ data/com.android.settings/cores

adb shell chmod 777/ data/ data/com.android.settings/cores

adb shell restorecon -R / data/ data/com.android.settings/cores

adb shell 'echo /data/data/com.android.settings/cores/core.%e.%p > /proc/sys/kernel/core_pattern'

adb shell 'system/bin/ulimit -P `pidof com.android.settings` -c unlimited'

#adb shell 'echo 2 > /proc/sys/fs/suid_dumpable'

当我们在机器上验证对这个app进行kill -11模拟时

$ kill -11 `pidof com.android.settings`

$ ls /data/data/com.android.settings/cores/core.ndroid.settings.27946

参数说明

coredump_filter 进程默认值是0x23,只抓取:私有匿名/共享匿名/私有大尺寸页,需要抓全部内存信息则 adb shell 'echo 0x27 > /proc/$PID/coredump_filter' 即可。

/proc/$PID/coredump_filter

bit0: 私有匿名

bit1: 共享匿名

bit2: 有底层文件的私有映射

bit3: 有底层文件共享映射

bit4: ELF头

bit5: 私有大尺寸页

bit6: 共享大尺寸页

core_pattern 控制生成 core 的文件名,以及输出的 core 的位置。例如:

adb shell 'echo /data/cores/core.%p > /proc/sys/kernel/core_pattern'

/proc/sys/kernel/core_pattern

%p: 添加pid

%u: 添加当前uid

%g: 添加当前gid

%s: 添加导致产生core的信号

%t: 添加core文件生成时的unix时间

%h: 添加主机名

%e: 添加命令名

%E: 可执行文件的路径名,用斜杠(’/’)替换为感叹号(’!’)。

当程序调用了 seteuid/setegid 改变进程的有效用户或组,则在默认情况下系统不会为这些进程生成 core,此时你可能需要调整 suid_dumpable 参数进入调试模式或安全模式下进行。

/proc/sys/fs/suid_dumpable

0:默认模式

1:调试模式

2:安全模式

文件格式

core 文件也是ELF文件的一种,因此它的主体格式组成部分与 ELF 文件相

同,以案例讲解的 core 文件为例,它主要组成部分为 /proc/self/maps 下的VMA以及各个线程寄存器。其中寄存器信息存放在PT_NOTE,各VMA存放在 PT_LOAD,当被过滤掉的 VMA,它只有 Program Header 描述,没有对应的 segment。

离线调试

注:MTK 平台的 MINIDUMP 也是 coredump 的一种,它所保存的内存信息有限,core 的分析可以使用 GDB、lldb 等调试工具,如何使用这些调试工具,这里就不一一介绍。

$ ~/work/debug/gdb_arm64/gdb-12.1/output/bin/aarch64-linux-gdb

当我们没有符号表时,仅加载 core-file 也是可以使用的。

(gdb) core-file PROCESS_MINIDUMP

当我们有相应的符号表时,即可加载符号表目录

(gdb) set solib-search-path symbols/

或者

(gdb) set sysroot symbols/

(gdb) info sharedlibrary

显示所有共享库的地址范围

(gdb) info registers

显示当前线程的当前帧寄存器信息

(gdb) info locals

显示当前帧的局部变量

(gdb) info thread

显示有哪些线程

(gdb) thread 2

切换到2号线程上

(gdb) bt

显示当前线程的堆栈

(gdb) thread apply all [command]

例如打印所有线程堆栈

(gdb) thread apply all bt

让所有线程做同样的命令

(gdb) frame

显示当前帧信息

(gdb) frame 3

切换到#3帧

(gdb) print 或 (gdb) p

打印变量

(gdb) ptype 'android::AHandler'

查看某个class或struct的数据结构

(gdb) ptype /o 'android::AHandler'

查看数据类型占多少字节

(gdb) set print pretty on

格式化输出

(gdb) set log on

保存gdb输出的结果

(gdb) x /gx 0x7c02d886f0

读取地址0x7c02d886f0内存内容,其中输出格式如下:

o(octal), x(hex), d(decimal),

u(unsigned decimal), t(binary),

f(float), a(address), i(instruction),

c(char), s(string),

z(hex, zero padded on the left).

(gdb) disassemble 0x0000007c95de6708

(gdb) disassemble 'android::AMessage::setTarget'

显示函数汇编信息

5

内存检测机制

ASAN

在 Android 11 之后的 AOSP master 中,弃用了 arm64 上的平台开发 ASAN,改为使用 HWASAN,AddressSanitizer (ASAN) 是一种基于编译器的快速检测工具,用于检测内存错误。

Stack and heap buffer overflow/underflow

堆栈和堆缓冲区上溢/下溢

Heap use after free

使用已释放的内存

Stack use outside scope

超出栈范围

Double free/wild free

多次释放内存/错误释放

HWASAN

HWASan 仅适用于 Android 10 及更高版本,且只能用于 AArch64 硬件,具备 ASAN 同样的检测能力,在 Linux-4.14 版本以上支持 tagged-pointers才能使用。

编译Android版本时带入环境变量如下:

$ export SANITIZE_TARGET=hwaddress

跳过某个模块,在相应模块下的Android.bp文件下添加内容如下:

sanitize: {

hwaddress: false,

address: false,

},

Android.mk则添加内容如下:

LOCAL_NOSANITIZE := hwaddress

APP构建支持HWASAN则在Application.mk下添加内容如下:

APP_STL := c++_shared

APP_CFLAGS := -fsanitize=hwaddress -fno-omit-frame-pointer

APP_LDFLAGS := -fsanitize=hwaddress

MTE

最新 Android S 上引入的 ARM Memory Tagging Extension (MTE),MTE 的原理和 HWASan 类似,最大的区别在于 HWASan 需要重新编译,在所有内存访问前插桩相应的检测函数来实现,而 MTE 则在读写指令内部完成检测,完全由硬件支持。

更多内容转至掘金大佬-芦半山:

6

野指针的危害

当所指向的对象被释放或者收回,但是对该指针没有作任何的修改,以至于该指针仍旧指向已经回收的内存地址,此情况下该指针便称 Wild pointer (野指针),如果这个野指针所指向的内存被分配给其它指针,而这个野指针仍在使用,程序将难以预测。

# include<stdio.h>

classA{

public:

virtual~A = default;

virtualvoidfoo{

printf( "A:%ld\n", a);

}

longa;

};

classB{

public:

virtual~B = default;

virtualvoidfoo{

printf( "B:%ld\n", b);

}

longb;

};

intmain( int/*argc*/, char** /*argv[]*/) {

A *a = newA;

A *a_bak = a;

a->a = 1000L;

printf( "A ptr = %p\n", a); deletea; // 此时a指针已经被free掉,那么指针a_bak是个野指针

B *b = newB;

printf( "B ptr = %p\n", b);

b->b = 2000L;

b->foo;

a_bak->foo; // 此处程序会运行怎么结果

deleteb;

return0;

}

以上程序会怎么输出,由于 B 与 A 的数据结构大小一致,运行在同一个线程,极大可能会分配刚释放的指针地址,因此这个程序最后指针 b 与指针 a_bak 是同一个。

# ./data/Tester64

B:2000

B:2000

像以上这个结果,那程序如下改写,那么可怕的事情就会出现,下面这个程序会报什么错。

# include<stdio.h>

classA{

public:

longbad;

longa;

};

classB{

public:

virtual~B = default;

virtualvoidfoo{

b = 2000L;

printf( "B:%ld\n", b);

}

longb;

};

intmain( int/*argc*/, char** /*argv[]*/) {

A *a = newA;

A *a_bak = a;

printf( "A ptr = %p\n", a);

deletea;

B *b = newB;

printf( "B ptr = %p\n", b);

a_bak->bad = 0x20L;

b->foo;

deleteb;

return0;

}

上面这个程序在27行处,bad 会将 B 的虚函数表破坏,导致28和29行在虚函数表上寻找 foo 函数与析构函数地址时发段错误。

Process uptime: 0s

Cmdline: ./data/Tester64

pid: 12652, tid: 12652, name: Tester64 >>> ./data/Tester64 <<<

uid: 0

tagged_addr_ctrl: 0000000000000001

signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x28

Cause: null pointer dereference

x8 0000000000000020 x9 a454ef76eb4317d3 x10 0000000000004001 x11 0000000000000000

x12 0000000000000000 x13 0000000000000002 x14 0000000000000010 x15 0000000000000010

x24 0000000000000000 x25 0000000000000000 x26 0000000000000000 x27 0000000000000000

x28 0000000000000000 x29 0000007fe173d2e0

backtrace:

#00 pc 00000000000010d0 /data/Tester64 (main+124)

#01 pc 000000000008436c /apex/com.android.runtime/lib64/bionic/libc.so (__libc_init+100)

程序会出错还好,如果程序不会出错,仍继续运行,那么这个程序将变得很可怕,因为你不知道程序将会怎么运行,像下面这个程序,刻意改写控制程序往其它方向运行。

# include<stdio.h>

classA{

public:

void*bad;

longa;

};

classB{

public:

virtual~B {

printf( "delete B\n");

};

virtualvoidfoo{

b = 2000L;

printf( "B:%ld\n", b);

}

longb;

};

voidfunc1{

printf( "Hello !!\n");

}

voidfunc2{

printf( "GoGoGo !!\n");

}

intmain( int/*argc*/, char** /*argv[]*/) {

A *a = newA;

A *a_bak = a;

printf( "A ptr = %p\n", a);

deletea;

B *b = newB;

printf( "B ptr = %p\n", b);

long*data = newlong[ 4] { 0x0L, ( long)func2, ( long)func1, 0x0L};

a_bak->bad = data;

printf( "Test .. \n");

b->foo;

deleteb;

printf( "Done.\n");

return0;

}

# ./data/Tester64

Test ..

Hello !!

GoGoGo !!

Done.

7

数组越界的危害

比起前面的野指针,数组越界大多数情况都能被 HWASAN 等内存检测发现,当然前面的野指针情况也是能被检测发现的,数组越界往往会出现某个对象的内存前半部分被污染,而后半部分数据正常的情况。

# include<stdio.h>

classA{

public:

longa = 0x55AA;

longb = 0xDEAD;

};

intmain( int/*argc*/, char** /*argv[]*/) {

long*b = newlong[ 2] { 0x0L, 0x1L};

A *a = newA;

printf( "A:%p\n", a);

printf( "B:%p\n", b);

b[ 2] = 0xDEAD;

printf( "B2:%p\n", &b[ 2]);

printf( "0x%lx-0x%lx\n", a->a, a->b);

return0;

}

因为 b 的数据大小与对象 a 的数据大小相同,因此在这个程序刚启动分配指针地址时,基本会连在一块,因此 b[2] 越界行为将对象 a 的内容破坏,往往程序运行了很久,内存碎片化增加,就不知道 b[2] 越界操作会破坏哪个对象的内存。

# ./data/Tester64

0xdead-0xdead

8

机器码翻译

本文案例中的 tombstone 文件 PC 跑飞,没有落在 text 段地址上,这里换一份 tombstone 方便说明,我们可以通过编译的方法生成对应的 ELF 文件,即可用 objdump 得到对应的汇编。

signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x40

Cause: null pointer dereference

x8 db4cf552ad46f717 x9 0000000000000001 x10 00000000000349e0 x11 0000007bc0000000

x16 0000007ca3543d50 x17 0000007ca35338ec x18 0000007b38c74000 x19 0000000000000020

x24 0000007b548e76c0 x25 0000007b548e9000 x26 0000007b548e7b30 x27 0000007b548e7b18

x28 0000007b548e7a10 x29 0000007b548e7410

memory near pc (/apex/com.android.appsearch/lib64/libicing.so):

0000007a8e4d0de0 a90557f6f90023f7 9100c3fda9064ff4 .#…W…O……

0000007a8e4d0df0 f94016c8d53bd056 f81f83a8aa0003f3 V.;…@………

编写汇编文件 code.S,内容如下:

.inst0 xf90023f7

.inst0 xa90557f6

.inst0 xa9064ff4

.inst0 x9100c3fd

.inst0 xd53bd056

.inst0 xf94016c8

.inst0 xaa0003f3

.inst0 xf81f83a8.inst0 x39408008.inst0 x350001c8

.inst0 xb0fffde2

.inst0 x91181c42

.inst0 x910023e0

.inst0 x2a1f03e1

.inst0 x52808ee3

.inst0 x910023f4

.inst0 x9400d695

.inst0 x91002280

.inst0 x90fffde1

.inst0 x912b7421

编译:aarch64-linux-android-as code.S -o code.o

反汇编:aarch64-linux-android-objdump -d code.o

code.o: file format elf64-littleaarch64

Disassembly of section .text:

0000000000000000 <.text>:

0: f90023f7 str x23, [sp, #64]

4: a90557f6 stp x22, x21, [sp, #80]

8: a9064ff4 stp x20, x19, [sp, #96]

c: 9100c3fd add x29, sp, #0x30

10: d53bd056 mrs x22, tpidr_el0

14: f94016c8 ldr x8, [x22, #40]

18: aa0003f3 mov x19, x0

1c: f81f83a8 stur x8, [x29, #-8]

28: b0fffde2 adrp x2, fffffffffffbd000 <.text+0xfffffffffffbd000>

2c: 91181c42 add x2, x2, #0x607

34: 2a1f03e1 mov w1, wzr

38: 52808ee3 mov w3, #0x477 // #1143

40: 9400d695 bl 35a94 <.text+0x35a94>

48: 90fffde1 adrp x1, fffffffffffbc000 <.text+0xfffffffffffbc000>

4c: 912b7421 add x1, x1, #0xadd

8

汇编翻译

有些时候我们可能需要手动修改 ELF 文件的机器码达到一些调试的目的,这时候我们就需要知道汇编对应的机器码,去查手册也比较麻烦,这时我们直接编写汇编文件即可。

编写汇编文件 code_asm.S,内容如下:

strx30, [sp, #-16]!

str x29, [sp, #-16]!

编译:aarch64-linux-android-as code_asm.S -o code_asm.o

反汇编:aarch64-linux-android-objdump -d code_asm.o

code_asm.o: file format elf64-littleaarch64

Disassembly of section .text:

0000000000000000 <.text>:

0: f81f0ffe str x30, [sp, #-16]!

4: f81f0ffd str x29, [sp, #-16]!

9

案例讲解

错误日志

Process uptime: 0s

Cmdline: mediaserver64

pid: 1139, tid: 11981, name: NPDecoder >>> mediaserver64 <<<

uid: 1013

tagged_addr_ctrl: 0000000000000001

signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0x7c02d886f0

x8 0000000000000080 x9 0000007ca126fed7 x10 0000000000000006 x11 0000007bfd0a81fc

x12 9ef8a95ca9649dbe x13 e44782d5ac38720e x14 0000007bfd0a8030 x15 0000001e56307b5c

x28 0000007ca13c2c28 x29 0000007c02d886f0

backtrace:

#00 pc 00000000000f86f0 [anon:stack_and_tls:11981]

分析

直接原因

memory near pc ([anon:stack_and_tls:11981]):

0000007c02d886e0 0000007c02d88700 6f2ab3b40fa2f8ef ….|………o

0000007c02d886f0 0000007c02d88750 0000007ca133f8e0 P…|…..3.|…

0000007c02d88700 0000000000000002 0000000000000000 …………….

code.o: file format elf64-littleaarch64

Disassembly of section .text:

0000000000000000 <.text>:

0: 02d886f0 .inst 0x02d886f0 ; undefined

4: 0000007c udf #124

从 tombstone 的 backtrace 信息上看,很明显是PC跑飞了,跑到线程栈地址空间下,由于栈内存地址不可运行,因此被拒绝访问而出错,如果有x的权限那么也是错误,会发生 SIGILL 指令错误。

栈回溯

从 tombstone 中的 x29 和 SP 寄存器附近的内存中进行栈回溯,由于大小受限,往往无法回溯到栈底,无法完全证明程序就是经过这几个函数。

memory near x29 ([anon:stack_and_tls:11981]):

0000007c02d886e0 0000007c02d88700 6f2ab3b40fa2f8ef ….|………o

0000007c02d886f0 0000007c02d88750 0000007ca133f8e0 P…|…..3.|… 【x29 = 0x0000007c02d886f0】

0000007c02d88700 0000000000000002 0000000000000000 …………….

0000007c02d88720 0000000000000000 0000007c02d88808 …………|…

0000007c02d88740 0000007c02d89000 6f2ab3b40fa2f8ef ….|………o

0000007c02d88750 0000007c02d88830 0000007ca796ee7c 0…|…|…|…

0000007c02d88760 0000007ca79f3dd8 0000007ca79edb80 .=..|…….|…

0000007c02d88780 0000000000000000 0000000000000002 …………….

0000007c02d887b0 0000007c02d88830 0000007ca796d420 0…|… …|…

0000007c02d887c0 0000007c02d88830 0000007ca796d460 0…|…`…|…

这里我们通过 FP(x29) 回溯,如果这个 x29 是有效的,那么 x29 存放的是上一个 Caller 的 FP 寄存器,x29+0x8 的地址存放上一个 Caller 的 LR 寄存器。

0x0000007ca133f8e0 地址落在 /system/lib64/libstagefright.so 的 TEXT 段上。

[14] .text PROGBITS 0000000000081000 00081000

000000000012bed8 0000000000000000 AX 0 0 4096

Funcation_Offset = PC - TEXT_LOAD + .text_offset

(gdb) p /x 0x0000007ca133f8e0-0x7ca128f000+0x81000

(gdb) x /2i 0x1318e0-0x4

0x1318dc <_ZN7android10MediaCodec16queueInputBufferEmmmljPNS_7AStringE+200>: bl 0x1ad390 <_ZN7android8AMessageC1EjRKNS_2spIKNS_8AHandlerEEE@plt>

0x1318e0 <_ZN7android10MediaCodec16queueInputBufferEmmmljPNS_7AStringE+204>: eor x8, x29, x25

(gdb) p _ZN7android10MediaCodec16queueInputBufferEmmmljPNS_7AStringE+200

struct android::AString *)) 0x1318dc < android::MediaCodec::queueInputBuffer(unsigned long, unsigned long, unsigned long, long, unsigned int, android::AString*)+200>

因此0x0000007ca133f8e0所在的函数是android::MediaCodec::queueInputBuffer。

一般我们直接 PC 减去库文件的起始地址即可得到函数的偏移地址。

即:0x0000007ca133f8e0 - 0x0000007ca120e000

最后这份 tombstone 只能通过 FP 推导出这两个函数,是否正确待取证,这也体现了 tombstone 的局限性,此时我们希望能完整的推导,并取证栈是完整性,需要使用到 coredump 文件进行。

FP

LR

函数名

0x0000007c02d886f0

0x0000007c02d88750

0x0000007ca133f8e0

android::MediaCodec::queueInputBuffer

0x0000007c02d88750

0x0000007c02d88830

0x0000007ca796ee7c

android::NuPlayer::Decoder::onInputBufferFetched

MTK 的 aee 会将保存一份 minidump ,将 minidump 装载到gdb上回溯。

core-file PROCESS_MINIDUMP

(gdb) bt

#0 0x0000007c02d886f0 in ??

由此可见 GDB 也无法正确回溯这个栈,我们重复前面的步骤在完整的栈上进行计算,可以得到

当前FP

Caller FP

Caller LR

Caller 函数名

0x7c02d886f0

0x0000007c02d88750

0x0000007ca133f8e0

android::MediaCodec::queueInputBuffer

0x7c02d88750

0x0000007c02d88830

0x0000007ca796ee7c

android::NuPlayer::Decoder::onInputBufferFetched

0x7c02d88830

0x0000007c02d888a0

0x0000007ca796c984

android::NuPlayer::Decoder::doRequestBuffers

0x7c02d888a0

0x0000007c02d88920

0x0000007ca7963d48

android::NuPlayer::DecoderBase::onRequestInputBuffers

0x7c02d88920

0x0000007c02d88980

0x0000007ca79717bc

android::NuPlayer::Decoder::handleAnInputBuffer

0x7c02d88980

0x0000007c02d88a20

android::NuPlayer::Decoder::onMessageReceived

0x7c02d88a20

0x0000007c02d88a70

0x0000007c95de2874

android::AHandler::deliverMessage

0x7c02d88a70

0x0000007c02d88ad0

0x0000007c95de8608

android::AMessage::deliver

0x7c02d88ad0

0x0000007c02d88b30

0x0000007c95de3bbc

android::ALooper::loop

0x7c02d88b30

0x0000007c02d88ba0

android::Thread::_threadLoop

0x7c02d88ba0

0x0000007c02d88c10

thread_data_t::trampoline

0x7c02d88c10

0x0000007c02d88c50

0x0000007caaf3be74

__pthread_start

0x7c02d88c50

0x0000007c02d88c80

0x0000007caaedb830

__start_thread

这个线程栈内存能被回溯到 __start_thread 函数,基本可以确定这个栈是可信的,并且最后的 FP 地址存放了 Caller FP 与 Caller LR,并且从汇编的指令流程上是吻合的,可以确定最后的 FP 应该是某个函数的栈顶,记这个函数为 A 函数。

A 函数是谁?

(gdb) x /12gx 0x7c02d886d0

0x7c02d886e0: 0x0000007c02d88700 0x6f2ab3b40fa2f8ef

此处是 A 函数的栈。

0x7c02d886f0: 0x0000007c02d88750 0x0000007ca133f8e0// FP

0x7c02d88700: 0x0000000000000002 0x0000000000000000

0x7c02d88720: 0x0000000000000000 0x0000007c02d88808

当 A 函数运行完成后返回 LR 地址 0x0000007ca133f8e0 (android::MediaCodec::queueInputBuffer)

注:以下“_ZN7android10MediaCodec16queueInputBufferEmmmljPNS_7AStringE” 用 "android::MediaCodec::queueInputBuffer" 替代

(gdb) disassemble 0x0000007ca133f8e0-0x20,+0x30

Dump of assembler code from 0x7ca133f8c0 to 0x7ca133f8f0:

0x0000007ca133f8c0 <android::MediaCodec::queueInputBuffer+172>: mov x1, sp

0x0000007ca133f8c4 <android::MediaCodec::queueInputBuffer+176>: mov x0, x26

0x0000007ca133f8c8 <android::MediaCodec::queueInputBuffer+180>: bl 0x7ca13bb130 <_ZNK7android7RefBase9incStrongEPKv@plt>

0x0000007ca133f8cc <android::MediaCodec::queueInputBuffer+184>: mov x2, sp

0x0000007ca133f8d0 <android::MediaCodec::queueInputBuffer+188>: mov x0, x25

0x0000007ca133f8d4 <android::MediaCodec::queueInputBuffer+192>: mov w1, #0x6549 // #25929

0x0000007ca133f8d8 <android::MediaCodec::queueInputBuffer+196>: movk w1, #0x7175, lsl #16

0x0000007ca133f8dc <android::MediaCodec::queueInputBuffer+200>: bl 0x7ca13bb390 <_ZN7android8AMessageC1EjRKNS_2spIKNS_8AHandlerEEE@plt>

0x0000007ca133f8e0 <android::MediaCodec::queueInputBuffer+204>: eor x8, x29, x25

0x0000007ca133f8e4 <android::MediaCodec::queueInputBuffer+208>: cmp x8, #0xfff

0x0000007ca133f8e8 <android::MediaCodec::queueInputBuffer+212>: str x25, [sp, #8]

0x0000007ca133f8ec <android::MediaCodec::queueInputBuffer+216>: b.hi 0x7ca133f8f4 <_ZN7android10MediaCodec16queueInputBufferEmmmljPNS_7AStringE+224> // b.pmore

End of assembler dump.

这个 A 函数是 android::AMessage::AMessage(unsigned int, android::sp<android::AHandler const> const&)?

2523status_tMediaCodec::queueInputBuffer(

2524size_tindex,

2525size_toffset,

2526size_tsize,

2527int64_tpresentationTimeUs,

2528uint32_tflags,

2529AString *errorDetailMsg) {

2530if(errorDetailMsg != NULL) {

2531errorDetailMsg->clear;

2532}

2533

2534sp<AMessage> msg = newAMessage(kWhatQueueInputBuffer, this); 2535msg->setSize( "index", index);

2536msg->setSize( "offset", offset);

2537msg->setSize( "size", size);

2538msg->setInt64( "timeUs", presentationTimeUs);

2539msg->setInt32( "flags", flags);

2540msg->setPointer( "errorDetailMsg", errorDetailMsg);

2541

2542sp<AMessage> response;

2543returnPostAndAwaitResponse(msg, &response);

2544}

A 函数会是 AMessage 的构造方法吗?

(gdb) disassemble 0x7ca13bb390

Dump of assembler code for function _ZN7android8AMessageC1EjRKNS_2spIKNS_8AHandlerEEE@plt:

0x0000007ca13bb390 <+0>: adrp x16, 0x7ca13cd000

0x0000007ca13bb394 <+4>: ldr x17, [x16, #992]

0x0000007ca13bb398 <+8>: add x16, x16, #0x3e0

0x0000007ca13bb39c <+12>: br x17

End of assembler dump.

(gdb) x /gx 0x7ca13cd000+0x3e0

0x7ca13cd3e0 <_ZN7android8AMessageC1EjRKNS_2spIKNS_8AHandlerEEE@got.plt>: 0x0000007c95de66a8

(gdb) disassemble 0x0000007c95de66a8

Dump of assembler code for function _ZN7android8AMessageC2EjRKNS_2spIKNS_8AHandlerEEE:

0x0000007c95de66a8 <+0>: stp x29, x30, [sp, #-48]!

0x0000007c95de66ac <+4>: str x21, [sp, #16]

0x0000007c95de66b0 <+8>: stp x20, x19, [sp, #32]

0x0000007c95de66b4 <+12>: mov x29, sp

0x0000007c95de66b8 <+16>: mov x19, x2

0x0000007c95de66bc <+20>: mov w20, w1

0x0000007c95de66c0 <+24>: mov x21, x0

0x0000007c95de66c4 <+28>: bl 0x7c95dfb490 <_ZN7android7RefBaseC2Ev@plt>

0x0000007c95de66c8 <+32>: movi v0.2d, #0x0

0x0000007c95de66cc <+36>: adrp x8, 0x7c95dfd000 <__dso_handle_const>

0x0000007c95de66d0 <+40>: ldr x8, [x8, #2744]

0x0000007c95de66d4 <+44>: str w20, [x21, #16]

0x0000007c95de66d8 <+48>: stur q0, [x21, #24]

0x0000007c95de66dc <+52>: add x8, x8, #0x10

0x0000007c95de66e0 <+56>: stur q0, [x21, #40]

0x0000007c95de66e4 <+60>: mov x0, x21

0x0000007c95de66e8 <+64>: mov x1, x19

0x0000007c95de66ec <+68>: stur q0, [x21, #56]

0x0000007c95de66f0 <+72>: str x8, [x21]

0x0000007c95de66f4 <+76>: str xzr, [x21, #72]

0x0000007c95de66f8 <+80>: ldp x20, x19, [sp, #32]

0x0000007c95de66fc <+84>: ldr x21, [sp, #16]

0x0000007c95de6700 <+88>: ldp x29, x30, [sp], #48

0x0000007c95de6704 <+92>: b 0x7c95dfb950 <_ZN7android8AMessage9setTargetERKNS_2spIKNS_8AHandlerEEE@plt>

End of assembler dump.

这个函数的栈大小为0x30,并且 FP=SP,如果A函数是 AMessage 的构造函数,那么在该函数未退栈时 FP=SP 不成立。

继续往前看栈上内存信息:

0x7c02d88610: 0x0000007c02d88660 0x0000007c95de6510

0x7c02d88620: 0x0000007c02d89000 0x6f2ab3b40fa2f8ef

0x7c02d88630: 0x0000007c02d88670 0x0000007caae98c58

0x7c02d88640: 0x0000007c02d886b0 0x0000007caae9ce40

0x7c02d88650: 0x0000000000000005 0x6f2ab3b40fa2f8ef

0x7c02d88660: 0x0000007c02d886a0 0x0000007caae98c58

0x7c02d88670: 0x0000000000000002 0x0000000000000000

0x7c02d886a0: 0x0000007c02d886c0 0x0000007c9dccfb74 operator new(unsigned long)

0x7c02d886b0: 0x0000000000000018 0x0000007caae98c58

0x7c02d886c0: 0x0000007c02d886f0 0x0000007c95de6754

0x7c02d886e0: 0x0000007c02d88700 0x6f2ab3b40fa2f8ef

此处是 A 函数的栈。

0x7c02d886f0: 0x0000007c02d88750 0x0000007ca133f8e0// FP

0x7c02d88700: 0x0000000000000002 0x0000000000000000

0x7c02d88720: 0x0000000000000000 0x0000007c02d88808

地址 0x7c02d886c0 存放的内存正好指向 A 函数 FP 地址,因此这里很可能是 A 函数的下一个函数的栈

函数栈{x29,x30} = {0x0000007c02d886f0,0x0000007c95de6754}

注:以下将字符串"_ZN7android8AMessage9setTargetERKNS_2spIKNS_8AHandlerEEE"更替为"android::AMessage::setTarget"

(gdb) disassemble 0x0000007c95de6754-0x20,+0x40

Dump of assembler code from 0x7c95de6734 to 0x7c95de6774:

0x0000007c95de6734 <android::AMessage::setTarget+44>: cbz x21, 0x7c95de6880 <android::AMessage::setTarget+376>

0x0000007c95de6738 <android::AMessage::setTarget+48>: mov x20, x1

0x0000007c95de673c <android::AMessage::setTarget+52>: ldr x1, [x21]

0x0000007c95de6740 <android::AMessage::setTarget+56>: mov x0, #0x2ddc // #11740

0x0000007c95de6744 <android::AMessage::setTarget+60>: movk x0, #0x568e, lsl #16

0x0000007c95de6748 <android::AMessage::setTarget+64>: movk x0, #0x8c5e, lsl #32

0x0000007c95de674c <android::AMessage::setTarget+68>: movk x0, #0x7974, lsl #48

0x0000007c95de6750 <android::AMessage::setTarget+72>: bl 0x7c95dfb370 <__cfi_slowpath@plt>

0x0000007c95de6754 <android::AMessage::setTarget+76>: ldr w8, [x21, #16]

0x0000007c95de6758 <android::AMessage::setTarget+80>: str w8, [x19, #20]

0x0000007c95de675c <android::AMessage::setTarget+84>: ldr x21, [x20]

0x0000007c95de6760 <android::AMessage::setTarget+88>: ldr x1, [x21]

0x0000007c95de6764 <android::AMessage::setTarget+92>: mov x0, #0x2ddc // #11740

0x0000007c95de6768 <android::AMessage::setTarget+96>: movk x0, #0x568e, lsl #16

0x0000007c95de676c <android::AMessage::setTarget+100>: movk x0, #0x8c5e, lsl #32

0x0000007c95de6770 <android::AMessage::setTarget+104>: movk x0, #0x7974, lsl #48

End of assembler dump.

(gdb) p _ZN7android8AMessage9setTargetERKNS_2spIKNS_8AHandlerEEE+72

A函数会是android::AMessage::setTarget?

(gdb) disassemble 0x0000007c95de6708,+0x30

Dump of assembler code from 0x7c95de6708 to 0x7c95de6738:

0x0000007c95de6708 <android::AMessage::setTarget+0>: sub sp, sp, #0x60

0x0000007c95de670c <android::AMessage::setTarget+4>: stp x29, x30, [sp, #32]

0x0000007c95de6710 <android::AMessage::setTarget+8>: stp x24, x23, [sp, #48]

0x0000007c95de6714 <android::AMessage::setTarget+12>: stp x22, x21, [sp, #64]

0x0000007c95de6718 <android::AMessage::setTarget+16>: stp x20, x19, [sp, #80]

0x0000007c95de671c <android::AMessage::setTarget+20>: add x29, sp, #0x20

0x0000007c95de6720 <android::AMessage::setTarget+24>: mrs x23, tpidr_el0

0x0000007c95de6724 <android::AMessage::setTarget+28>: ldr x8, [x23, #40]

0x0000007c95de6728 <android::AMessage::setTarget+32>: stur x8, [x29, #-8]

0x0000007c95de672c <android::AMessage::setTarget+36>: ldr x21, [x1]

0x0000007c95de6730 <android::AMessage::setTarget+40>: mov x19, x0

0x0000007c95de6734 <android::AMessage::setTarget+44>: cbz x21, 0x7c95de6880 <android::AMessage::setTarget+376>

从汇编看函数栈正是0x60与A函数的栈相等。

回去再看看 AMessage::AMessage 的汇编,可以发现它是退栈后跳转到 android::AMessage::setTarget

0x0000007c95de6700 <+88>: ldp x29, x30, [sp], #48

0x0000007c95de6704 <+92>: b 0x7c95dfb950 <_ZN7android8AMessage9setTargetERKNS_2spIKNS_8AHandlerEEE@plt>

再回看 setTarget 汇编刚好 x0=0x79748c5e568e2ddc

0x0000007c95de6740 <android::AMessage::setTarget+56>: mov x0, #0x2ddc // #11740

0x0000007c95de6744 <android::AMessage::setTarget+60>: movk x0, #0x568e, lsl #16

0x0000007c95de6748 <android::AMessage::setTarget+64>: movk x0, #0x8c5e, lsl #32

0x0000007c95de674c <android::AMessage::setTarget+68>: movk x0, #0x7974, lsl #48

0x0000007c95de6750 <android::AMessage::setTarget+72>: bl 0x7c95dfb370 <__cfi_slowpath@plt>

x16 = 0x0000007c95dfdb70与x17= 0x0000007c9844f118 也满足tombstone信息。

(gdb) disassemble 0x7c95dfb370

Dump of assembler code for function __cfi_slowpath@plt:

0x0000007c95dfb370 <+0>: adrp x16, 0x7c95dfd000 <__dso_handle_const>

0x0000007c95dfb374 <+4>: ldr x17, [x16, #2928]

0x0000007c95dfb378 <+8>: add x16, x16, #0xb70

0x0000007c95dfb37c <+12>: br x17

End of assembler dump.

(gdb) x /gx 0x7c95dfd000+0xb70

0x7c95dfdb70 <__cfi_slowpath@got.plt>: 0x0000007c9844f118

(gdb) disassemble 0x0000007c9844f118

Dump of assembler code for function __cfi_slowpath(uint64_t, void):

0x0000007c9844f118 <+0>: stp x29, x30, [sp, #-16]!

0x0000007c9844f11c <+4>: mov x29, sp

0x0000007c9844f120 <+8>: lsr x8, x1, #17

0x0000007c9844f124 <+12>: and x8, x8, #0x7ffffffffe

0x0000007c9844f12c <+20>: cmp x8, x9

0x0000007c9844f130 <+24>: b.hi 0x7c9844f14c <__cfi_slowpath(uint64_t, void)+52> // b.pmore

0x0000007c9844f138 <+32>: ldr x9, [x9]

0x0000007c9844f13c <+36>: ldrh w8, [x9, x8]

0x0000007c9844f140 <+40>: cmp w8, #0x1

0x0000007c9844f144 <+44>: b.eq 0x7c9844f15c <__cfi_slowpath(uint64_t, void)+68> // b.none

0x0000007c9844f148 <+48>: cbnz w8, 0x7c9844f164 <__cfi_slowpath(uint64_t, void)+76>

0x0000007c9844f14c <+52>: xpaclri

0x0000007c9844f150 <+56>: mov x2, xzr

0x0000007c9844f154 <+60>: mov x3, x30

0x0000007c9844f158 <+64>: bl 0x7c9844f2a0 <__loader_cfi_fail@plt>

0x0000007c9844f15c <+68>: ldp x29, x30, [sp], #16

0x0000007c9844f160 <+72>: ret

0x0000007c9844f164 <+76>: and x9, x1, #0xfffffffffffc0000

0x0000007c9844f168 <+80>: sub x8, x9, x8, lsl #12

0x0000007c9844f16c <+84>: add x3, x8, #0x42, lsl #12

0x0000007c9844f170 <+88>: mov x2, xzr

0x0000007c9844f174 <+92>: ldp x29, x30, [sp], #16

0x0000007c9844f178 <+96>: br x3

End of assembler dump.

因此 A 函数的栈是 android::AMessage::setTarget(android::sp<android::AHandler const> const&)

计算汇编走向

根据 tombstone 最后的栈地址 SP=0x0000007c02d886d0 说明函数 __cfi_slowpath(uint64_t, void) 已经退栈。

0x7c02d88620: 0x0000007c02d89000 0x6f2ab3b40fa2f8ef

0x7c02d88630: 0x0000007c02d88670 0x0000007caae98c58

0x7c02d88640: 0x0000007c02d886b0 0x0000007caae9ce40

0x7c02d88650: 0x0000000000000005 0x6f2ab3b40fa2f8ef

je_malloc

0x7c02d88660: 0x0000007c02d886a0 0x0000007caae98c58

0x7c02d88670: 0x0000000000000002 0x0000000000000000

malloc(size_t)

0x7c02d886a0: 0x0000007c02d886c0 0x0000007c9dccfb74 operator new(unsigned long)

0x7c02d886b0: 0x0000000000000018 0x0000007caae98c58

__cfi_slowpath(uint64_t, void*)

0x7c02d886c0: 0x0000007c02d886f0 0x0000007c95de6754

0x7c02d886e0: 0x0000007c02d88700 0x6f2ab3b40fa2f8ef

android::AMessage::setTarget(android::sp<android::AHandler const> const&)

0x7c02d886f0: 0x0000007c02d88750 0x0000007ca133f8e0

0x7c02d88700: 0x0000000000000002 0x0000000000000000

0x7c02d88720: 0x0000000000000000 0x0000007c02d88808

0x7c02d88740: 0x0000007c02d89000 0x6f2ab3b40fa2f8ef

android::MediaCodec::queueInputBuffer(unsigned long, unsigned long, unsigned long, long, unsigned int, android::AString*)

0x7c02d88750: 0x0000007c02d88830 0x0000007ca796ee7c

0x7c02d88760: 0x0000007ca79f3dd8 0x0000007ca79edb80

0x7c02d88780: 0x0000000000000000 0x0000000000000002

其中恢复其调用栈如下:

backtrace:

unknown?

android::AMessage::setTarget(android::sp<android::AHandler const> const&)

android::MediaCodec::queueInputBuffer(unsigned long, unsigned long, unsigned long, long, unsigned int, android::AString*)

...

__cfi_slowpath函数有两处退栈相关。

第一部分:

0x0000007c9844f14c <+52>: xpaclri

0x0000007c9844f150 <+56>: mov x2, xzr

0x0000007c9844f154 <+60>: mov x3, x30

0x0000007c9844f158 <+64>: bl 0x7c9844f2a0 <__loader_cfi_fail@plt>

0x0000007c9844f15c <+68>: ldp x29, x30, [sp], #16

0x0000007c9844f160 <+72>: ret

第二部分:

0x0000007c9844f164 <+76>: and x9, x1, #0xfffffffffffc0000

0x0000007c9844f168 <+80>: sub x8, x9, x8, lsl #12

0x0000007c9844f16c <+84>: add x3, x8, #0x42, lsl #12

0x0000007c9844f170 <+88>: mov x2, xzr

0x0000007c9844f174 <+92>: ldp x29, x30, [sp], #16

0x0000007c9844f178 <+96>: br x3

如果走的是第一部分退栈,那么 x16 和 x17 将与 tombstone 不符合,因此可以确定走的是第二部分。

这个函数非常的大,但有规律,很显然先将 x8与x0 作比较,并且数值与 tombstone 中

x0=0x79748c5e568e2ddc 类似,那么我们可以搜索部分关键字找到相应的 case。

0x0000007ca12920b4 <+4276>: mov x8, #0x2ddc // #11740

0x0000007ca12920b8 <+4280>: movk x8, #0x568e, lsl #16

0x0000007ca12920bc <+4284>: movk x8, #0x8c5e, lsl #32

0x0000007ca12920c0 <+4288>: movk x8, #0x7974, lsl #48

0x0000007ca12920c4 <+4292>: cmp x0, x8

tombstone 中 x9=0x0000007ca126fed7

0x0000007ca12963a4 <+21412>: ldrb w8, [x9, x8]

tombstone 中x8=0x0000000000000080

最后在此处退栈回到 android::AMessage::setTarget ,然后 PC=LR=0x0000007c02d886f0 发生段错误

结论

不难发现一个细节 0x7c02d886c0地址上存储的内容是有问题的。

__cfi_slowpath(uint64_t, void*)

0x7c02d886c0: 0x0000007c02d886f0 0x0000007c95de6754

0x7c02d886e0: 0x0000007c02d88700 0x6f2ab3b40fa2f8ef

android::AMessage::setTarget(android::sp<android::AHandler const> const&)

0x7c02d886f0: 0x0000007c02d88750 0x0000007ca133f8e0

0x7c02d88700: 0x0000000000000002 0x0000000000000000

0x7c02d88720: 0x0000000000000000 0x0000007c02d88808

0x0000007c9844f118 < __cfi_slowpath +0>: stp x29, x30, [sp, #-16]!

压栈应该是 0x7c02d886c0: 0x0000007c02d886f0 0x0000007c95de6754

0x0000007c9844f11c < __cfi_slowpath +4>: mov x29, sp

...

0x0000007c9844f174 < __cfi_slowpath +92>: ldp x29, x30, [sp], #16

0x0000007c9844f178 < __cfi_slowpath +96>: br x3

压栈应该是 0x7c02d886c0: 0x0000007c95de6754 0x0000007c95de6754

...

(gdb) p /t 0xf81f0ffe 【 str x30, [sp, #-16]!

(gdb) p /t 0xf81f0ffd 【 str x29, [sp, #-16]!

因此能符合 tombstone 信息,只有此处指令异常导致。

错误模拟

# ps -ef | grep "medias"

media 1145 1 0 01:35:04 ? 00:00:06 mediaserver64

mediacodec 1223 1 0 01:35:04 ? 00:00:01 media.swcodec oid.media.swcodec/bin/mediaswcodec

# cat /proc/1145/maps | grep "libstagefright.so"

修改机器码模拟报错场景。

# ./data/memory_get64 1145 0x7abb4ab000

pid 1145, target 0x7abb4ab000

0xd2891e28f81f0ffe

# ./data/memory_set64 1145 0x7abb4ab000 0xd2891e28 0xf81f0ffd

pid 1145, target 0x7abb4ab000, val 0xd2891e28f81f0ffd

06-27 23:10:07.311 1145 1145 F libc : Fatal signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0x7fdc95f510 in tid 1145 (mediaserver64), pid 1145 (mediaserver64)

06-27 23:10:07.579 27515 27515 F DEBUG : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

06-27 23:10:07.579 27515 27515 F DEBUG : Revision: '0'

06-27 23:10:07.579 27515 27515 F DEBUG : ABI: 'arm64'

06-27 23:10:07.579 27515 27515 F DEBUG : Process uptime: 0s

06-27 23:10:07.580 27515 27515 F DEBUG : Cmdline: mediaserver64

06-27 23:10:07.580 27515 27515 F DEBUG : pid: 1145, tid: 1145, name: mediaserver64 >>> mediaserver64 <<<

06-27 23:10:07.580 27515 27515 F DEBUG : uid: 1013

06-27 23:10:07.580 27515 27515 F DEBUG : tagged_addr_ctrl: 0000000000000001

06-27 23:10:07.580 27515 27515 F DEBUG : signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0x7fdc95f510

06-27 23:10:07.580 27515 27515 F DEBUG : x0 bf2d51af2cdcf7a8 x1 0000007abb5df510 x2 0000000000000000 x3 0000007abb4ab000

06-27 23:10:07.580 27515 27515 F DEBUG : x8 0000000000000003 x9 0000007abb489b40 x10 0000000000000002 x11 0000000000000001

06-27 23:10:07.580 27515 27515 F DEBUG : x28 0000000000000000 x29 0000007fdc95f510

06-27 23:10:07.580 27515 27515 F DEBUG : backtrace:

06-27 23:10:07.580 27515 27515 F DEBUG : #00 pc 000000000001f510 [stack]

Coredump寄存器恢复栈

通过readelf读取MINIDUMP文件,保存寄存器上下文在ELF文件的第一个节中。

Program Headers:

Type Offset VirtAddr PhysAddr

FileSiz MemSiz Flags Align

NOTE 0x000000000000fb28 0x0000000000000000 0x0000000000000000

0x00000000000032cc 0x0000000000000000 0x0

LOAD 0x0000000000013000 0x0000005ffabc7000 0x0000000000000000

0x0000000000001000 0x0000000000001000 R 0x0

上图绿色为LR和PC,将它们修改为正确的返回地址 0x0000007c95de6754

最后推荐一下我做的网站,玩Android: wanandroid.com,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!

2022Android面经,历时一个半月,斩获大厂offer

一文看懂Android签名v1、v2、v3、v4,竟然都v4了?

Android动态加载so!这一篇就够了!

点击关注我的公众号

如果你想要跟大家分享你的文章,欢迎投稿~

┏(^0^)┛明天见!返回搜狐,查看更多

责任编辑:

平台声明:该文观点仅代表作者本人,搜狐号系信息发布平台,搜狐仅提供信息存储空间服务。
阅读 ()
大家都在看
推荐阅读