C语言在被编译器编译的过程中,会有生成汇编代码的过程。故而用C语言写一段程序的内部机制跟用汇编代码写一段程序的机制时一样的。比如在子函数中,子函数的返回值存在寄存器eax中,那么对应的汇编代码就是用eax保存子函数的输出值。即在C中已经包含了这个机制,但汇编代码还需要用代码来实现这个用eax保存子函数返回值的机制。其实汇编程序拥有多种方式保存子函数的返回值(全局变量,某个寄存器),只是在C中调用汇编函数时,要符合C原有的种种机制,从而在汇编代码中,用eax寄存器来保存函数的32位返回值。
也就是说,要用汇编代码写一个函数,能够供C程序调用,那么这段汇编代码就要符合C语言程序的机制。比如将要返回的值放在eax寄存器中。在C程序中就可以按照C函数的调用方式调用此汇编函数,汇编函数在eax中存的值将自动被系统当成返回值。
在C程序中调用子函数时,有的寄存器有特殊的作用。C调用子函数所征用的寄存器有:eax,st(0),ebx,ebp,esp,edi,esi。
保存函数输出变量,存函数返回值的寄存器:eax,st(0)。
eax保存函数的输出值,但若子函数的返回值为32位整型时,eax的值为函数返回值,为64位时用edx:eax保存低。
st(0)用来保存函数的的浮点型输出值,但若子函数返回值为浮点型,st(0)的值为函数返回值。
调用子函数前后寄存器值必须不变的有:ebx,ebp,esp,edi,esi。
ebx被用来保存全局偏移表的地址值。[啥是全局偏移表]
ebp被用来指向父函数中的栈顶。
esp被用来指向在子函数中栈的新栈顶。
edi及esi被用作局部寄存器使用。
这些寄存器在子函数调用后都不得改变其值。就是说如果在子函数中要用到这些寄存器,那么一定要对这些寄存器原有的值进行备份,使用完这些寄存器后再将备份值还原到各寄存器中。
在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程序中编写汇编函数的固定结构。如果没有用到某个寄存器,则响应的push和pop的代码就可以省略掉。采取这样的备份,就保证了C语言支持嵌套调用:子函数返回前各寄存器都得到了原来在父函数中的值,从未经改变。
可在汇编函数中随意使用的寄存器:ecx,edx,st(1) ~~ st(7)。
C Note Over。