深入理解计算机系统 3.1linux中的汇编之不同(zz)

本文详细介绍GCC中的内嵌汇编语言,适用于Intel i386平台。文章讲解AT&T汇编语言格式特点,包括寄存器命名、操作数表示等,并介绍如何使用GCC asm语句在C代码中嵌入汇编指令。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

http://www.dzsc.com/dzbbs/20030915/200765194326687841.html

gcc中的内嵌汇编语言

gcc中的内嵌汇编语言
  主 题:  gcc中的内嵌汇编语言(INTEL i386平台)
  作 者:  灵溪 <guyy@njupt.edu.cn>
  日 期:  4/1/99 08:51
  论 坛:  BJLC 源码论坛
  内 容:
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: PUSH l $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:这也是一个宏,定义为(*(volati

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值