襄阳公共教育资源登录:gcc中的内嵌汇编语言

来源:百度文库 编辑:九乡新闻网 时间:2024/04/29 11:07:43
gcc中的内嵌汇编语言(Intel   i386平台)  

一.声明  
虽然Linux的核心代码大部分是用C语言编写的,但是不可避免的其中  
还是有一部分是用汇编语言写成的。有些汇编语言代码是直接写在汇  
编源程序中的,特别是Linux的启动代码部分;还有一些则是利用gcc  
的内嵌汇编语言嵌在C语言程序中的。这篇文章简单介绍了gcc中的内  
嵌式汇编语言,主要想帮助那些才开始阅读Linux核心代码的朋友们  
能够更快的入手。  

写这篇文章的主要信息来源是GNU的两个info文件:as.info和  
gcc.info,如果你觉得这篇文章中的介绍还不够详细的话,你可以查  
阅这两个文件。当然,直接查阅这两个文件可以获得更加权威的信息。  
如果你不想被这两篇文档中的一大堆信息搞迷糊的话,我建议你先阅  
读一下这篇文章,然后在必要时再去查阅更权威的信息。  

二.简介  
在Linux的核心代码中,还是存在相当一部分的汇编语言代码。如果  
你想顺利阅读Linux代码的话,你不可能绕过这一部分代码。在Linux  
使用的汇编语言代码中,主要有两种格式:一种是直接写成汇编语言  
源程序的形式,这一部分主要是一些Linux的启动代码;另一部分则  
是利用gcc的内嵌式汇编语言语句asm嵌在Linux的C语言代码中的。这  
篇文章主要是介绍第二种形式的汇编语言代码。  

首先,我介绍一下as支持的汇编语言的语法格式。大家知道,我们现  
在学习的汇编语言的格式主要是Intel风格的,而在Linux的核心代码  
中使用的则是AT&T格式的汇编语言代码,应该说大部分人对这种格式  
的汇编语言还不是很了解,所以我觉得有必要介绍一下。  

接着,我主要介绍一下gcc的内嵌式汇编语言的格式。gcc的内嵌式汇  
编语言提供了一种在C语言源程序中直接嵌入汇编指令的很好的办法,  
既能够直接控制所形成的指令序列,又有着与C语言的良好接口,所  
以在Linux代码中很多地方都使用了这一语句。  

三.AT&T的汇编语言语法格式  
我想我们大部分人对Intel格式的汇编语言都很了解了。但是,在  
Linux核心代码中,所有的汇编语言指令都是用AT&T格式的汇编语  
言书写的。这两种汇编语言在语法格式上有着很大的不同:  

1.在AT&T的汇编语言中,用 '$ '前缀表示一个立即操作数;而在Intel  
的格式中,立即操作数的表示不带任何前缀符。例如:下面两个语句  
是完全相同的:  
*AT&T:   pushl   $4  
*Intel:   push   4  

2.AT&T和Intel的汇编语言格式中,源操作数和目标操作数的位置正  
好相反。Intel的汇编语言中,目标操作数在源操作数的左边;而在  
AT&T的汇编语言中,目标操作数则在源操作数的右边。例如:  
*AT&T   :   addl   $4,%eax  
*Intel:   add   eax,4  

3.在AT&T的汇编语言中,操作数的字长是由操作码助记符的最后一个  
字母决定的,后缀 'b '、 'w '、 'l '分别表示操作数的字长为8比特(字  
节,byte),16比特(字,word)和32比特(长字,long),而  
Intel格式中操作数的字长是用“word   ptr”或者“byte   ptr”等前  
缀来表示的。例如:  
*AT&T:   movb   FOO,%al  
*Intel:   mov   al,byte   ptr   FOO  

4.在AT&T汇编指令中,直接远跳转/调用的指令格式是“lcall/ljmp  
$SECTION,$OFFSET”,同样,远程返回的指令是“lret  
$STACK-ADJUST”;而在Intel格式中,相应的指令分别为“call/jmp  
far   SECTION:OFFSET”和“ret   far   STACK-ADJUST”。  

①AT&T汇编指令操作助记符命名规则  
AT&T汇编语言中,操作码助记符的后缀字符指定了该指令中操作数的  
字长。后缀字母 'b '、 'w '、 'l '分别表示字长为8比特(字节,byte),  
16比特(字,word)和32比特(长字,long)的操作数。如果助记符  
中没有指定字长后缀并且该指令中没有内存操作数,汇编程序 'as '会  
根据指令中指定的寄存器操作数补上相应的后缀字符。所以,下面的  
两个指令具有相同的效果(这只是GNU的汇编程序as的一个特性,AT&T  
的Unix汇编程序将没有字长后缀的指令的操作数字长假设为32比特):  

