函数调用过程分析

函数调用过程分析

1. 静态变量和初始化

int static_variable = 5;
--------------------------------------------
        .data ;进入程序的数据区
        .even ;确保变量开始于内存的偶数地址
        .global _static_variable ;声明变量为全局类型
 _static_variable:
        .long 5 ;创建空间,初始化

2. 堆栈帧

一个函数分为:函数序、函数体、函数跋

函数序:执行启动工作,如:为局部变量保存堆栈中的内存

函数跋:在函数即将返回之前清理堆栈。

函数体:执行工作的地方

void f()
{
    register int i1, i2, i3, i4, i5,
                 i6, i7, i8, i9, i10;
    register char *c1, *c2, *c3, *c4, *c5,
                  *c6, *c7, *c8, *c9, *c10;
    extern int a_very_long_name_to_see_...
    double dbl;
    int func_ret_int();
    double func_ret_double();
    char *func_ret_char_ptr();
-------------------------------------------------------
        .text    ;进入程序代码段
        .global _f    ;函数的全局声明
_f:     link a6, #-88     ;创建堆栈帧,堆栈帧是堆栈中的一个区域,存储变量和其他值
        moveml 0x3cfc, sp@    ;选定寄存器中的值复制到堆栈中

0x3cfc表示寄存器d2至d7、a2到a5中的值需要被保存

局部变量声明和函数原型不会产生任何汇编代码。但局部变量声明时进行了初始化,也会出现指令用于赋值操作

3. 寄存器变量

i1 = 1; i2 = 2; i3 = 3; i4 = 4; i5 = 5;
i6 = 6; i7 = 7; i8 = 8; i9 = 9; i10 = 10;
c1 = (char *)110; c2 = (char *)120;
c3 = (char *)130; c4 = (char *)140;
c5 = (char *)150; c6 = (char *)160;
c7 = (char *)170; c8 = (char *)180;
c9 = (char *)190; c10 = (char *)200; 
-------------------------------------------
moveq #1,d7
moveq #2,d6
moveq #3,d5
moveq #4,d4
moveq #5,d3
moveq #6,d2
movl #7,a6@(-4)
movl #8,a6@(-8)
movl #9,a6@(-12)
movl #10,a6@(-16)
movl #110,a5
movl #120,a4
movl #130,a3
movl #140,a2
movl #150,a6@(-20)
movl #160,a6@(-24)
movl #170,a6@(-28)
movl #180,a6@(-32)
movl #190,a6@(-36)
movl #200,a6@(-40)

值1至6被放在数据寄存器,7至10放在其他地方

指针变量前4个存放在寄存器

其他变量,机器执行间接寻址和索引操作。a6称为帧指针,指向堆栈帧内部的一个引用位置

这台机器(motorola 68000),a6用作帧指针,a7是堆栈指针sp,d0和d1用于存函数返回值

4. 堆栈帧的布局

运行时堆栈保存了每个函数运行时所需要的数据,包括它的自动变量和返回地址。

4.1 传递函数参数

i2 = func_ret_int(10, i1, i10);
---------------------------------
movl a6@(-16),sp@-
movl d7,sp@-
pea  10
jbsr _func_ret_int

前3条指令把函数的参数压入堆栈中。以参数列表相反的次序逐个压入栈。

接下来跳转子程序,把返回地址压入堆栈中,并跳转到_func_ret_int的起始位置,当被调用函数结束任务返回调用位置时,就需要用到压入堆栈的返回地址。

5d3178bbba95520405

4.2 函数序

int func_ret_int(int a, int b, register int c)
{
    int d;
---------------------------------------------------
        .global  _func_ret_int
_func_ret_int:
        link    a6,#-8
        moveml  #0x80,sp@
        movl    a6@(16),d7

link指令分成几个步骤:

  • a6的内容被压入堆栈中

  • 堆栈指针的当前值被复制到a6

  • link指令从堆栈指针中减去8

  • 这将创建空间用于保存局部变量和被保存的寄存器值*

    5d317bbfca55072652

下一条指令把单一寄存器保存到堆栈帧,操作数0x80指定寄存器d7。寄存器存储在堆栈的顶部,剩余部分必然是局部变量存储的地方。如上图右边。

函数序最后才能够堆栈复制一个值到d7,函数把第三个参数声明为寄存器变量。

4.3 堆栈中的参数次序

被调用函数使用帧指针加一个偏移量来访问参数,当参数以反序压入到堆栈时,参数列表的第一个参数便位于堆栈中这堆参数的顶部,它距离帧指针的偏移量是一个常数。

4.4 最终的堆栈帧布局

5d317e8fc517c97106

    d = b - 6;
    return a + b + c;
--------------------------------
movl    a6@(12),d0
subl    #6,d0
movl    d0,a6@(-4)
movl    a6@(8),d0
movl    a6@(12),d0
addl    d7,d0
moveml  a6@(-8),#0x80
unlk    a6
rts    

4.5 函数跋

  • mveml恢复以前被保存的寄存器值

  • unkl(unlink)把a6的值复制给堆栈指针并把从堆栈中弹出的a6旧值装入a6中。清除堆栈帧中返回地址以上的那部分内容

  • rts指令通过把返回地址从堆栈中弹出到程序计数器,从而从该函数返回。

现在,执行流从调用程序的地点继续。堆栈尚未完全清理

i2 = func_ret_int(10, i1, i10);
------------------------------------
lea    sp@(12),sp
movl    d0,d6

第1条指令把参数从堆栈中弹出,此时堆栈的状态就和调用前状态完全一样了。

4.6 返回值

函数跋没有使用d0,因此依然保存函数的返回值。第二条指令把d0复制给d6,后者存放变量i2的存放位置。

dbl = func_ret_double();
c1 = func_ret_char_ptr();
-------------------------------------
jbsr    _func_ret_double
movl    d0,a6@(-48)
movl    d1,a6@(-44)

pea    a5@
jbsr    _func_ret_char_ptr
addqw   #4,sp
movl    d0,a5

第一个函数double是8字节,无法放入一个寄存器中,因此返回值需要d0和d1两个寄存器。

最后那个函数调用说明了指针变量时如何从函数中返回的:也是通过d0进行传递的。

参考:C和指针-第18章 运行时环境

posted @ 2019-07-19 16:53  LOXO  阅读(2431)  评论(0编辑  收藏  举报