ARM Procedure
Call Standard定义了各寄存器在函数调用过程中的作用、基础类型的长度、以及函数调用基本准则,包括栈处理、参数传递等。
本文通过实例描述arm与arm64在函数调用过程中栈帧的处理方法,理解栈帧的特点对于理解反汇编代码和定位bug有重要意义。
下面是一个样例代码,我们将研究它的调用栈:
-
int func2(int x, int y, int z)
-
{
-
int r;
-
r = x + y + z;
-
return r;
-
}
-
int func1(int i, int j)
-
{
-
int tmp = 3;
-
int k = 0;
-
k = func2(i, j, tmp);
-
return k;
-
}
-
int main()
-
{
-
int i = 1;
-
int j = 2;
-
int k = 0;
-
k = func1(i, j);
-
return 0;
-
}
由于在函数调用过程中,最后一层调用与中间调用对栈的处理略有不同,样例中采用两层调用,main -> func1 -> func2
将样例代码分别用arm和arm64
tool chain编译并反汇编。
为了便于分析汇编代码,先把arm和arm64的寄存器用途列出:
注:arm64
r0 ~ r30寄存器有两种使用模式,64位时称为x0
~ x30,32位时称为w0 ~ w30。
arm反汇编:
-
000083bc <func2>:
-
83bc: b480 push {r7} r7会被使用故入栈
-
83be: b087 sub sp, #28 栈预留局部变量空间
-
83c0: af00 add r7, sp, #0
-
83c2: 60f8 str r0, [r7, #12] x
-
83c4: 60b9 str r1, [r7, #8] y
-
83c6: 607a str r2, [r7, #4] z
-
83c8: 68fa ldr r2, [r7, #12]
-
83ca: 68bb ldr r3, [r7, #8]
-
83cc: 441a add r2, r3
-
83ce: 687b ldr r3, [r7, #4]
-
83d0: 4413 add r3, r2
-
83d2: 617b str r3, [r7, #20] r
-
83d4: 697b ldr r3, [r7, #20]
-
83d6: 4618 mov r0, r3
-
83d8: 371c adds r7, #28
-
83da: 46bd mov sp, r7
-
83dc: f85d 7b04 ldr.w r7, [sp], #4
-
83e0: 4770 bx lr
-
83e2: bf00 nop
-
-
000083e4 <func1>:
-
83e4: b580 push {r7, lr} lr入栈;r7会被使用故入栈
-
83e6: b084 sub sp, #16 栈预留局部变量空间
-
83e8: af00 add r7, sp, #0
-
83ea: 6078 str r0, [r7, #4] i
-
83ec: 6039 str r1, [r7, #0] j
-
83ee: 2303 movs r3, #3
-
83f0: 60bb str r3, [r7, #8] tmp
-
83f2: 2300 movs r3, #0
-
83f4: 60fb str r3, [r7, #12] k
-
83f6: 6878 ldr r0, [r7, #4] 第一个参数放入r0
-
83f8: 6839 ldr r1, [r7, #0] 第二个参数放入r1
-
83fa: 68ba ldr r2, [r7, #8] 第三个参数放入r2
-
83fc: f7ff ffde bl 83bc <func2>
-
8400: 60f8 str r0, [r7, #12]
-
8402: 68fb ldr r3, [r7, #12]
-
8404: 4618 mov r0, r3
-
8406: 3710 adds r7, #16
-
8408: 46bd mov sp, r7
-
840a: bd80 pop {r7, pc}
-
-
0000840c <main>:
-
840c: b580 push {r7, lr} lr入栈;r7会被使用故入栈
-
840e: b084 sub sp, #16 栈预留局部变量空间
-
8410: af00 add r7, sp, #0
-
8412: 2301 movs r3, #1
-
8414: 607b str r3, [r7, #4] int i
-
8416: 2302 movs r3, #2
-
8418: 60bb str r3, [r7, #8] int j
-
841a: 2300 movs r3, #0
-
841c: 60fb str r3, [r7, #12] int k
-
841e: 6878 ldr r0, [r7, #4] 第一个参数放入r0
-
8420: 68b9 ldr r1, [r7, #8] 第二个参数放入r1
-
8422: f7ff ffdf bl 83e4 <func1>
-
8426: 60f8 str r0, [r7, #12]
-
8428: 2300 movs r3, #0
-
842a: 4618 mov r0, r3
-
842c: 3710 adds r7, #16
-
842e: 46bd mov sp, r7
-
8430: bd80 pop {r7, pc}
-
8432: bf00 nop
arm64反汇编:
-
0000000000400540 <func2>:
-
400540: d10083ff sub sp, sp, #0x20 sp-32
-
400544: b9000fe0 str w0, [sp,#12] x
-
400548: b9000be1 str w1, [sp,#8] y
-
40054c: b90007e2 str w2, [sp,#4] z
-
400550: b9400fe1 ldr w1, [sp,#12]
-
400554: b9400be0 ldr w0, [sp,#8]
-
400558: 0b000021 add w1, w1, w0
-
40055c: b94007e0 ldr w0, [sp,#4]
-
400560: 0b000020 add w0, w1, w0
-
400564: b9001fe0 str w0, [sp,#28] r
-
400568: b9401fe0 ldr w0, [sp,#28]
-
40056c: 910083ff add sp, sp, #0x20
-
400570: d65f03c0 ret
-
-
0000000000400574 <func1>:
-
400574: a9bd7bfd stp x29, x30, [sp,#-48]! sp-48, lr fp入栈
-
400578: 910003fd mov x29, sp 新sp放入fp
-
40057c: b9001fa0 str w0, [x29,#28] i
-
400580: b9001ba1 str w1, [x29,#24] j
-
400584: 52800060 mov w0, #0x3 // #3
-
400588: b9002fa0 str w0, [x29,#44] tmp
-
40058c: b9002bbf str wzr, [x29,#40] k
-
400590: b9401fa0 ldr w0, [x29,#28] 第一个参数放入r0
-
400594: b9401ba1 ldr w1, [x29,#24] 第二个参数放入r1
-
400598: b9402fa2 ldr w2, [x29,#44] 第三个参数放入r2
-
40059c: 97ffffe9 bl 400540 <func2>
-
4005a0: b9002ba0 str w0, [x29,#40]
-
4005a4: b9402ba0 ldr w0, [x29,#40]
-
4005a8: a8c37bfd ldp x29, x30, [sp],#48
-
4005ac: d65f03c0 ret
-
-
00000000004005b0 <main>:
-
4005b0: a9be7bfd stp x29, x30, [sp,#-32]! sp-32, lr fp入栈
-
4005b4: 910003fd mov x29, sp 新sp放入fp
-
4005b8: 52800020 mov w0, #0x1 // #1
-
4005bc: b9001fa0 str w0, [x29,#28] i
-
4005c0: 52800040 mov w0, #0x2 // #2
-
4005c4: b9001ba0 str w0, [x29,#24] j
-
4005c8: b90017bf str wzr, [x29,#20] k
-
4005cc: b9401fa0 ldr w0, [x29,#28] 第一个参数放入r0
-
4005d0: b9401ba1 ldr w1, [x29,#24] 第二个参数放入r1
-
4005d4: 97ffffe8 bl 400574 <func1>
-
4005d8: b90017a0 str w0, [x29,#20]
-
4005dc: 52800000 mov w0, #0x0 // #0
-
4005e0: a8c27bfd ldp x29, x30, [sp],#32
-
4005e4: d65f03c0 ret
调用栈对比示意图
从以上对比可以看出,arm与arm64的调用栈有以下区别:
1. arm64的lr,fp放在栈顶,而arm放在栈底;
2. arm64中当前fp和sp相同,都是栈顶指针,与x86的bp和sp不同,x86的bp和sp之间是整个函数栈帧;
3. 函数返回时,arm64先将栈中的lr值放入当前lr,再ret;而arm直接将栈中的lr值放入pc;
附: 使用gcc编译选项-fomit-frame-pointer,可以使arm64不使用fp寄存器,这时栈帧稍有变化,栈不在保存fp,局部变量寻址过程也不使用该寄存器
阅读(13469) | 评论(0) | 转发(1) |