程序机器级表示

本文详细介绍了IA32架构中的寄存器使用习惯、栈帧结构及过程调用机制。通过具体实例,展示了如何利用寄存器进行参数传递,以及在函数调用过程中栈帧的变化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

0.IA32寄存器

    IA32包含8个存储32位值的整数通用寄存器。其中大多数情况下前六个可以看成通用寄存器。最初的8086寄存器是16位的,因此不以%e开头的16位寄存器主要用于实地址模式。
    寄存器%eax,%edx和%ecx为划分为调用者保存寄存器,当过程P调用Q时,Q可以覆盖这些寄存器而不会破坏P所需要的数据;而寄存器%ebx,%edi和%esi为划分为被调用者保存寄存器,即使用前,被调用者Q需要首先保存该寄存器值(push压栈 ),在覆盖该寄存器时首先需要压栈保存,并在返回时恢复他们。


文章出处:http://www.cnblogs.com/dandingyy/archive/2013/01/04/2843030.html 

    一个函数调用包括将数据(包括参数和返回值)和控制从代码一部分传到另一部分。还包括对函数内局部变量分配空间,并在退出时释放空间。  其中,转移控制到过程 和 从过程转移出控制——使用指令;局部变量的分配和释放通过 程序栈 来实现。

1.栈帧结构

   栈由高地址向低地址方向增长。对单个过程分配的栈称为 栈帧。以两个指针来界定:帧指针%ebp和栈指针%esp.栈指针是不断变化的,所以大多数信息基于帧指针%ebp.(注意在我的电脑上,帧指针是%esp,所以在汇编时总是由movl 8(%esp) %eax来得到参数)。

image

   从上图的栈帧结构中看到,假设P调用Q。

  • P栈帧部分参数为传入Q的参数;
  • P的返回地址形成P栈帧的末尾;
  • Q在%ebp+4+4i的位置上得到其参数;
  • 栈向低地址增长,故栈指针减小——分配空间;栈指针增大——释放空间。

2.转移控制

一个简单的sum调用函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
int  accum = 0;
 
int  sum( int  x, int  y)
{
     int  t = x + y;
     accum += t;
     return  t;
}
 
int  main()
{
     return  sum(1,3);
}

该函数使用gcc –o sum sum.c –O1得到可执行文件sum。对该可执行文件使用objdump –d sum 得到反汇编代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
080483b4 <sum>:
  80483b4:   8b 44 24 08             mov    0x8(%esp),%eax
  80483b8:   03 44 24 04             add    0x4(%esp),%eax
  80483bc:   01 05 18 a0 04 08       add    %eax,0x804a018
  80483c2:   c3                      ret   
 
080483c3 <main>:
  80483c3:   83 ec 08                sub    $0x8,%esp
  80483c6:   c7 44 24 04 03 00 00    movl   $0x3,0x4(%esp)
  80483cd:   00
  80483ce:   c7 04 24 01 00 00 00    movl   $0x1,(%esp)
  80483d5:   e8 da ff ff ff          call   80483b4 <sum>
  80483da:   83 c4 08                add    $0x8,%esp
  80483dd:   c3                      ret

【汇编的一个习惯技巧】

对下面的一个代码片段:

1
2
3
     call next
next:
     popl %eax

这是一个汇编代码的习惯用法,结果是把popl指令地址放入%eax中——将程序计数器值放入整数寄存器的唯一方法。

这不是一个过程调用,因为跳转的结果与指令顺序相同:

执行call next前,PC设为popl指令的地址(PC始终设置为下条指令地址);

执行call next后,%esp被设置为popl指令地址(因为它是调用者的返回地址);程序同时也跳转到了popl指令,(此时帧指针和栈指针是重合的,栈指针%esp只有在声明新局部变量时会增大,所以%esp还存储的是popl的地址),根据: %eax = M[R[%esp]],所以popl指令地址放入了%eax中

3.寄存器使用惯例

寄存器组是被所有过程共享的资源,但同一时刻只有一个过程激活,所以需要保证被调用者不会影响调用者在寄存器中的值。

将%eax, %edx, %ecx作为调用者保存寄存器,被调用者可以覆盖这些寄存器;

将%ebx, %ebi, %edi作为被调用者保存寄存器,需要在过程开始前pushl,在过程结束后popl到寄存器。

4.过程示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int  swap_add( int  *xp, int  *yp)
{
     int  x = *xp;
     int  y = *yp;
     *xp = y;
     *yp = x;
     return  x+y;
}
 
int  caller()
{
     int  arg1 = 534;
     int  arg2 = 1057;
     int  sum = swap_add(&arg1, &arg2);
     return  sum;
}

汇编代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
swap_add:
.LFB0:
     .cfi_startproc
     pushl   %ebx
     .cfi_def_cfa_offset 8
     .cfi_offset 3, -8
     movl    8(%esp), %ebx
     movl    12(%esp), %ecx
     movl    (%ebx), %edx
     movl    (%ecx), %eax
     movl    %eax, (%ebx)
     movl    %edx, (%ecx)
     addl    %edx, %eax
     popl    %ebx
     .cfi_def_cfa_offset 4
     .cfi_restore 3
     ret
     .cfi_endproc
caller:
.LFB1:
     .cfi_startproc
     subl    $24, %esp
     .cfi_def_cfa_offset 28
     movl    $534, 16(%esp)
     movl    $1057, 20(%esp)
     leal    20(%esp), %eax
     movl    %eax, 4(%esp)
     leal    16(%esp), %eax
     movl    %eax, (%esp)
     call    swap_add
     addl    $24, %esp
     .cfi_def_cfa_offset 4
     ret
     .cfi_endproc

对caller栈帧,分配如下:

共32个字节:

+24  (一个空闲位,在书上该位用来存放了“保存的%ebp")

+20 arg2

+16 arg1

……

+8

+4 &arg2

&arg1(%esp保存该地址)

返回地址(from ret)

    对swap_add栈帧,%esp从上面的返回地址后面的地址开始,由于首先pushl %edx, %esp变为下一个地址:

……

&arg1

返回地址(call调用会存储该地址)

Initial %ebx(%esp保存该地址,由于没有”保存的%ebp“,所以该位置就类似于书上的%ebp位置。)

所以可以从%esp+8处取得参数值,注意此处与书上有所不同,因为一直没有用到%ebp帧地址,书上使用%edp帧地址,所以在每次初始部分多了:

pushl %ebp

movl %esp %ebp  (这一步结束,就将%ebp指向了”保存的%ebp“的地址。)

在结束部分,要恢复%ebp的值:

popl %ebp

ret

练习3.32: 不论从书上还是实际试验都可以看到,前面位置的参数里%ebp越近(%ebp+4+4i为第i个参数的地址)。

练习3.33:不要犯下面三个错误:(1)注意地址相减表示由高位到低位的扩展;(2)注意做十六进制相加(每个寄存器4字节);(3)C对应12,E对应14.

5.递归过程

栈规则的关键是:每次函数调用它都会保存自己的私有信息(包括返回地址、栈指针%ebp、被调用者保存寄存器值%ebp等,如果需要还有其他局部变量)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值