为了提高代码的执行效率,有时程序中需要使用汇编语言来编制源代码。这就涉及在两种语言的相互调用问题,而且linux 使用的是 AT&T 的汇编语言格式,与 INTEL 汇编的有所区别,详细可以参考相关书籍。
在汇编应用程序中调用 C 函数主要涉及汇编程序如何向 C 函数传递参数以及相关寄存器的保存,我们先看一个将C 程序转换编译成汇编程序的代码,看看汇编程序调用函数时所做的处理:
/* 交换 a 和 b 的值 */
void swap(int *a, int *b, int d, int e, int f, int g, int h, int m, int n)
{
int c;
c = *a; *a = *b; *b = c;
}
int main(void)
{
int a, b;
a = 16; b = 32;
swap(&a, &b, 0, 0, 0, 0, 0, 0, 0);
return (a - b);
}
将文件保存为 swap.c,这里将 swap.c 文件中加入了多余的几个参数,来说明汇编程序的传递参数机制。
使用命令 gcc -Wall -S -o swap.s swap.c 生成该 C 语言程序的汇编程序 swap.s
swap:
pushl %ebp
movl %esp, %ebp
subl $16, %esp
movl 8(%ebp), %eax
movl (%eax), %eax
movl %eax, -4(%ebp)
movl 12(%ebp), %eax
movl (%eax), %edx
movl 8(%ebp), %eax
movl %edx, (%eax)
movl 12(%ebp), %edx
movl -4(%ebp), %eax
movl %eax, (%edx)
leave
ret
main:
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ecx
subl $52, %esp
movl $16, -8(%ebp)
movl $32, -12(%ebp)
movl $0, 32(%esp)
movl $0, 28(%esp)
movl $0, 24(%esp)
movl $0, 20(%esp)
movl $0, 16(%esp)
movl $0, 12(%esp)
movl $0, 8(%esp)
leal -12(%ebp), %eax
movl %eax, 4(%esp)
leal -8(%ebp), %eax
movl %eax, (%esp)
call swap
movl -8(%ebp), %edx
movl -12(%ebp), %eax
movl %edx, %ecx
subl %eax, %ecx
movl %ecx, %eax
addl $52, %esp
popl %ecx
popl %ebp
leal -4(%ecx), %esp
ret
在 swap 中可以看出汇编程序从 main 的栈中取出参数 a 和 b,然后进行交换,并返回;而在 main 程序中,调用 swap 之前为它的参数分配了 52 字节的空间,显然我们这里只是使用了9个参数需要的空间为 4*9=36 字节的空间就够使用,那么额外的 52-36=16 字节用作什么用途呢?另外在 main 的开始处的 andl 指令我觉得应该是使栈空间16字节对齐的操作;当将 C 程序中 swap 函数的参数改变时,可以看出其对应的汇编程序中也总是有16字节的额外空间,留作疑问。
C 语言中嵌入汇编的操作,只需要遵循一定的格式,然后使用汇编指令就可以,如下面代码
char* strcpy(char *dest, const char *src)
{
__asm__
("cld/n"
"1:/tloadsb/n/t"
"stosb/n/t"
"testb %%al, %%al/n/t"
"jne 1b"
::"S"(src),"D"(dest)//:"si","di","ax"/* 可能是应用程序中不用指出变化的寄存器 */
/* 否则将 gcc 报错: can't find a register in class ‘SIREG’ while reloading ‘asm’ */
);
return dest;
}
上面的/n/t转换是为了汇编代码整齐,没有特殊的含义。可以看出,嵌入汇编时需要使用 __asm__(); 嵌入汇编代码,也可以使用 asm(); 但是为了向前兼容最好使用前面的格式。
而在C程序调用汇编函数过程中,只要遵循参数的反向压入栈,而且汇编程序按照依次取出参数就可以了,从上面的几个例子中不难写出一个C函数调用纯汇编过程的源程序,不再示例。