分类:
2008-03-10 12:47:47
from :欧阳光 1999.10.13
初次接触到AT&T格式的汇编代码,看着那一堆莫名其妙的怪符号,真是有点痛不欲生的感觉,只好慢慢地去啃gcc文档,在似懂非懂的状态下过了一段时间。后来又在网上找到了灵溪写的《gcc中的内嵌汇编语言》一文,读后自感大有裨益。几个月下来,接触的源代码多了以后,慢慢有了一些经验。为了使初次接触AT&T格式的汇编代码的同志不至于遭受我这样的痛苦,就整理出该文来和大家共享.如有错误之处,欢迎大家指正,共同提高.
本文主要以举例的方式对gcc中的内嵌汇编语言进行进一步的解释。
一、gcc对内嵌汇编语言的处理方式
gcc在编译内嵌汇编语言时,采取的步骤如下
再以arch/i386/kernel/apm.c中的一段代码为例,我们来比较一下它们编译前后的情况
源程序 |
编译后的汇编代码 |
__asm__ ( "pushl %%edi\n\t" "pushl %%ebp\n\t" "lcall %%cs:\n\t" "setc %%al\n\t" "addl %1,%2\n\t" "popl %%ebp\n\t" "popl %%edi\n\t" :"=a"(ea),"=b"(eb), "=c"(ec),"=d"(ed),"=S"(es) :"a"(eax_in),"b"(ebx_in),"c"(ecx_in) :"memory","cc"); |
movl eax_in,%eax |
二.对第三个冒号后面内容的解释
第三个冒号后面内容主要针对gcc优化处理,它告诉gcc在本段汇编代码中对寄存器和内存的使用情况,以免gcc在优化处理时产生错误.
源程序 | 编译后的汇编代码 | |
正常情况下 | int main() {int bar=1; bar=fun(); bar++; return bar; } |
pushl %ebp movl %esp,%ebp call fun incl %eax #显然,bar缺省使用eax寄存器 leave ret |
加了汇编后 | int main() {int bar=1; bar=fun(); asm volatile("" : : : "eax"); bar++; return bar; } |
pushl %ebp movl %esp,%ebp #建立堆栈框架 call fun #fun的返回值放入bar中,此时由于嵌入汇编 #指明改变了eax的值,为了避免冲突, #bar改为使用edx寄存器 movl %eax,%edx #APP #NO_APP incl %edx movl %edx,%eax #放入main()的返回值 leave ret |
三.对"&"限定符的解释
这是一个较常见用于输出的限定符.它告诉gcc输出操作数使用的寄存器不可再让输入操作数使用.
对于"g","r"等限定符,为了有效利用为数不多的几个通用寄存器,gcc一般会让输入操作数和输出操作数选用同一个寄存器.但如果代码没编好,会引起一些意想不到的错误:如 asm("call fun;mov ebx,%1":"=a"(foo):"r"(bar));gcc编译的结果是foo和bar同时使用eax寄存器:
movl bar,eax
#APP
call fun
movl ebx,eax
#NO_APP
movl eax,foo
本来这段代码的意图是将fun()函数的返回值放入foo变量,但半路杀出个程咬金,用ebx的值冲掉了返回值,所以这是一段错误的代码,解决的方法是加上一个给输出操作数加上一个"&"限定符:asm("call fun;mov ebx,%1":"=&a"(foo):"r"(bar));这样gcc就会让输入操作数另寻高就,不再使用eax寄存器了
四.对%quot;&"限定符的解释(老铁补充)
%:说明指令中可与下一操作数交换的那个操作数,这意味着编译可以交换这两个操作数以使得能以代价更小的方法来满足操作数约束,这常常用于真正只有两个操作数的加法指令的指令样板中,这种加法指令的结果必须存放在两个操作数之一中.