Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1923973
  • 博文数量: 261
  • 博客积分: 8073
  • 博客等级: 中将
  • 技术积分: 2363
  • 用 户 组: 普通用户
  • 注册时间: 2006-04-10 15:23
文章分类

全部博文(261)

文章存档

2013年(1)

2012年(1)

2011年(50)

2010年(34)

2009年(4)

2008年(17)

2007年(55)

2006年(99)

分类:

2006-04-15 09:52:01

初次接触到AT&T格式的汇编代码,看着那一堆莫名其妙的怪符号,真是有点痛不欲生的感觉,只好慢慢地去啃gcc文档,在似懂非懂的状态下过了一段时间。后来又在网上找到了灵溪写的《gcc中的内嵌汇编语言》一文,读后自感大有裨益。几个月下来,接触的源代码多了以后,慢慢有了一些经验。为了使初次接触AT&T格式的汇编代码的同志不至于遭受我这样的痛苦,就整理出该文来和大家共享。如有错误之处,欢迎大家指正,共同提高。

本文主要以举例的方式对gcc中的内嵌汇编语言进行进一步的解释。

一、gcc对内嵌汇编语言的处理方式

gcc在编译内嵌汇编语言时,采取的步骤如下:

1、变量输入:根据限定符的内容将输入操作数放入合适的寄存器,如果限定符指定为立即数("i")或内存变量("m"),则该步被省略,如果限定符没有具体指定输入操作数的类型(如常用的"g"),gcc会视需要决定是否将该操作数输入到某个寄存器。这样每个占位符都与某个寄存器,内存变量,或立即数形成了一一对应的关系,这就是对第二个冒号后内容的解释。

如:"a"(foo),"i"(100),"m"(bar)表示%0对应eax寄存器,%1对应100,%2对应内存变量bar

2、生成代码:然后根据这种一一对应的关系(还应包括输出操作符),用这些寄存器,内存变量,或立即数来取代汇编代码中的占位符(则有点像宏操作),注意,则一步骤并不检查由这种取代操作所生成的汇编代码是否合法,例如,如果有这样一条指令asm("movl %0,%1"::"m"(foo),"m"(bar));如果你用gcc -c -S选项编译该源文件,那么在生成的汇编文件中,你将会看到生成了movl foo,bar这样一条指令,这显然是错误的。这个错误在稍后的编译检查中会被发现。

3、变量输出:按照输出限定符的指定将寄存器的内容输出到某个内存变量中,如果输出操作数的限定符指定为内存变量("m"),则该步骤被省略。这就是对第一个冒号后内容的解释,如:asm("mov %0,%1":"=m"(foo),"=a"(bar);编译后为

#APP
movl foo,eax

#NO_APP
movl eax,bar

该语句虽然有点怪怪的,但它很好的体现了gcc的运作方式。

再以arch/i386/kernel/apm。c中的一段代码为例,我们来比较一下它们编译前后的情况

源程序

编译后的汇编代码
PHP 代码:
__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

pushl %edi

pushl
%ebp

lcall
%cs:

setc %al

addl eb,ec

popl
%ebp

popl
%edi

#NO_APP

movl %eax,ea

movl
%ebx,eb

movl
%ecx,ec

movl
%edx,ed

movl
%esi,es

二、对第三个冒号后面内容的解释

第三个冒号后面内容主要针对gcc优化处理,它告诉gcc在本段汇编代码中对寄存器和内存的使用情况,以免gcc在优化处理时产生错误。

1、它可以是"eax","ebx","ecx"等寄存器名,表示本段汇编代码对该寄存器进行了显式操作,如 asm ("mov %%eax,%0",:"=r"(foo)::"eax");这样gcc在优化时会避免使用eax作临时变量,或者避免cache到eax的内存变量通过该段汇编码。

下面的代码均用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

#fun的返回值放入bar中,此时由于嵌入汇编

#指明改变了eax的值,为了避免冲突,

#bar改为使用edx寄存器

movl %eax,%edx

#APP

#NO_APP

incl %edx

movl %edx,%eax #放入main()的返回值

leave

ret

2、"merory"是一个常用的限定,它表示汇编代码以不可预知的方式改变了内存,这样gcc在优化时就不会让cache到寄存器的内存变量使用该寄存器通过汇编代码,否则可能会发生同步出错。有了上面的例子,这个问题就很好理解了


三、对"&"限定符的解释

这是一个较常见用于输出的限定符。它告诉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寄存器了
阅读(1056) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~