函数调用过程及函数调用栈研究

函数调用栈为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的寄存器传递

原文地址:https://www.cnblogs.com/wulei0630/p/9446666.html