Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1736913
  • 博文数量: 206
  • 博客积分: 1450
  • 博客等级: 上尉
  • 技术积分: 2285
  • 用 户 组: 普通用户
  • 注册时间: 2008-09-11 11:38
个人简介

学习永无止境!!

文章分类

全部博文(206)

文章存档

2022年(5)

2019年(3)

2018年(8)

2017年(32)

2016年(7)

2015年(13)

2014年(44)

2013年(24)

2011年(6)

2010年(17)

2009年(46)

2008年(1)

分类: C/C++

2009-11-11 14:40:35

以举例的方式对gcc中的内嵌汇编语言进行的解释。

一、gcc对内嵌汇编语言的处理方式
   gcc在编译内嵌汇编语言时,采取的步骤如下:
   变量输入:根据限定符的内容将输入操作数放入合适的寄存器,如果限定符指定为立即数("i")或内存变量("m"),则该步被省略,如果限定符没有具体指定输入操作数的类型(如常用的"g"),gcc会视需要决定是否将该操作数输入到某个寄存器.这样每个占位符都与某个寄存器,内存变量,或立即数形成了一一对应的关系.这就是对第二个冒号后内容的解释.::"a"(foo),"i"(100),"m"(bar)表示%0对应eax寄存器,%1对应100,%2对应内存变量bar. 
   生成代码:然后根据这种一一对应的关系(还应包括输出操作符),用这些寄存器,内存变量,或立即数来取代汇编代码中的占位符(则有点像宏操作),注意,这一步骤并不检查由这种取代操作所生成的汇编代码是否合法,例如,如果有这样一条指令asm("movl %0,%1"::"m"(foo),"m"(bar));如果你用gcc -c -S选项编译该源文件,那么在生成的汇编文件中,你将会看到生成了movl foo,bar这样一条指令,这显然是错误的.这个错误在稍后的编译检查中会被发现
   变量输出:按照输出限定符的指定将寄存器的内容输出到某个内存变量中,如果输出操作数的限定符指定为内存变量("m"),则该步骤被省略.这就是对第一个冒号后内容的解释,:asm("mov %0,%1":"=m"(foo),"=a"(bar):);编译后为:
     #APP
     movl foo,eax
     #NO_APP
     movl eax,bar
   该语句虽然有点怪怪的,但它很好的体现了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
   movl ebx_in,%ebx
   movl ecx_in,%ecx
#APP GCC插入的注释,表示内嵌汇编开始
   pushl %edi 内嵌的汇编语句
   pushl %ebp
   lcall %cs:
   setc %al
   addl eb,ec
   popl %ebp
   popl %edi
#NO_APP GCC 插入的注释,表示内嵌汇编结束

   movl %eax,ea
   movl %ebx,eb
   movl %ecx,ec
   movl %edx,ed
   movl %esi,es


二、对第三个冒号后面内容的解释
   第三个冒号后面内容主要针对gcc优化处理,它告诉gcc在本段汇编代码中对寄存器和内存的使用情况,以免gcc在优化处理时产生错误。它可以是"eax","ebx","ecx"等寄存器名,表示本段汇编代码对该寄存器进行了显式操作, asm ("mov %%eax,%0",:"=r"(foo)::"eax");这样gcc在优化时会避免使用eax作临时变量,或者避免cacheeax的内存变量通过该段汇编码。
   下面的代码均用gcc-O2级优化,它显示了嵌入汇编中第三个冒号后"eax"的作用

源程序

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 
movl %eax,%edx
  #bar改为使用edx寄存器 
incl %edx 
movl %edx,%eax
  #放入main()的返回值 
ret 
   "merory"是一个常用的限定,它表示汇编代码以不可预知的方式改变了内存,这样gcc在优化时就不会让cache到寄存器的内存变量使用该寄存器通过汇编代码,否则可能会发生同步出错.有了上面的例子,这个问题就很好理解了 

三、对"&"限定符的解释
   这是一个较常见用于输出的限定符,它告诉gcc输出操作数使用的寄存器不可再让输入操作数使用。
   对于"g","r"等限定符,为了有效利用为数不多的几个通用寄存器,gcc一般会让输入操作数和输出操作数选用同一个寄存器。但如果代码没编好,会引起一些意想不到的错误,例如:
     asm("call fun;mov ebx,%1":"=a"(foo):"r"(bar));
   gcc编译的结果是foobar同时使用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寄存器了

补充说明:
   其实&对读代码根本没有意义,只有GCC或写汇编的才关心。 
   如果你非要知道&的作用,这里解释一下。
   1. GCC处理嵌入汇编时,如果两个输入操作数值相同,可能会分配到同一个寄存器,以减少寄存器的使用。 
   2. GCC把嵌入汇编看成一个整体,它并不知道是一条指令还是多条指令,通常它认为汇编指令输出结果时,输入操作数还没有改变,单条指令时,这种假设大多成立,但多条指令时可能不成立。比如定义如下:
     #define add1(a,b) asm("incl %0\n\taddl %2,%0":"=r"(res):"0"(a),"r"(b))
   计算a+b+1。我们看看add1(a,a)生成的指令,假设%eax中包含a的值:
     #%0=eax,%2=eax
     #APP
     incl %eax
     addl %eax, %eax
     #NO_APP
   结果不对,读或写汇编的人很容易看出这点,但是GCC不知道,加个&,定义写成:
     #define add1(a, b) asm("addl %2,%0":"=&r"(res):"0"(a),"r"(b))
   告诉GCC参作数%0earlyclobber,不要和%2分配到同一个寄存器,这样add1(a,a)生成指令:
     movl %eax, %edx
     #%0=edx, %2=eax
     #APP
     incl %edx
     addl %eax, %edx # edx = output
     #NO_APP
四、对%quot"限定符的解释
   %:说明指令中可与下一操作数交换的那个操作数,这意味着编译可以交换这两个操作数以使得能以代价更小的方法来满足操作数约束,这常常用于真正只有两个操作数的加法指令的指令样板中,这种加法指令的结果必须存放在两个操作数之一中。

阅读(1450) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~