对 《gcc中的内嵌汇编语言》一文的补充说明
本文主要以举例的方式对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
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在优化处理时产生错误.
他能够是"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
"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寄存器了
四.对%quot;&"限定符的解释(老铁补充)
%:说明指令中可和下一操作数交换的那个操作数,这意味着编译能够交换这两个操作数以使得能以代价更小的方法来满足操作数约束,这常常用于真正只有两个操作数的加法指令的指令样板中,这种加法指令的结果必须存放在两个操作数之一中.
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
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在优化处理时产生错误.
他能够是"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
"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寄存器了
四.对%quot;&"限定符的解释(老铁补充)
%:说明指令中可和下一操作数交换的那个操作数,这意味着编译能够交换这两个操作数以使得能以代价更小的方法来满足操作数约束,这常常用于真正只有两个操作数的加法指令的指令样板中,这种加法指令的结果必须存放在两个操作数之一中.

浙公网安备 33010602011771号