操作系统之汇编语言(2)

现在,让我们看看程序调用过程中汇编操作,一个过程调用包括将数据(以过程参数和返回值的形式), 和控制从代码的一部分传递到令一部分。另外,它还必须在进入是为过程的局部变量分配空间,并在退出时释放这些空间。

栈帧结构
IA32程序用程序栈来支持过程调用。机器用栈来传递过程参数,存储返回信息,保存寄存器用于以后恢复,以及本地存储,为整个过程分配的那部分栈称为栈帧(stack frame)。最顶端的栈帧以两个指针界定,寄存器%ebp为帧指针,而寄存器%esp为栈指针。当程序执行时,栈指针是可以移动的,因此绝大多数信息的访问都是相对于帧指针的。

这里写图片描述

转移控制
call指令:其效果是将返回值地址入栈,并跳转到被调用过程的起始处。返回地址是在程序中紧跟在call后面的那条指令的地址。这样当被调用函数返回时,执行会从此处继续。ret指令从栈中弹出地址,并跳转到这个位置。例如下面代码:

int accum = 0;  
int sum(int x,int y);  
int main()  
{  
    return sum(1.3);  
}  
int sum(int x,int y)  
{  
    int t = x + y;  
    accum += t;  
    return t;  
}  
经过反汇编后,节选处call部分的代码如下图所示:

这里写图片描述

在main函数中我们可以看到,在main函数中,地址为0x080483dc的call指令调用函数sum,指明了栈指针%esp和程序计数器%eip的值。我们可以看到在main函数中,地址0x080483dc的下一个执行地址是是0x080483e1,这一点很重要。call指令的效果是将返回地址0x080483e1压入栈中,并跳到函数sum的第一条指令,地址为0x8048394。函数sum继续执行,直到遇到地址为0x080483a4的ret指令。这条指令从栈中弹出值0x080483e1,然后跳转到这个地址,就在调用sum的call函数之后,继续main函数的执行。

寄存器使用示例

int swap_add(int* xp,int* yp);  
int caller()  
{  
    int arg1 = 534;  
    int arg2 = 1057;  
    int sum = swap_add(&arg1 , &arg2);  
    int diff = arg1 - arg2;  

    retur sum * diff;  
}  
int swap_add(int* xp,int* yp)  
{  
    int x = * xp;  
    int y = * yp;  
    *xp = y;  
    *yp = x;  
    return x + y;  
}  

我们先来看看caller函数调用swap_add正在运行时的栈帧结构。有些指令访问的栈位置是相对于栈指针%esp的,而另一些的访问的栈位置是相对于基地址指针%ebp的。

这里写图片描述

caller:
      push1 %ebp
      movl %esp,%ebp
      sub1 $24, %esp
      movl $534,-4(%ebp)
      movl $1057,-8(%ebp)
      leal -8(%ebp),%eax   //compute &arg2
      movl %eax,4(%esp)
      leal -4(%ebp),%eax   //compute &arg1
      movl %eax,(%esp)
      call swap_add

我们可以看到,在调用swap_add之前,我们用push1指令把%ebp的数据压入栈,然后为这个栈分配24字节。caller的帧栈包括局部变量arg1和arg2的存储,其位置相对于帧指针是-4和-8。这些变量必须存在栈中,因为我们必须为他们生成地址。
在这里我们可以看到分配给栈帧的24个字节中,8个用于局部变量,8个用于向swap_add传递参数,还有8个未使用。因为GCC坚持一个x86编程指导方针,也就是一个函数使用的所有栈空间必须是16字节的整数倍,包括保存%ebp值的4个字节和返回值的4个字节,caller一共使用了32个字节,采用这个规则是为了保证访问数据的严格对齐。

发布了82 篇原创文章 · 获赞 11 · 访问量 6万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章