mov   %ax,%bx  

movw   %ax,%bx  

AT&T中几乎所有的操作助记符与Intel格式中的助记符同名,仅有一  
小部分例外。操作数扩展指令就是例外之一。在AT&T汇编指令中,操  
作数扩展指令有两个后缀:一个指定源操作数的字长,另一个指定目  
标操作数的字长。AT&T的符号扩展指令的基本助记符为 'movs ',零扩  
展指令的基本助记符为 'movz '(相应的Intel指令为 'movsx '和  
'movzx ')。因此, 'movsbl   %al,%edx '表示对寄存器al中的字节数据  
进行字节到长字的符号扩展,计算结果存放在寄存器edx中。下面是一  
些允许的操作数扩展后缀:  
*bl:   字节-> 长字  
*bw:   字节-> 字  
*wl:   字-> 长字  
还有一些其他的类型转换指令的对应关系:  

*Intel   *AT&T  
⑴   cbw   cbtw  
符号扩展:al-> ax  
⑵   cwde   cwtl  
符号扩展:ax-> eax  
⑶   cwd   cwtd  
符号扩展:ax-> dx:ax  
⑷   cdq   cltd  
符号扩展:eax-> edx:eax  

还有一个不同名的助记符就是远程跳转/调用指令。在Intel格式中,  
远程跳转/调用指令的助记符为“call/jmp   far”,而在AT&T的汇编  
语言中,相应的指令为“lcall”和“ljmp”。  

②AT&T中寄存器的命名  
在AT&T汇编语言中,寄存器操作数总是以 '% '作为前缀。80386芯片的  
寄存器包括:  
⑴8个32位寄存器: '%eax ', '%ebx ', '%ecx ', '%edx ', '%edi ', '%esi ',  
'%ebp ', '%esp '  
⑵8个16位寄存器: '%ax ', '%bx ', '%cx ', '%dx ', '%si ', '%di ', '%bp ',  
'%sp '  
⑶8个8位寄存器: '%ah ', '%al ', '%bh ', '%bl ', '%ch ', '%cl ', '%dh ',  
'%dl '  
⑷6个段寄存器: '%cs ', '%ds ', '%es ', '%ss ', '%fs ', '%gs '  
⑸3个控制寄存器: '%cr0 ', '%cr1 ', '%cr2 '  
⑹6个调试寄存器: '%db0 ', '%db1 ', '%db2 ', '%db3 ', '%db6 ', '%db7 '  
⑺2个测试寄存器: '%tr6 ', '%tr7 '  
⑻8个浮点寄存器栈: '%st(0) ', '%st(1) ', '%st(2) ', '%st(3) ',  
'%st(4) ', '%st(5) ', '%st(6) ', '%st(7) '  

*注:我对这些寄存器并不是都了解,这些资料只是摘自as.info文档。  
如果真的需要寄存器命名的资料,我想可以参考一下相应GNU工具的机  
器描述方面的源文件。  

③AT&T中的操作码前缀  
⑴段超越前缀 'cs ', 'ds ', 'es ', 'ss ', 'fs ', 'gs ':当汇编程序中对内  
存操作数进行SECTION:MEMORY-OPERAND引用时,自动加上相应的段超  
越前缀。  
⑵操作数/地址尺寸前缀 'data16 ', 'addr16 ':这些前缀将32位的操作  
数/地址转化为16位的操作数/地址。  
⑶总线锁定前缀 'lock ':总线锁定操作。 'lock '前缀在Linux核心代码  
中使用很多,特别是SMP代码中。  
⑷协处理器等待前缀 'wait ':等待协处理器完成当前操作。  
⑸指令重复前缀 'rep ', 'repe ', 'repne ':在串操作中重复指令的执行。  

④AT&T中的内存操作数  
在Intel的汇编语言中,内存操作数引用的格式如下:  

SECTION:[BASE   +   INDEX*SCALE   +   DISP]  
而在AT&T的汇编语言中,内存操作数的应用格式则是这样的:  

SECTION:DISP(BASE,INDEX,SCALE)  

下面是一些内存操作数的例子:  

