gcc中的内嵌汇编语言.docx
- 文档编号:13549463
- 上传时间:2023-06-15
- 格式:DOCX
- 页数:22
- 大小:29.38KB
gcc中的内嵌汇编语言.docx
《gcc中的内嵌汇编语言.docx》由会员分享,可在线阅读,更多相关《gcc中的内嵌汇编语言.docx(22页珍藏版)》请在冰点文库上搜索。
gcc中的内嵌汇编语言
gcc中的内嵌汇编语言
作者:
欧阳光ouyangguang@
初次接触到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选项编译该源文件,那么在生成的汇
编文件中,你将会看到生成了movlfoo,bar这样一条指令,这显然是错误的.这个错误在稍
后的编译检查中会被发现.
3.变量输出:
按照输出限定符的指定将寄存器的内容输出到某个内存变量中,如果输出操
作数的限定符指定为内存变量("m"),则该步骤被省略.这就是对第一个冒号后内容的解
释,如:
asm("mov%0,%1":
"=m"(foo),"=a"(bar):
);编译后为
#APP
movlfoo,eax
#NO_APP
movleax,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");
movleax_in,%eax
movlebx_in,%ebx
movlecx_in,%ecx
#APP
pushl%edi
pushl%ebp
lcall%cs:
setc%al
addleb,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"的作用
源程序
编译后的汇编代码
正
常
情
况
下
intmain()
{intbar=1;
bar=fun();
bar++;
returnbar;
}
pushl%ebp
movl%esp,%ebp
callfun
incl%eax#显然,bar缺省使用eax寄存器
leave
ret
加
了
汇
编
后
intmain()
{intbar=1;
bar=fun();
asmvolatile("":
:
:
"eax");
bar++;
returnbar;
}
pushl%ebp
movl%esp,%ebp#建立堆栈框架
callfun
#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("callfun;movebx,%1":
"=a"(foo):
"r"(bar));gcc编译的结果是foo和bar同时使用
eax寄存器:
movlbar,eax
#APP
callfun
movlebx,eax
#NO_APP
movleax,foo
本来这段代码的意图是将fun()函数的返回值放入foo变量,但半路杀出个程咬金,用ebx的值冲
掉了返回值,所以这是一段错误的代码,解决的方法是加上一个给输出操作数加上一个"&"限定
符:
asm("callfun;movebx,%1":
"=&a"(foo):
"r"(bar));这样gcc就会让输入操作数另寻高
就,不再使用eax寄存器了
GCC内嵌汇编简介
发表:
2004-4-30:
16:
23出处:
你的博客网(yourblog.org)
在内嵌汇编中,可以将C语言表达式指定为汇编指令的操作数,而且不用去管如何将C语言表达式的值读入哪个寄存器,以及如何将计算结果写回C变量,你只要告诉程序中C语言表达式与汇编指令操作数之间的对应关系即可,GCC会自动插入代码完成必要的操作。
1、简单的内嵌汇编
例:
__asm____volatile__("hlt");“__asm__”表示后面的代码为内嵌汇编,“asm”是“__asm__”的别名。
“__volatile__”表示编译器不要优化代码,后面的指令保留原样,“volatile”是它的别名。
括号里面是汇编指令。
2、内嵌汇编举例
使用内嵌汇编,要先编写汇编指令模板,然后将C语言表达式与指令的操作数相关联,并告诉GCC对这些操作有哪些限制条件。
例如在下面的汇编语句:
__asm____violate__("movl%1,%0":
"=r"(result):
"m"(input));
“movl%1,%0”是指令模板;“%0”和“%1”代表指令的操作数,称为占位符,内嵌汇编靠它们将C语言表达式与指令操作数相对应。
指令模板后面用小括号括起来的是C语言表达式,本例中只有两个:
“result”和“input”,他们按照出现的顺序分别与指令操作数“%0”,“%1”对应;注意对应顺序:
第一个C表达式对应“%0”;第二个表达式对应“%1”,依次类推,操作数至多有10个,分别用“%0”,“%1”….“%9”表示。
在每个操作数前面有一个用引号括起来的字符串,字符串的内容是对该操作数的限制或者说要求。
“result”前面的限制字符串是“=r”,其中“=”表示“result”是输出操作数,“r”表示需要将“result”与某个通用寄存器相关联,先将操作数的值读入寄存器,然后在指令中使用相应寄存器,而不是“result”本身,当然指令执行完后需要将寄存器中的值存入变量“result”,从表面上看好像是指令直接对“result”进行操作,实际上GCC做了隐式处理,这样我们可以少写一些指令。
“input”前面的“r”表示该表达式需要先放入某个寄存器,然后在指令中使用该寄存器参加运算。
C表达式或者变量与寄存器的关系由GCC自动处理,我们只需使用限制字符串指导GCC如何处理即可。
限制字符必须与指令对操作数的要求相匹配,否则产生的汇编代码将会有错,读者可以将上例中的两个“r”,都改为“m”(m表示操作数放在内存,而不是寄存器中),编译后得到的结果是:
movlinput,result
很明显这是一条非法指令,因此限制字符串必须与指令对操作数的要求匹配。
例如指令movl允许寄存器到寄存器,立即数到寄存器等,但是不允许内存到内存的操作,因此两个操作数不能同时使用“m”作为限定字符。
GCC内嵌汇编2
出处:
http:
//www.yourblog.org/Data/20044/35012.html
内嵌汇编语法如下:
__asm__(汇编语句模板:
输出部分:
输入部分:
破坏描述部分)
共四个部分:
汇编语句模板,输出部分,输入部分,破坏描述部分,各部分使用“:
”格开,汇编语句模板必不可少,其他三部分可选,如果使用了后面的部分,而前面部分为空,也需要用“:
”格开,相应部分内容为空。
例如:
__asm____volatile__("cli":
:
:
"memory")
1、汇编语句模板
汇编语句模板由汇编语句序列组成,语句之间使用“;”、“\n”或“\n\t”分开。
指令中的操作数可以使用占位符引用C语言变量,操作数占位符最多10个,名称如下:
%0,%1,…,%9。
指令中使用占位符表示的操作数,总被视为long型(4个字节),但对其施加的操作根据指令可以是字或者字节,当把操作数当作字或者字节使用时,默认为低字或者低字节。
对字节操作可以显式的指明是低字节还是次字节。
方法是在%和序号之间插入一个字母,“b”代表低字节,“h”代表高字节,例如:
%h1。
2、输出部分
输出部分描述输出操作数,不同的操作数描述符之间用逗号格开,每个操作数描述符由限定字符串和C语言变量组成。
每个输出操作数的限定字符串必须包含“=”表示他是一个输出操作数。
例:
__asm____volatile__("pushfl;popl%0;cli":
"=g"(x))
描述符字符串表示对该变量的限制条件,这样GCC就可以根据这些条件决定如何分配寄存器,如何产生必要的代码处理指令操作数与C表达式或C变量之间的联系。
3、输入部分
输入部分描述输入操作数,不同的操作数描述符之间使用逗号格开,每个操作数描述符由限定字符串和C语言表达式或者C语言变量组成。
例1:
__asm____volatile__("lidt%0":
:
"m"(real_mode_idt));
例二(bitops.h):
Static__inline__void__set_bit(intnr,volatilevoid*addr)
{
__asm__(
"btsl%1,%0"
:
"=m"(ADDR)
:
"Ir"(nr));
}
后例功能是将(*addr)的第nr位设为1。
第一个占位符%0与C语言变量ADDR对应,第二个占位符%1与C语言变量nr对应。
因此上面的汇编语句代码与下面的伪代码等价:
btslnr,ADDR,该指令的两个操作数不能全是内存变量,因此将nr的限定字符串指定为“Ir”,将nr与立即数或者寄存器相关联,这样两个操作数中只有ADDR为内存变量。
4、限制字符
4.1、限制字符列表
限制字符有很多种,有些是与特定体系结构相关,此处仅列出常用的限定字符和i386中可能用到的一些常用的限定符。
它们的作用是指示编译器如何处理其后的C语言变量与指令操作数之间的关系。
分类限定符描述
通用寄存器“a”将输入变量放入eax
这里有一个问题:
假设eax已经被使用,那怎么办?
其实很简单:
因为GCC知道eax已经被使用,它在这段汇编代码
的起始处插入一条语句pushl%eax,将eax内容保存到堆栈,然
后在这段代码结束处再增加一条语句popl%eax,恢复eax的内容
“b”将输入变量放入ebx
“c”将输入变量放入ecx
“d”将输入变量放入edx
“s”将输入变量放入esi
“d”将输入变量放入edi
“q”将输入变量放入eax,ebx,ecx,edx中的一个
“r”将输入变量放入通用寄存器,也就是eax,ebx,ecx,
edx,esi,edi中的一个
“A”把eax和edx合成一个64位的寄存器(uselonglongs)
内存“m”内存变量
“o”操作数为内存变量,但是其寻址方式是偏移量类型,
也即是基址寻址,或者是基址加变址寻址
“V”操作数为内存变量,但寻址方式不是偏移量类型
“”操作数为内存变量,但寻址方式为自动增量
“p”操作数是一个合法的内存地址(指针)
寄存器或内存“g”将输入变量放入eax,ebx,ecx,edx中的一个
或者作为内存变量
“X”操作数可以是任何类型
立即数
“I”0-31之间的立即数(用于32位移位指令)
“J”0-63之间的立即数(用于64位移位指令)
“N”0-255之间的立即数(用于out指令)
“i”立即数
“n”立即数,有些系统不支持除字以外的立即数,
这些系统应该使用“n”而不是“i”
匹配“0”,表示用它限制的操作数与某个指定的操作数匹配,
“1”...也即该操作数就是指定的那个操作数,例如“0”
“9”去描述“%1”操作数,那么“%1”引用的其实就
是“%0”操作数,注意作为限定符字母的0-9与
指令中的“%0”-“%9”的区别,前者描述操作数,
后者代表操作数。
&该输出操作数不能使用过和输入操作数相同的寄存器
操作数类型“=”操作数在指令中是只写的(输出操作数)
“+”操作数在指令中是读写类型的(输入输出操作数)
浮点数“f”浮点寄存器
“t”第一个浮点寄存器
“u”第二个浮点寄存器
“G”标准的80387浮点常数
%该操作数可以和下一个操作数交换位置
例如addl的两个操作数可以交换顺序
(当然两个操作数都不能是立即数)
#部分注释,从该字符到其后的逗号之间所有字母被忽略
*表示如果选用寄存器,则其后的字母被忽略
5、破坏描述部分
破坏描述符用于通知编译器我们使用了哪些寄存器或内存,由逗号格开的字符串组成,每个字符串描述一种情况,一般是寄存器名;除寄存器外还有“memory”。
例如:
“%eax”,“%ebx”,“memory”等。
AT&T与INTEL的汇编语言语法的区别
出处:
http:
//www.yourblog.org/Data/20044/34973.html
1、大小写
INTEL格式的指令使用大写字母,而AT&T格式的使用小写字母。
例:
INTELAT&T
MOVEAX,EBXmovl%ebx,%eax
2、操作数赋值方向
在INTEL语法中,第一个表示目的操作数,第二个表示源操作数,赋值方向从右向左。
AT&T语法第一个为源操作数,第二个为目的操作数,方向从左到右,合乎自然。
例:
INTELAT&T
MOVEAX,EBXmovl%ebx,%eax
3、前缀
在INTEL语法中寄存器和立即数不需要前缀;AT&T中寄存器需要加前缀“%”;立即数
需要加前缀“$”。
例:
INTELAT&T
MOVEAX,1movl$1,%eax
符号常数直接引用,不需要加前缀,如:
movlvalue,%ebx,value为一常数;在符
号前加前缀$表示引用符号地址,如movl$value,%ebx,是将value的地址放到ebx中。
总线锁定前缀“lock”:
总线锁定操作。
“lock”前缀在Linux核心代码中使用很多,特
别是SMP代码中。
当总线锁定后其它CPU不能存取锁定地址处的内存单元。
远程跳转指令和子过程调用指令的操作码使用前缀“l“,分别为ljmp,lcall,与之
相应的返回指令伪lret。
例:
INTELAT&T
CALLFARSECTION:
OFFSETlcall$secion:
$offset
JMPFARSECTION:
OFFSETljmp$secion:
$offset
RETFARSATCK_ADJUSTlret$stack_adjust
4、间接寻址语法
INTEL中基地址使用“[”、“]”,而在AT&T中使用“(”、“)”;另外处理复杂操作数的
语法也不同,INTEL为Segreg:
[base+index*scale+disp],而在AT&T中为
%segreg:
disp(base,index,sale),其中segreg,index,scale,disp都是可选的,在指定
index而没有显式指定Scale的情况下使用默认值1。
Scale和disp不需要加前缀“&”。
INTELAT&T
Instrinstr
foo,segreg:
[base+index*scale+disp]%segreg:
disp(base,index,scale),foo
5、后缀
AT&T语法中大部分指令操作码的最后一个字母表示操作数大小,“b”表示byte(一个
字节);“w”表示word(2个字节);“l”表示long(4个字节)。
INTEL中处理内存操作数
时也有类似的语法如:
BYTEPTR、WORDPTR、DWORDPTR。
例:
INTELAT&T
moval,blmovb%bl,%al
movax,bxmovw%bx,%ax
moveax,dwordptr[ebx]movl(%ebx),%eax
在AT&T汇编指令中,操作数扩展指令有两个后缀,一个指定源操作数的字长,另一个
指定目标操作数的字长。
AT&T的符号扩展指令的为“movs”,零扩展指令为“movz”(相应
的Intel指令为“movsx”和“movzx”)。
因此,“movsbl%al,%edx”表示对寄存器al中的
字节数据进行字节到长字的符号扩展,计算结果存放在寄存器edx中。
下面是一些允许的操
作数扩展后缀:
bl:
字节->长字
bw:
字节->字
wl:
字->长字
跳转指令标号后的后缀表示跳转方向,“f”表示向前(forward),“b”表示向后(back)。
例:
jmp1f
1:
jmp1f
1:
6、指令
INTEL汇编与AT&T汇编指令基本相同,差别仅在语法上。
关于每条指令的语法可以参考I386Manual。
gcc中的内嵌汇编语言
出处:
gcc采用的是AT&T的汇编格式,MS采用Intel的格式.
一 基本语法
语法上主要有以下几个不同.
★寄存器命名原则
AT&T:
%eaxIntel:
eax
★源/目的操作数顺序
AT&T:
movl%eax,%ebxIntel:
movebx,eax
★常数/立即数的格式
AT&T:
movl$_value,%ebxIntel:
moveax,_value
把_value的地址放入eax寄存器
AT&T:
movl$0xd00d,%ebxIntel:
movebx,0xd00d
★操作数长度标识
AT&T:
movw%ax,%bxIntel:
movbx,ax
★寻址方式
AT&T:
immed32(basepointer,indexpointer,indexscale)
Intel:
[basepointer+indexpointer*indexscale+imm32)
Linux工作于保护模式下,用的是32位线性地址,所以在计算地址时
不用考虑segment
ffset的问题.上式中的地址应为:
imm32+basepointer+indexpointer*indexscale
下面是一些例子:
★直接寻址
AT&T:
_booga ;_booga是一个全局的C变量
注意加上$是表示地址引用,不加是表示值引用.
注:
对于局部变量,可以通过堆栈指针引用.
Intel:
[_booga]
★寄存器间接寻址
AT&T:
(%eax)
Intel:
[eax]
★变址寻址
AT&T:
_variable(%eax)
Intel:
[eax+_variable]
AT&T:
_array(,%eax,4)
Intel:
[eax*4+_array]
AT&T:
_array(%ebx,%eax,8)
Intel:
[ebx+eax*8+_array]
二 基本的行内汇编
基本的行内汇编很简单,一般是按照下面的格式
asm("statements");
例如:
asm("nop");asm("cli");
asm 和 __asm__是完全一样的.
如果有多行汇编,则每一行都要加上 "nt"
例如:
asm("pushl%eaxnt"
"movl$0,%eaxnt"
"popl%eax");
实际上gcc在处理汇编时,是要把asm(...)的内容"打印"到汇编
文件中,所以格式控制字符是必要的.
再例如:
asm("movl%eax,%ebx");
asm("xorl%ebx,%edx");
asm("movl$0,_booga);
在上面的例子中,由于我们在行内汇编中改变了edx和ebx的值,但是
由于gcc的特殊的处理方法,即先形成汇
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- gcc 中的 汇编语言
![提示](https://static.bingdoc.com/images/bang_tan.gif)