void main(int argc, char *argv[])
{
8380: e92d4800 push {fp, lr}
8384: e28db004 add fp, sp, #4
8388: e24dd008 sub sp, sp, #8
838c: e50b0008 str r0, [fp, #-8]
8390: e50b100c str r1, [fp, #-12]
printf("hello world ! n");
8394: e59f3028 ldr r3, [pc, #40]; 83c4
8398: e08f3003 add r3, pc, r3
839c: e1a00003 mov r0, r3
83a0: ebffffc0 bl 82a8
sleep(60);
83a4: e3a0003c mov r0, #60; 0x3c
83a8: ebffffc1 bl 82b4
func_test("abcdefghigklmnzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz");
83ac: e59f3014 ldr r3, [pc, #20]; 83c8
83b0: e08f3003 add r3, pc, r3
83b4: e1a00003 mov r0, r3
83b8: ebffffe4 bl 8350
}
83bc: e24bd004 sub sp, fp, #4
83c0: e8bd8800 pop {fp, pc}
83c4: 00000044 .word 0x00000044
83c8: 0000003c .word 0x0000003c
为了方便分析,把这一段摘出来:
static int func_test(char *buf)
{
// 指令地址 二进制指令反汇编得到的ARM 汇编指令
8350:e92d4800 push{fp, lr}
// 两条指令地址相差为4, 所以是32bits 指令,也就是 ARM 指令,Thumb 指令为16 bits 将 fp(r11) 和 lr(r14) 压入栈中,为啥呢? ARM core registers 核心寄存器就那么些,fp, lr 每种工作模式下面都只有一个,但是函数调用套函数调用,以至于嵌套调用很深,那怎么办,
只能将 fp, lr 压入到栈中。注意的是,lr 寄存器的值在什么时候更新呢? 像执行 bl 这样的跳转链接指令的时候, ARM 处理器会将跳转指令之前的那条指令的地址放入到lr 中。这点理解清楚很重要,当压入栈的lr 被破坏掉之后,就可以依靠 lr 寄存器中的值尝试恢复 callstack。同时,需要记住 ARM 的核心寄存器都是 32 bits , fp, lr 当然也不例外。
说完了 ARM 寄存器,再说下 栈, 栈是一块内存区域( RAM/ Memory area)。ARM 处理器有一个寄存器(SP)专门存放栈指针,换句话说 sp 寄存器的值就是最后入栈的数据的内存地址。
言归正传, push ARM 汇编指令,执行压栈操作,将 fp, lr 存入到栈中,sp 会自动更新。
Memory
8354:e28db004 add fp, sp, #4
// 将 sp + 4 (#4 表示立即数),得到的结果存入到fp 中,为什么 fp 是这么算出来的呢?
首先, fp 指向于栈顶。 栈是从高地址向低地址增长,因此,在执行完 fp, lr 的压栈操作之后, sp 的值比之前小了 4 (2个 4 bytes 寄存器占用 8 bytes 的内存空间)。
8358:e24dd018 sub sp, sp, #24
// 将 sp 的值减去 24, 并将结果存入到 sp 中。这个是干什么用? 预留内存空间用于保存函数传入的参数,以及函数内部定义的数组。
835c:e50b0018 str r0, [fp, #-24]
// 将r0 寄存器中的值存入到 fp 偏移 -24 在内存中地址。 其中r0 保存着main() 函数调用func_test() 函数时传入的 字符串的地址。指针类型size 为 4bytes.?? 相比于下面为数组进行的内存4 bytes 对齐,这里给 char * 的指针预留了 24 - 16 = 8 bytes
的内存空间不是很明白。哪位大神看到了知道答案的话还请赐教!!!
char buffer[10];
strcpy(buffer, buf);
8360:e24b3010 subr3, fp, #16
// 把 fp 减去 16,得到的结果存入到 r3 中。
这里定义的数组为 char buffer[10]; 申请了10 bytes 的连续内存,为什么实际分配的是 16-4 = 12 bytes 呢? 估计是内存对齐,4 字节对齐。测试了一下, 改成 char buffer[12]; objdump 出来的汇编代码是完全一样的。
8364:e1a00003 mov r0, r3
// 将r3 中的值传送到 r0 中
8368:e51b1018 ldrr1, [fp, #-24]
// 将fp 偏移 -24 的值在内存memory 地址中存放的内容放入到r1 中,这个其实就是 main() 调用 func_test() 函数时传入的参数,字符串指针。这两步完成将调用 strcpy() 函数的参数分别放入r0, r1 中。
836c:ebffffca bl829c
return 0;
8370:e3a03000 mov r3, #0
}
8374:e1a00003 mov r0, r3
// 将返回值 写到 r0 中
8378:e24bd004 subsp, fp, #4
// 有 fp 的好处就是算上个函数的sp 特别方便,都不需要关心在这个“子函数” func_test() 中到底用了多少栈空间,每个C 函数都要开栈,在这个函数执行完毕返回的时候,这个函数的栈空间不再有意义。
837c:e8bd8800 pop{fp, pc}
// 将函数调用开始时,上个函数的 fp , lr 出栈,存入到 fp 和 pc 寄存器。
3. Android 执行分析
将上面写的测试代码放到 Android 手机上运行,我这里用的是 milestone (umts_sholes)。
$ adb push main /data/main && adb shell
#cd /data
#./main
在程序运行过程中,再打开一个adb shell 看下这个进程的map
$ adb shell
# ps |grep main
root 2225 2223 544 236 c007acb4 afd0c03c S ./main
# cat /proc/2225/maps
00008000-00009000 r-xp 00000000 1f:08 522 /data/main
00009000-0000a000 rw-p 00000000 1f:08 522 /data/main
0000a000-0000c000 rw-p 00000000 00:00 0 [heap]
40000000-4000b000 r--s 00000000 00:0b 233 /dev/__properties__ (deleted)
4000b000-4000c000 r--p 00000000 00:00 0
afd00000-afd40000 r-xp 00000000 1f:06 782 /system/lib/libc.so
afd40000-afd43000 rw-p 00040000 1f:06 782 /system/lib/libc.so
afd43000-afd4e000 rw-p 00000000 00:00 0
b0001000-b0009000 r-xp 00001000 1f:06 470 /system/bin/linker
b0009000-b000a000 rw-p 00009000 1f:06 470 /system/bin/linker
b000a000-b0015000 rw-p 00000000 00:00 0
be926000-be93b000 rw-p 00000000 00:00 0 [stack]
#
sleep 60s 之后程序调用func_test() 会出现 android native crash, 将 生成的tombstones log文件adb pull 出来分析一下
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'MOTO_RTEU/umts_sholes/umts_sholes/sholes:2.2.1/SHOLS_U2_05.26.3/296482885:user/release-keys'
pid: 2225, tid: 2225 >>> ./main <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 7a7a7a78
r0 00000000 r1 00008430 r2 feff0101 r3 00000000
r4 00008380 r5 be93ac84 r6 00000001 r7 be93ac8c
r8 00000000 r9 00000000 10 00000000 fp 7a7a6e6d
ip be93ac67 sp be93ac40 lr 00008370 pc 7a7a7a78 cpsr 400f0010
d0 6472656767756265 d1 000000000000006c
d2 0000000000000064 d3 0000000000000020
d4 0000000000000000 d5 0000000000000000
d6 0000000000000000 d7 0000000000000000
d8 0000000000000000 d9 0000000000000000
d10 0000000000000000 d11 0000000000000000
d12 0000000000000000 d13 0000000000000000
d14 0000000000000000 d15 0000000000000000
d16 0000000000000000 d17 0000000000000000
d18 0000000000000000 d19 0000000000000000
d20 0000000000000000 d21 0000000000000000
d22 0000000000000000 d23 0000000000000000
d24 0000000000000000 d25 0000000000000000
d26 0000000000000000 d27 0000000000000000
d28 0000000000000000 d29 0000000000000000
d30 0000000000000000 d31 0000000000000000
scr 00000000
#00 pc 7a7a7a78
#01 lr 00008370 /data/main
code around pc:
7a7a7a58 ffffffff ffffffff ffffffff ffffffff
7a7a7a68 ffffffff ffffffff ffffffff ffffffff
7a7a7a78 ffffffff ffffffff ffffffff ffffffff
7a7a7a88 ffffffff ffffffff ffffffff ffffffff
7a7a7a98 ffffffff ffffffff ffffffff ffffffff
code around lr:
00008350 e92d4800 e28db004 e24dd018 e50b0018
00008360 e24b3010 e1a00003 e51b1018 ebffffca
00008370 e3a03000 e1a00003 e24bd004 e8bd8800
00008380 e92d4800 e28db004 e24dd008 e50b0008
00008390 e50b100c e59f3028 e08f3003 e1a00003
stack:
be93ac00 00000000
be93ac04 00000000
be93ac08 be93ac4c
be93ac0c afd19aad /system/lib/libc.so
be93ac10 4000b00c
be93ac14 000083e4 /data/main
be93ac18 00008380 /data/main
be93ac1c be93ac84
be93ac20 00000001
be93ac24 000083f4 /data/main
be93ac28 00008380 /data/main
be93ac2c 64636261
be93ac30 68676665
be93ac34 6c6b6769
be93ac38 df002777
be93ac3c e3a070ad
#00 be93ac40 7a7a7a7a
be93ac44 7a7a7a7a
be93ac48 7a7a7a7a
be93ac4c 7a7a7a7a
be93ac50 7a7a7a7a
be93ac54 7a7a7a7a
be93ac58 7a7a7a7a
be93ac5c 7a7a7a7a
be93ac60 7a7a7a7a
be93ac64 00007a7a
be93ac68 00009ef4
be93ac6c 00009ee4
be93ac70 00009edc
be93ac74 00009eec
be93ac78 00000000
be93ac7c ffffffe0
be93ac80 00000001
be93ac84 be93ad78
结合 objdump 出来的ARM 汇编代码地址, pc ,lr ,sp 以及 lr 地址附近的指令和上面的分析一致。
原作者:windtakers