如果希望汇编语言函数和C以及C++程序一起工作,就必须显式地遵守C样式的函数格式。
也就是说所有输入变量都必须从堆栈中读取,并且大多数输出值都返回到EAX寄存器中.
在汇编函数代码中,C样式函数对于可以修改哪些寄存器和函数必须保留哪些寄存器有着特定的规则。如果必须保留的寄存器在函数中被修改了,那么必须恢复寄存器的原始值,否则在执行返回发出调用的C程序时也许会出现不可预料的结果。在函数中可以安全地使用MMX和SSE寄存器,但是使用通用寄存器和FPU寄存器时必须谨慎。下表列出寄存器在函数中的状态。
如下表所示,被调用的函数必须保留EBX 、EDI 、ESI 、EBP和ESP寄存器。这就要求在执行
函数代码之前把寄存器的值压入堆栈,井且在函数准备返回调用程序时把它们弹出堆栈。这通常是按照标准的开头和结尾格式完成的。
寄存器 | 状态 |
---|---|
EAX | 用于保存输出值,但是可能在函数返回之前被修改 |
EBX | 用于指向全局偏移表,必须保留 |
ECX | 在函数中可用 |
EDX | 在函数中可用 |
EBP | C程序使用它作为堆栈基址指针,必须保留 |
ESP | 在函数中用于指向新的堆栈位置,必须保留 |
EDI | C程序使用它作为局部寄存器,必须保留 |
ESI | C程序使用它作为局部寄存器,必须保留 |
ST(0) | 用于保存浮点输出值,但是可能在函数返回之前被修改 |
ST(l) – ST(7) | 在函数中可用 |
C 函数调用的汇编语言函数的基本模板如下:
.section.text
.type func, @function
func:
pushl %ebp
movl %esp, %ebp
subl $12, %esp
pushl %edi
pushl %esi
pushl %ebx
<function code>
popl %ebx
popl %esi
popl %edi
movl %ebp, %esp
popl %ebp
ret
这个代码模板可以用于C或者C++函数使用的所有汇编语言函数。当然,如果特定的函数不改变EBX 、ESI或者EDI寄存器,可以省略相关的PUSH和POP指令。
注意开头中包含的SUBL指令,它用于为函数中的局部变量保留堆栈空间。这条指令在堆栈中保留12个字节的内存空间。这段空间可以用于保存3个4字节的数据值。如果局部变量需要更多空间,就必须从ESP的值减去空间的长度。在函数中,相对于EBP寄存器引用局部变量。例如,如果第一个局部变量为4字节值,那么它的位置就是-4 (%ebp)。用-8 (%ebp) 引用第二个变量,用-12 (%ebp) 引用第三个变量。
但是, C程序调用的汇编语言函数也许还声明它们自己的.data和.bss段用于存储数据。在编译时,这些内存区域将和C程序的内存需求结合在一起。指针可以传递回调用程序以便访问存储
在这些内存位置中的任何数据。
字符串返回值
处理返回字符串值的函数要困难一些。和返回整数值到EAX寄存器中的函数不同,这种函数不能把整个数据字符串返回到EAX寄存器中(当然,除非字符串的长度是4个字符)。
替换的做法是,返回字符串的函数返回指向字符串存储位置的指针。调用这个函数的C或者C++程序必须使用指针变量保存返回值。然后可以通过指针值访问字符串。
不幸的是,默认情况下, C程序假设函数的返回值是整数值。必须通知编译器这个函数将返回字符串的指针。通过创建函数调用的原型(prototype) 来完成这个任务。
原型在使用函数之前定义函数格式,以便C编译器知道如何处理函数调用。原型定义函数需要的输入值的数据类型,还有返回值的数据类型。不定义特定的值,只定义需要的数据类型。
原型的例子如下:
char *functionl(int, int);
浮点返回值
浮点返回值是特殊的情况。整数和字符串返回值都使用EAX寄存器把值从汇编语言函数返回到发出调用的C程序。如果希望返回浮点值,情况稍微有些区别。
C样式的函数不使用EAX寄存器,而是使用ST(O)寄存器在函数之间交换浮点值。函数把返回值存放到FPU堆栈中,然后调用程序负责把返回值弹出堆栈并且把它赋值给变量。
因为把浮点值存放到FPU堆栈中时,总会把浮点值转换为扩展双精度浮点值,所以原始值是什么浮点数据类型,以及C程序使用什么浮点数据类型包含结果值不重要。由FPU完成适当的转换。
C 和C++程序使用两种数据类型表示浮点值:
- float: 表示单精度浮点值
- double: 表示双精度浮点值
以上每一种类型都可以用于从FPU堆栈获得结果值。汇编语言函数使用什么精度把值存放到FPU 堆栈中不重要。
在C程序中使用返回浮点值的函数时,必须定义函数原型。和字符串返回值的原型相同的原则在这里也适用:
float functionl (float, float, int);
如果C变量必须使用双精度浮点值,就必须使用返回类型double:
double functionl(double, int);
在C++程序中使用汇编语言函数的规则几乎和在C程序中使用它们的规则相同。只有一处区别,但是这一区别是重要的。
默认情况下, C++程序假设在C++程序中使用的所有函数都使用C++样式的命名和调用约定。但是,若程序中使用的汇编语言函数使用C语言的调用约定。必须通知编译器使用的哪些函数是C 函数。这是通过extern语句完成的。
extern 语句用于定义使用C调用约定的函数,它使用下面的格式.
extern "C"
{
/*code*/
}
每个用到的汇编语言函数原型都必须放饺在extern语句内。这确保编译器在访问齿数时使用C调用约定,井且不会破坏函数名称。
上述笔记内容学习自 AT&T Professional Assembly Language — Chapter XIV
到这里 《AT&T Professional Assembly Language》 学习笔记就整理完毕。了解程序的底层运行原理,让我获益良多,也让我学会了从汇编角度定位程序问题的习惯。 最后三章 优化例程,文件使用和IA-32平台高级功能并没有记录详细的笔记,感兴趣的读者可以自行阅读此部分(主要是一些使用例程以及IA-32特殊指令集功能介绍)。