函数调用过程分析
函数调用过程分析
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的起始位置,当被调用函数结束任务返回调用位置时,就需要用到压入堆栈的返回地址。
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
-
这将创建空间用于保存局部变量和被保存的寄存器值*
下一条指令把单一寄存器保存到堆栈帧,操作数0x80指定寄存器d7。寄存器存储在堆栈的顶部,剩余部分必然是局部变量存储的地方。如上图右边。
函数序最后才能够堆栈复制一个值到d7,函数把第三个参数声明为寄存器变量。
4.3 堆栈中的参数次序
被调用函数使用帧指针加一个偏移量来访问参数,当参数以反序压入到堆栈时,参数列表的第一个参数便位于堆栈中这堆参数的顶部,它距离帧指针的偏移量是一个常数。
4.4 最终的堆栈帧布局
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章 运行时环境