AT&T 汇编-10: 使用函数(2)

自己定义的汇编函数可以使用任意方式传递参数,但是如果每个函数都以不同方式传递参数则很容易造成混乱,所以一般都遵循 c 语言对参数的处理方式。

有些通用寄存器被 c 语言用作特殊的用途,如果在函数种需要修改这些寄存器需要把它们保存起来,在退出函数时再恢复原来的值:

+--------+------------------+
| 寄存器 |       作用       |
+--------+------------------+
|   ebx  |  指向全局偏移表  |
+--------+------------------+
|   edi  |    局部寄存器    |
+--------+------------------+
|   esi  |    局部寄存器    |
+--------+------------------+
|   ebp  | 作为堆栈基址指针 |
+--------+------------------+
|   esp  |  当前进程的栈顶  |
+--------+------------------+

一般来说符合 c 语言函数的基本模板如下:

.type func, @function
func:
   pushl %ebp
   movl  %esp, %ebp
   pushl %edi
   pushl %esi
   pushl %ebx

   ...

   popl  %ebx
   popl  %esi
   popl  %edi
   movl  %ebp, %esp
   popl  %ebp
   ret

当然如果不改变 ebx,edi,esi 寄存器的值可以省略保存和恢复这些寄存器的相关指令。

当在 c 程序中使用汇编函数时,需要把各个源文件分别生成 .o 文件,再从 .o 文件生成最终的可执行文件:

汇编源文件 print.s:

# print.s

.section .rodata
msg:
   .ascii   "This is a message.\n"

.section .text
.type print, @function
.globl print
print:
   pushl %ebp
   movl  %esp, %ebp
   pushl %ebx

   movl  $4,    %eax
   movl  $1,    %ebx
   movl  $msg,  %ecx
   movl  $19,   %edx
   int   $0x80

   popl  %ebx
   movl  %ebp, %esp
   popl  %ebp
   ret

包含汇编函数 print() 原型的头文件 print.h:

#ifndef __PRINT_H__
#define __PRINT_H__

void print(void);

#endif

主函数 main.c:

#include "print.h"

int main()
{
   print();

   return 0;
}

编译方法:

as print.s -o print.o
gcc -c main.c
gcc -o a.out main.o print.o

这里和编译多个 c 源文件的步骤差不多,只是其中某些源代码是汇编代码。因为 c 语言不清楚汇编函数的原型,所以需要在头文件中声明,否则会出现 warning。

参数传递

如果函数接收多个参数,参数的入栈顺序和函数原型中的参数顺序相反,即第一个参数最后入栈,最后一个参数最先入栈,并且在函数中必须严格按照参数的入栈顺序和各自的长度读取参数,否则可能会发生错误。

汇编函数 print2.s:

# print2.s

.section .rodata
msg:
   .asciz   "arg1 = %d\targ2 = %d\n"

.section .text
.type print2, @function
.globl print2
print2:
   pushl %ebp
   movl  %esp, %ebp

   push  $0
   push  12(%ebp)
   pushl 8(%ebp)
   pushl $msg
   call  printf

   movl  %ebp, %esp
   popl  %ebp
   ret

包含函数原型的头文件 print2.h:

#ifndef __PRINT2_H__
#define __PRINT2_H__

void print2(short int a, int b);

#endif

主函数 main2.c:

#include "print2.h"

int main()
{
   print2(5, 12);

   return 0;
}

栈的变化情况如图所示(ebp 的值为执行了第 12 行后的值):

4(%ebp)      8(%ebp)                 12(%ebp)
   |             |                       |
   v             v                       v
   +-------------+-----+-----+-----+-----+-----+-----+-----+
   | return addr | 0xc | 0x0 | 0x0 | 0x0 | 0x5 | 0x0 | ... |
   +-------------+-----+-----+-----+-----+-----+-----+-----+
   ^
   |
print2()

-12(%ebp)  -8(%ebp)                -4(%ebp)                 (%ebp)    4(%ebp)
    |          |                       |                       |         |
    v          v                       v                       v         v
    +----------+-----+-----+-----+-----+-----+-----+-----+-----+---------+
    | msg addr | 0xc | 0x0 | 0x0 | 0x0 | 0x5 | 0x0 | 0x0 | 0x0 | old ebp |
    +----------+-----+-----+-----+-----+-----+-----+-----+-----+---------+
    ^          ^                       ^           ^           ^
    |          |                       |           |           |
  pushl      pushl                   push        push        pushl
  $msg      8(%ebp)                12(%ebp)       $0         %ebp

memory addresses ->

在 32 位机器上,short int 的长度是 2 字节,int 的长度是 4 字节,所以对于两个参数的处理不同。

返回值

c 语言函数从 eax 寄存器中获得整型和字符串(指针)类型的返回值。

汇编函数定义 funcs.s:

# funcs.s

.section .text

.type add5, @function
.globl add5
add5:
   pushl %ebp
   movl  %esp, %ebp

   movl  8(%ebp), %eax
   addl  $5,      %eax

   movl  %ebp, %esp
   popl  %ebp
   ret

.type straddr, @function
.globl straddr
straddr:
   pushl %ebp
   movl  %esp, %ebp

   movl  8(%ebp), %eax

   movl  %ebp, %esp
   popl  %ebp
   ret

包含函数原型的头文件 funcs.h:

#ifndef __FUNCS_H__
#define __FUNCS_H__

int add5(int n);
char* straddr(const char *str);

#endif

主函数 main3.c:

#include <stdio.h>
#include "funcs.h"

int main()
{
   int ret;
   const char *str = "hello";
   char *strret;

   ret = add5(5);
   printf("The result is: %d.\n", ret);

   ret = add5(12);
   printf("The result is: %d.\n", ret);

   printf("str addr = %p\n", str);
   strret = straddr(str);
   printf("return straddr = %p\n", strret);

   return 0;
}

函数 add5() 接收一个整型参数,返回该整型加上 5 后的值。函数把返回值放到寄存器 eax 中,c 语言从 eax 中取出数值并把值赋给主函数中接收返回值的变量。

在 32 位机器上指针大小是 4 字节,函数 straddr 只是简单地把传进来的字符串地址赋值给 eax,主程序打印返回值,看是否和传进去的值相同。

发表于 2011年7月1日
本文目前尚无任何评论.

发表评论

XHTML: 您可以使用这些标签: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>