函数调用栈为ABI规范的内容,不同的操作系统有不同的约定。
Arm+Linux平台下函数调用过程的研究
1. C语言代码:
int add(int a, int b, int c, int d, int e) { int f = 3; int g = 4; return (a + b + c + d + e + f + g); } int main() { int a = 0; a = add(1, 2, 3, 4, 5); return 0; }
2. C语言代码反汇编:arm-linux-objdump -D -S Test > dump
0000834c <add>: int add(int a, int b, int c, int d, int e) { 834c: e52db004 push {fp} ; (str fp, [sp, #-4]!) 8350: e28db000 add fp, sp, #0 ; 0x0 8354: e24dd01c sub sp, sp, #28 ; 0x1c 8358: e50b0010 str r0, [fp, #-16] 835c: e50b1014 str r1, [fp, #-20] 8360: e50b2018 str r2, [fp, #-24] 8364: e50b301c str r3, [fp, #-28] int f = 3; 8368: e3a03003 mov r3, #3 ; 0x3 836c: e50b300c str r3, [fp, #-12] int g = 4; 8370: e3a03004 mov r3, #4 ; 0x4 8374: e50b3008 str r3, [fp, #-8] return (a + b + c + d + e + f + g); 8378: e51b2010 ldr r2, [fp, #-16] 837c: e51b3014 ldr r3, [fp, #-20] 8380: e0822003 add r2, r2, r3 8384: e51b3018 ldr r3, [fp, #-24] 8388: e0822003 add r2, r2, r3 838c: e51b301c ldr r3, [fp, #-28] 8390: e0822003 add r2, r2, r3 8394: e59b3004 ldr r3, [fp, #4] 8398: e0822003 add r2, r2, r3 839c: e51b300c ldr r3, [fp, #-12] 83a0: e0822003 add r2, r2, r3 83a4: e51b3008 ldr r3, [fp, #-8] 83a8: e0823003 add r3, r2, r3 } 83ac: e1a00003 mov r0, r3 83b0: e28bd000 add sp, fp, #0 ; 0x0 83b4: e8bd0800 pop {fp} 83b8: e12fff1e bx lr 000083bc <main>: int main() { 83bc: e92d4800 push {fp, lr} 83c0: e28db004 add fp, sp, #4 ; 0x4 83c4: e24dd010 sub sp, sp, #16 ; 0x10 int a = 0; 83c8: e3a03000 mov r3, #0 ; 0x0 83cc: e50b3008 str r3, [fp, #-8] a = add(1, 2, 3, 4, 5); 83d0: e3a03005 mov r3, #5 ; 0x5 83d4: e58d3000 str r3, [sp] 83d8: e3a00001 mov r0, #1 ; 0x1 83dc: e3a01002 mov r1, #2 ; 0x2 83e0: e3a02003 mov r2, #3 ; 0x3 83e4: e3a03004 mov r3, #4 ; 0x4 83e8: ebffffd7 bl 834c <add> 83ec: e1a03000 mov r3, r0 83f0: e50b3008 str r3, [fp, #-8] return 0; 83f4: e3a03000 mov r3, #0 ; 0x0 }
3. 函数调用及返回过程,fp指针(当前函数栈针基地址)和sp指针(指向栈顶的指针)的变化
(1) 前四个参数由寄存器R0~R3传递,多于四个的参数用栈传递
(2) 函数调用调用函数动作:
① 将多于四个的参数压入栈,以sp指针作为基地址
② 将前四个参数写入寄存器R0~R3
③ 跳转到被调用函数地址
(3) 函数调用被调用函数动作,
① 将fp压入栈,且sp - 4 (这个fp为调用函数的栈帧基地址)
② 将sp赋值给fp,然后sp减去一个数(这一步是给被调用函数分配栈帧,减去的数值即为栈帧大小)
③ 在新的栈帧上为参数(保存于R3~R0)分配空间,为局部变量分配空间
④ 返回:返回值由R0传递,然后将fp赋值给sp,且弹出保存于栈中的调用函数栈针基地址fp
X86+Linux平台与Arm+Linux平台有类似的函数调用过程
1. C语言源代码
#include <stdio.h> int add(int a, int b, int c, int d, int e) { int f = 6; int g = 7; return (a + b + c + d + e + f + g); } int main() { int a = 0; a = add(1, 2, 3, 4, 5); return 0; }
2. C语言代码反汇编: objdump -D -S Test > dump
int add(int a, int b, int c, int d, int e) { 8048394: 55 push %ebp 8048395: 89 e5 mov %esp,%ebp 8048397: 83 ec 10 sub $0x10,%esp int f = 6; 804839a: c7 45 f8 06 00 00 00 movl $0x6,-0x8(%ebp) int g = 7; 80483a1: c7 45 fc 07 00 00 00 movl $0x7,-0x4(%ebp) return (a + b + c + d + e + f + g); 80483a8: 8b 45 0c mov 0xc(%ebp),%eax 80483ab: 8b 55 08 mov 0x8(%ebp),%edx 80483ae: 8d 04 02 lea (%edx,%eax,1),%eax 80483b1: 03 45 10 add 0x10(%ebp),%eax 80483b4: 03 45 14 add 0x14(%ebp),%eax 80483b7: 03 45 18 add 0x18(%ebp),%eax 80483ba: 03 45 f8 add -0x8(%ebp),%eax 80483bd: 03 45 fc add -0x4(%ebp),%eax } 80483c0: c9 leave 80483c1: c3 ret 080483c2 <main>: int main() { 80483c2: 55 push %ebp 80483c3: 89 e5 mov %esp,%ebp 80483c5: 83 ec 24 sub $0x24,%esp int a = 0; 80483c8: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%ebp) a = add(1, 2, 3, 4, 5); 80483cf: c7 44 24 10 05 00 00 movl $0x5,0x10(%esp) 80483d6: 00 80483d7: c7 44 24 0c 04 00 00 movl $0x4,0xc(%esp) 80483de: 00 80483df: c7 44 24 08 03 00 00 movl $0x3,0x8(%esp) 80483e6: 00 80483e7: c7 44 24 04 02 00 00 movl $0x2,0x4(%esp) 80483ee: 00 80483ef: c7 04 24 01 00 00 00 movl $0x1,(%esp) 80483f6: e8 99 ff ff ff call 8048394 <add> 80483fb: 89 45 fc mov %eax,-0x4(%ebp) return 0; 80483fe: b8 00 00 00 00 mov $0x0,%eax
3. 从反汇编代码中可以看出,X86平台和Arm平台函数调用过程类似
① X86的esp等价于Arm的sp, X86的ebp等价于Arm的fp
② X86的参数传递是直接用栈传递的,并未用CPU的寄存器传递