*AT&T   *Intel  
⑴   -4(%ebp)   [ebp-4]  
⑵   foo(,%eax,4)   [foo+eax*4]  
⑶   foo(,1)   [foo]  
⑷   %gs:foo   gs:foo  

还有,绝对跳转/调用指令中的内存操作数必须以 '* '最为前缀,否则  
as总是假设这是一个相对跳转/调用指令。  

⑤AT&T中的跳转指令  
as汇编程序自动对跳转指令进行优化,总是使用尽可能小的跳转偏移  
量。如果8比特的偏移量无法满足要求的话,as会使用一个32位的偏  
移量,as汇编程序暂时还不支持16位的跳转偏移量,所以对跳转指令  
使用 'addr16 '前缀是无效的。  

还有一些跳转指令只支持8位的跳转偏移量,这些指令包括: 'jcxz ',  
'jecxz ', 'loop ', 'loopz ', 'loope ', 'loopnz '和 'loopne '。所以,  
在as的汇编源程序中使用这些指令可能会出错。(幸运的是,gcc并  
不使用这些指令)  

对AT&T汇编语言语法的简单介绍差不多了,其中有些特性是as特有的。  
在Linux核心代码中,并不涉及到所有上面这些提到的语法规则,其  
中有两点规则特别重要:第一,as中对寄存器引用时使用前缀 '% ';第  
二,AT&T汇编语言中源操作数和目标操作数的位置与我们熟悉的Intel  
的语法正好相反。  

四.gcc的内嵌汇编语言语句asm  
利用gcc的asm语句,你可以在C语言代码中直接嵌入汇编语言指令,  
同时还可以使用C语言的表达式指定汇编指令所用到的操作数。这一  
特性提供了很大的方便。  

要使用这一特性,首先要写一个汇编指令的模板(这种模板有点类似  
于机器描述文件中的指令模板),然后要为每一个操作数指定一个限  
定字符串。例如:  
extern   __inline__   void   change_bit(int   nr,volatile   void   *addr)  
{  

__asm__   __volatile__(   LOCK_PREFIX  

"btcl   %1,%0 "  

: "=m "   (ADDR)  

: "ir "   (nr));  
}  
上面的函数中:  

LOCK_PREFIX:这是一个宏,如果定义了__SMP__,扩展为 "lock; ",  
用于指定总线锁定前缀,否则扩展为 " "。  

ADDR:这也是一个宏,定义为(*(volatile   struct   __dummy   *)   addr)  

"btcl   %1,%0 ":这就是嵌入的汇编语言指令,btcl为指令操作码,%1,  
%0是这条指令两个操作数的占位符。后面的两个限定字符串就用于描  
述这两个操作数。  

:   "=m "   (ADDR):第一个冒号后的限定字符串用于描述指令中的“输  
出”操作数。刮号中的ADDR将操作数与C语言的变量联系起来。这个  
限定字符串表示指令中的“%0”就是addr指针指向的内存操作数。这  
是一个“输出”类型的内存操作数。  

:   "ir "   (nr):第二个冒号后的限定字符串用于描述指令中的“输入”  
操作数。这条限定字符串表示指令中的“%1”就是变量nr,这个的操  
作数可以是一个立即操作数或者是一个寄存器操作数。  

*注:限定字符串与操作数占位符之间的对应关系是这样的:在所有  
限定字符串中(包括第一个冒号后的以及第二个冒号后的所有限定字  
符串),最先出现的字符串用于描述操作数“%0”,第二个出现的字  
符串描述操作数“%1”,以此类推。  

①汇编指令模板  
asm语句中的汇编指令模板
  • 对我有用[0]
  • 丢个板砖[0]
  • 引用
  • 举报
  • 管理
  • TOP
精华推荐:借人气宣传一下自己开发的语言Dao
  • goldcattle
  • (goldcattle)
  • 等 级:
#4楼 得分:0回复于:2002-04-07 02:12:21 asm   ( "addl   %2,%0 ": "=r "(foo): "0 "(foo), "g "(bar));  
在上面这条指令中,“%0”是一个输入-输出类型的操作数, "=r "(foo)  
用于限定其输出功能,该指令的输出结果会存放到C语言变量foo中;  
指令中没有显式的出现“%1”操作数,但是针对它有一个限定字符串  
"0 "(foo),事实上指令中隐式的“%1”操作数用于描述“%0”操作数  
的输入功能,它的限定字符串中的 "0 "限定了“%1”操作数与“%0”  
具有相同的地址。可以这样理解上述指令中的模板:该指令将“%1”  
和“%2”中的值相加,计算结果存放回“%0”中,指令中的“%1”与  
“%0”具有相同的地址。注意,用于描述“%1”的 "0 "限定字符足以  
保证“%1”与“%0”具有相同的地址。但是,如果用下面的指令完成  
这种输入-输出操作就不会正常工作:  

