32位汇编语言学习笔记(13)--函数的调用



cdeclc语言的函数默认调用约定,这种调用约定要求函数参数的压栈顺序是从右向左。

假设函数是 int func(int arg1, int arg2, int arg3),使用c的默认调用约定,那么参数3先压入栈,参数1最后压入栈,栈的生长方向是向低地址方向生长,所以参数3的地址最大。

函数的调用栈如下图所示:

32位汇编语言学习笔记(13)--函数的调用_第1张图片

esp是栈指针,总是指向栈顶,ebp是栈帧指针,指向为函数分配的栈帧(stack frame)的起始位置。


函数调用涉及的指令如下:

call指令

call指令用于函数调用,相当于如下两条指令:

push %eipeip保存call指令的下一条指令,也就是函数的返回地址)

jmp被调用函数的起始地址。

call Label:直接跳转到函数地址

call *Operand:间接跳转到函数地址


leave指令

leave指令用于在函数返回前清理调用栈,相当于如下两条指令:

mov %ebp %esp

pop %ebp


ret指令

ret指令用于从当前的函数调用栈返回,相当于如下指令:

pop %eip //把函数的返回地址从栈中弹出,放入eip

然后处理器根据eip的值,执行函数返回地址指向的那条指令。

 

IA32寄存器使用惯例:

eax,ecxedx是调用者保存寄存器,被调用函数可以修改这些寄存器的值。

ebx,esiedi是被调用者保存寄存器,被调用函数不能破坏寄存器的值,如果要使用,先要保存原始值,在函数返回前恢复回去。

 

示例:

 

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);

   int diff = arg1 - arg2;

 

   return sum * diff;

}

 

gcc -O1 -c -m32 swapadd.c

 

objdump -d swapadd.o

 

swapadd.o:    file format elf32-i386

 

Disassembly of section .text:

 

00000000 <swap_add>:

  0:  55                     push  %ebp //把旧的ebp压人栈

  1:  89 e5                  mov   %esp,%ebp//把当前栈指针值设置为ebp

  3:  53                     push  %ebx//保存ebx

  4:  8b 55 08               mov   0x8(%ebp),%edx//edx = &arg1

  7:  8b 4d 0c               mov   0xc(%ebp),%ecx // ecx = &arg2

  a:  8b 1a                  mov   (%edx),%ebx //ebx=arg1

  c:  8b 01                  mov   (%ecx),%eax //eax=arg2

  e:  89 02                  mov   %eax,(%edx) //arg1=旧的arg2

 10:  89 19                  mov   %ebx,(%ecx) //arg2=旧的arg1

 12:  01 d8                  add   %ebx,%eax //eax = arg1+arg2

 14:  5b                     pop   %ebx //恢复ebx

 15:  5d                     pop   %ebp //恢复ebp

 16:  c3                     ret   

 

00000017 <caller>:

 17:  55                     push  %ebp //把旧的ebp压入堆栈

 18:  89 e5                  mov   %esp,%ebp //把当前栈指针值设置为ebp

 1a:  83 ec 18               sub   $0x18,%esp //esp=esp-0x18=esp-24

 1d:  c7 45 fc 16 02 00 00   movl  $0x216,0xfffffffc(%ebp) //ebp-4的地址装入534

 24:  c7 45 f8 21 04 00 00   movl  $0x421,0xfffffff8(%ebp)ebp-8的地址装入1057

 2b:  8d 45 f8               lea   0xfffffff8(%ebp),%eax//eax=ebp-8

 2e:  89 44 24 04            mov   %eax,0x4(%esp)//arg2的地址

 32:  8d 45 fc               lea   0xfffffffc(%ebp),%eax//eax=ebp-4

 35:  89 04 24               mov   %eax,(%esp)//arg1的地址

 38:  e8 fc ff ff ff         call  39 <caller+0x22> //调用swap_add函数

 3d:  8b 55 fc               mov   0xfffffffc(%ebp),%edx//edx=arg1

 40:  2b 55 f8               sub   0xfffffff8(%ebp),%edx//edx = arg1 –arg2

 43:  0f af c2               imul  %edx,%eax //eax = (arg1-arg2)*sum

 46:  c9                     leave 

 47:  c3                     ret  

 

caller函数调用swap_add函数执行pop   %ebx指令前的栈帧如下图所示:

 32位汇编语言学习笔记(13)--函数的调用_第2张图片

gcc 在分配栈空间时有 8 个字节未使用,存在浪费,这是为了保证每个函数使用的栈空间都是 16 字节的整数倍, 24 个字节加上保存 ebp 4 个字节再加上保存函数返回地址的 4 个字节是 32 个字节。这样做的好处是保证访问数据的严格对齐。

你可能感兴趣的:(32位汇编语言学习笔记(13)--函数的调用)