asm   ( "addl   %2,%0 ": "=r "(foo): "r "(foo), "g "(bar));  
虽然该指令中“%0”和“%1”同样引用了C语言变量foo,但是gcc并  
不保证在生成的汇编程序中它们具有相同的地址。  

还有一些汇编指令可能会改变某些寄存器的值,相应的汇编指令模板  
中必须将这种情况通知编译器。所以在模板中还有第三种类型的限定  
字符串,它们跟在输入操作数限定字符串的后面,之间用冒号间隔。  
这些字符串是某些寄存器的名称,代表该指令会改变这些寄存器中的  
内容。  

在内嵌的汇编指令中可能会直接引用某些硬件寄存器,我们已经知道  
AT&T格式的汇编语言中,寄存器名以“%”作为前缀,为了在生成的  
汇编程序中保留这个“%”号,在asm语句中对硬件寄存器的引用必须  
用“%%”作为寄存器名称的前缀。如果汇编指令改变了硬件寄存器的  
内容,不要忘记通知编译器(在第三种类型的限定串中添加相应的字  
符串)。还有一些指令可能会改变CPU标志寄存器EFLAG的内容,那么  
需要在第三种类型的限定字符串中加入 "cc "。  

为了防止gcc在优化过程中对asm中的汇编指令进行改变,可以在 "asm "  
关键字后加上 "volatile "修饰符。  

可以在一条asm语句中描述多条汇编语言指令;各条汇编指令之间用  
“;”或者“\n”隔开。  

②操作数限定字符  
操作数限定字符串中利用规定的限定字符来描述相应的操作数,一些  
常用的限定字符有:(还有一些没有涉及的限定字符,参见gcc.info)  

1。 "m ":操作数是内存变量。  

2。 "o ":操作数是内存变量,但它的寻址方式必须是“偏移量”类型的,  
也就是基址寻址或者基址加变址寻址。  

3。 "V ":操作数是内存变量,其寻址方式非“偏移量”类型。  

4。 "   ":操作数是内存变量,其地址自动增量。  

6。 "r ":操作数是通用寄存器。  

7。 "i ":操作数是立即操作数。(其值可在汇编时确定)  

8。 "n ":操作数是立即操作数。有些系统不支持除字(双字节)以外的  
立即操作数,这些操作数要用 "n "而不是 "i "来描述。  

9。 "g ":操作数可以是立即数,内存变量或者寄存器,只要寄存器属  
于通用寄存器。  

10。 "X ":操作数允许是任何类型。  

11。 "0 ", "1 ",..., "9 ":操作数与某个指定的操作数匹配。也就是说,  
该操作数就是指定的那个操作数。例如,如果用 "0 "来描述 "%1 "操作  
数,那么 "%1 "引用的其实就是 "%0 "操作数。  

12。 "p ":操作数是一个合法的内存地址(指针)。  

13。 "= ":操作数在指令中是只写的(输出操作数)。  

14。 "+ ":操作数在指令中是读-写类型的(输入-输出操作数)。  

15。 "a ":寄存器EAX。  

16。 "b ":寄存器EBX。  

17。 "c ":寄存器ECX。  

18。 "d ":寄存器EDX。  

19。 "q ":寄存器 "a ", "b ", "c "或者 "d "。  

20。 "A ":寄存器 "a "或者 "d "。  

21。 "a ":寄存器EAX。  

22。 "f ":浮点数寄存器。  

23。 "t ":第一个浮点数寄存器。  

24。 "u ":第二个浮点数寄存器。  

25。 "D ":寄存器di。  

26。 "S ":寄存器si。  

27。 "I ":0-31之间的立即数。(用于32位的移位指令)  

28。 "J ":0-63之间的立即数。(用于64位的移位指令)  

29。 "N ":0-255之间的立即数。(用于 "out "指令)  

30。 "G ":标准的80387浮点常数。  

*注:还有一些不常见的限定字符并没有在此说明,另外有一些限定  
字符,例如 "% ", "& "等由于我缺乏编译器方面的一些知识,所以我也  
不是很理解它们的含义,如果有高手愿意补充,不慎感激!不过在  
核心代码中出现的限定字符差不多就是上面这些了。