登录 (或注册)中文
技术主题 软件下载 社区 技术讲座
Linux 中 x86 的内联汇编
将各个部分组合起来
BharataB.Rao(rbharata@in.ibm.com)IBMLinux技术中心,IBM软件实验室,印度
简介:BharataB.Rao提供了在Linux平台上使用和构造x86内联汇编的概括性介绍。他介绍了内联汇编及其各种用法的基础知
识,提供了一些基本的内联汇编编码指导,并解释了在Linux内核中内联汇编代码的一些实例。
发布日期:2001年3月01日
级别:初级
访问情况 :4506次浏览
评论:0(查看|添加评论-登录)
平均分(7个评分)
为本文评分
如果您是Linux内核的开发人员,您会发现自己经常要对与体系结构高度相关的功能进行编码或优化代码路径。您很可能是通过将
汇编语言指令插入到C语句的中间(又称为内联汇编的一种方法)来执行这些任务的。让我们看一下Linux中内联汇编的特定用
法。(我们将讨论限制在IA32汇编。)
GNU汇编程序简述
让我们首先看一下Linux中使用的基本汇编程序语法。GCC(用于Linux的GNUC编译器)使用AT&T汇编语法。下面列出了这
种语法的一些基本规则。(该列表肯定不完整;只包括了与内联汇编相关的那些规则。)
寄存器命名
寄存器名称有%前缀。即,如果必须使用eax,它应该用作%eax。
源操作数和目的操作数的顺序
在所有指令中,先是源操作数,然后才是目的操作数。这与将源操作数放在目的操作数之后的Intel语法不同。
mov%eax,%ebx,transfersthecontentsofeaxtoebx.
操作数大小
根据操作数是字节(byte)、字(word)还是长型(long),指令的后缀可以是b、w或l。这并不是强制性的;GCC会尝试通过读取操
作数来提供相应的后缀。但手工指定后缀可以改善代码的可读性,并可以消除编译器猜测不正确的可能性。
movb%al,%bl--Bytemove
movw%ax,%bx--Wordmove
movl%eax,%ebx--Longwordmove
立即操作数
通过使用$指定直接操作数。
movl$0xffff,%eax--willmovethevalueof0xffffintoeaxregister.
间接内存引用
任何对内存的间接引用都是通过使用()来完成的。
movb(%esi),%al--willtransferthebyteinthememory
Linux中x86的内联汇编 http://www.ibm.com/developerworks/cn/linux/sdk/assemb...
第1页共9页 2011/12/2412:15
pointedbyesiintoal
register
内联汇编
GCC为内联汇编提供特殊结构,它具有以下
格式
pdf格式笔记格式下载页码格式下载公文格式下载简报格式下载
:
GCG 的 "asm" 结构
asm(assemblertemplate
:outputoperands(optional)
:inputoperands(optional)
:listofclobberedregisters
(optional)
);
本例中,汇编程序
模板
个人简介word模板免费下载关于员工迟到处罚通告模板康奈尔office模板下载康奈尔 笔记本 模板 下载软件方案模板免费下载
由汇编指令组成。输入操作数是充当指令输入操作数使用的C表达式。输出操作数是将对其执行汇编指令输
出的C表达式。
内联汇编的重要性体现在它能够灵活操作,而且可以使其输出通过C变量显示出来。因为它具有这种能力,所以"asm"可以用作汇
编指令和包含它的C程序之间的接口。
一个非常基本但很重要的区别在于简单内联汇编只包括指令,而扩展内联汇编包括操作数。要说明这一点,考虑以下示例:
内联汇编的基本要素
{
inta=10,b;
asm("movl%1,%%eax;
movl%%eax,%0;"
:"=r"(b)/*output*/
:"r"(a)/*input*/
:"%eax");/*clobberedregister*/
}
在上例中,我们使用汇编指令使"b"的值等于"a"。请注意以下几点:
"b"是输出操作数,由%0引用,"a"是输入操作数,由%1引用。
"r"是操作数的约束,它指定将变量"a"和"b"存储在寄存器中。请注意,输出操作数约束应该带有一个约束修饰符"=",指
定它是输出操作数。
要在"asm"内使用寄存器%eax,%eax的前面应该再加一个%,换句话说就是%%eax,因为"asm"使用%0、%1等来标
识变量。任何带有一个%的数都看作是输入/输出操作数,而不认为是寄存器。
第三个冒号后的修饰寄存器%eax告诉将在"asm"中修改GCC%eax的值,这样GCC就不使用该寄存器存储任何其它的
值。
movl%1,%%eax将"a"的值移到%eax中,movl%%eax,%0将%eax的内容移到"b"中。
因为"b"被指定成输出操作数,因此当"asm"的执行完成后,它将反映出更新的值。换句话说,对"asm"内"b"所做的更改
将在"asm"外反映出来。
现在让我们更详细的了解每一项的含义。
汇编程序模板
汇编程序模板是一组插入到C程序中的汇编指令(可以是单个指令,也可以是一组指令)。每条指令都应该由双引号括起,或者整
组指令应该由双引号括起。每条指令还应该用一个定界符结尾。有效的定界符为新行(\n)和分号(;)。'\n'后可以跟一个tab(\t)作
Linux中x86的内联汇编 http://www.ibm.com/developerworks/cn/linux/sdk/assemb...
第2页共9页 2011/12/2412:15
为格式化符号,增加GCC在汇编文件中生成的指令的可读性。指令通过数%0、%1等来引用C表达式(指定为操作数)。
如果希望确保编译器不会在"asm"内部优化指令,可以在"asm"后使用关键字"volatile"。如果程序必须与ANSIC兼容,则应该
使用__asm__和__volatile__,而不是asm和volatile。
操作数
C表达式用作"asm"内的汇编指令操作数。在汇编指令通过对C程序的C表达式进行操作来执行有意义的作业的情况下,操作数是
内联汇编的主要特性。
每个操作数都由操作数约束字符串指定,后面跟用括弧括起的C表达式,例如:"constraint"(Cexpression)。操作数约束的主要功
能是确定操作数的寻址方式。
可以在输入和输出部分中同时使用多个操作数。每个操作数由逗号分隔开。
在汇编程序模板内部,操作数由数字引用。如果总共有n个操作数(包括输入和输出),那么第一个输出操作数的编号为0,逐项
递增,最后那个输入操作数的编号为n-1。总操作数的数目限制在10,如果机器描述中任何指令模式中的最大操作数数目大于
10,则使用后者作为限制。
修饰寄存器列表
如果"asm"中的指令指的是硬件寄存器,可以告诉GCC我们将自己使用和修改它们。这样,GCC就不会假设它装入到这些寄存器
中的值是有效值。通常不需要将输入和输出寄存器列为clobbered,因为GCC知道"asm"使用它们(因为它们被明确指定为约
束)。不过,如果指令使用任何其它的寄存器,无论是明确的还是隐含的(寄存器不在输入约束列表中出现,也不在输出约束列表中
出现),寄存器都必须被指定为修饰列表。修饰寄存器列在第三个冒号之后,其名称被指定为字符串。
至于关键字,如果指令以某些不可预知且不明确的方式修改了内存,则可能将"memory"关键字添加到修饰寄存器列表中。这样就
告诉GCC不要在不同指令之间将内存值高速缓存在寄存器中。
操作数约束
前面提到过,"asm"中的每个操作数都应该由操作数约束字符串描述,后面跟用括弧括起的C表达式。操作数约束主要是确定指令
中操作数的寻址方式。约束也可以指定:
是否允许操作数位于寄存器中,以及它可以包括在哪些种类的寄存器中
操作数是否可以是内存引用,以及在这种情况下使用哪些种类的地址
操作数是否可以是立即数
约束还要求两个操作数匹配。
常用约束
在可用的操作数约束中,只有一小部分是常用的;下面列出了这些约束以及简要描述。有关操作数约束的完整列表,请参考GCC和
GAS手册。
寄存器操作数约束 (r)
使用这种约束指定操作数时,它们存储在通用寄存器中。请看下例:
asm("movl%%cr3,%0\n":"=r"(cr3val));
这里,变量cr3val保存在寄存器中,%cr3的值复制到寄存器上,cr3val的值从该寄存器更新到内存中。指定"r"约束时,GCC可
以将变量cr3val保存在任何可用的GPR中。要指定寄存器,必须通过使用特定的寄存器约束直接指定寄存器名。
a%eax
b%ebx
c%ecx
d%edx
S%esi
D%edi
Linux中x86的内联汇编 http://www.ibm.com/developerworks/cn/linux/sdk/assemb...
第3页共9页 2011/12/2412:15
内存操作数约束 (m)
当操作数位于内存中时,任何对它们执行的操作都将在内存位置中直接发生,这与寄存器约束正好相反,后者先将值存储在要修改的
寄存器中,然后将它写回内存位置中。但寄存器约束通常只在对于指令来说它们是绝对必需的,或者它们可以大大提高进程速度时使
用。当需要在"asm"内部更新C变量,而您又确实不希望使用寄存器来保存其值时,使用内存约束最为有效。例如,idtr的值存储
在内存位置loc中:
("sidt%0\n"::"m"(loc));
匹配(数字)约束
在某些情况下,一个变量既要充当输入操作数,也要充当输出操作数。可以通过使用匹配约束在"asm"中指定这种情况。
asm("incl%0":"=a"(var):"0"(var));
在匹配约束的示例中,寄存器%eax既用作输入变量,也用作输出变量。将var输入读取到%eax,增加后将更新的%eax再次存
储在var中。这里的"0"指定第0个输出变量相同的约束。即,它指定var的输出实例只应该存储在%eax中。该约束可以用于以
下情况:
输入从变量中读取,或者变量被修改后,修改写回到同一变量中
不需要将输入操作数和输出操作数的实例分开
使用匹配约束最重要的意义在于它们可以导致有效地使用可用寄存器。
一般内联汇编用法示例
以下示例通过各种不同的操作数约束说明了用法。有如此多的约束以至于无法将它们一一列出,这里只列出了最经常使用的那些约束
类型。
"asm" 和寄存器约束 "r"让我们先看一下使用寄存器约束r的"asm"。我们的示例显示了GCC如何分配寄存器,以及它如何更新
输出变量的值。
intmain(void)
{
intx=10,y;
asm("movl%1,%%eax;
"movl%%eax,%0;"
:"=r"(y)/*yisoutputoperand*/
:"r"(x)/*xisinputoperand*/
:"%eax");/*%eaxisclobberedregister*/
}
在该例中,x的值复制为"asm"中的y。x和y都通过存储在寄存器中传递给"asm"。为该例生成的汇编代码如下:
main:
pushl%ebp
movl%esp,%ebp
subl$8,%esp
movl$10,-4(%ebp)
movl-4(%ebp),%edx/*x=10isstoredin%edx*/
#APP/*asmstartshere*/
movl%edx,%eax/*xismovedto%eax*/
movl%eax,%edx/*yisallocatedinedxandupdated*/
#NO_APP/*asmendshere*/
movl%edx,-8(%ebp)/*valueofyinstackisupdatedwith
thevaluein%edx*/
当使用"r"约束时,GCC在这里可以自由分配任何寄存器。在我们的示例中,它选择%edx来存储x。在读取了%edx中x的值
后,它为y也分配了相同的寄存器。
因为y是在输出操作数部分中指定的,所以%edx中更新的值存储在-8(%ebp),堆栈上y的位置中。如果y是在输入部分中指定
的,那么即使它在y的临时寄存器存储值(%edx)中被更新,堆栈上y的值也不会更新。
因为%eax是在修饰列表中指定的,GCC不在任何其它地方使用它来存储数据。
输入x和输出y都分配在同一个%edx寄存器中,假设输入在输出产生之前被消耗。请注意,如果您有许多指令,就不是这种情况
了。要确保输入和输出分配到不同的寄存器中,可以指定&约束修饰符。下面是添加了约束修饰符的示例。
intmain(void)
Linux中x86的内联汇编 http://www.ibm.com/developerworks/cn/linux/sdk/assemb...
第4页共9页 2011/12/2412:15
{
intx=10,y;
asm("movl%1,%%eax;
"movl%%eax,%0;"
:"=&r"(y)/*yisoutputoperand,notethe
&constraintmodifier.*/
:"r"(x)/*xisinputoperand*/
:"%eax");/*%eaxisclobberedregister*/
}
以下是为该示例生成的汇编代码,从中可以明显地看出x和y存储在"asm"中不同的寄存器中。
main:
pushl%ebp
movl%esp,%ebp
subl$8,%esp
movl$10,-4(%ebp)
movl-4(%ebp),%ecx/*x,theinputisin%ecx*/
#APP
movl%ecx,%eax
movl%eax,%edx/*y,theoutputisin%edx*/
#NO_APP
movl%edx,-8(%ebp)
特定寄存器约束的使用
现在让我们看一下如何将个别寄存器作为操作数的约束指定。在下面的示例中,cpuid指令采用%eax寄存器中的输入,然后在四
个寄存器中给出输出:%eax、%ebx、%ecx、%edx。对cpuid的输入(变量"op")传递到"asm"的eax寄存器中,因为cpuid
希望它这样做。在输出中使用a、b、c和d约束,分别收集四个寄存器中的值。
asm("cpuid"
:"=a"(_eax),
"=b"(_ebx),
"=c"(_ecx),
"=d"(_edx)
:"a"(op));
在下面可以看到为它生成的汇编代码(假设_eax、_ebx等...变量都存储在堆栈上):
movl-20(%ebp),%eax/*store'op'in%eax--input*/
#APP
cpuid
#NO_APP
movl%eax,-4(%ebp)/*store%eaxin_eax--output*/
movl%ebx,-8(%ebp)/*storeotherregistersin
movl%ecx,-12(%ebp)
respectiveoutputvariables*/
movl%edx,-16(%ebp)
strcpy函数可以通过以下方式使用"S"和"D"约束来实现:
asm("cld\n
Linux中x86的内联汇编 http://www.ibm.com/developerworks/cn/linux/sdk/assemb...
第5页共9页 2011/12/2412:15
rep\n
movsb"
:/*noinput*/
:"S"(src),"D"(dst),"c"(count));
通过使用"S"约束将源指针src放入%esi中,使用"D"约束将目的指针dst放入%edi中。因为rep前缀需要count值,所以将
它放入%ecx中。
在下面可以看到另一个约束,它使用两个寄存器%eax和%edx将两个32位的值合并在一起,然后生成一个64位的值:
#definerdtscll(val)\
__asm____volatile__("rdtsc":"=A"(val))
Thegeneratedassemblylookslikethis(ifvalhasa64bitmemoryspace).
#APP
rdtsc
#NO_APP
movl%eax,-8(%ebp)/*AsaresultofAconstraint
movl%edx,-4(%ebp)
%eaxand%edxserveasoutputs*/
Noteherethatthevaluesin%edx:%eaxserveas64bitoutput.
使用匹配约束
在下面将看到系统调用的代码,它有四个参数:
#define_syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4)\
typename(type1arg1,type2arg2,type3arg3,type4arg4)\
{\
long__res;\
__asm__volatile("int$0x80"\
:"=a"(__res)\
:"0"(__NR_##name),"b"((long)(arg1)),"c"((long)(arg2)),\
"d"((long)(arg3)),"S"((long)(arg4)));\
__syscall_return(type,__res);\
}
在上例中,通过使用b、c、d和S约束将系统调用的四个自变量放入%ebx、%ecx、%edx和%esi中。请注意,在输出中使用了
"=a"约束,这样,位于%eax中的系统调用的返回值就被放入变量__res中。通过将匹配约束"0"用作输入部分中第一个操作数约
束,syscall号__NR_##name被放入%eax中,并用作对系统调用的输入。这样,这里的%eax既可以用作输入寄存器,又可以用
作输出寄存器。没有其它寄存器用于这个目的。另请注意,输入(syscall号)在产生输出(syscall的返回值)之前被消耗(使
用)。
内存操作数约束的使用
请考虑下面的原子递减操作:
__asm____volatile__(
"lock;decl%0"
:"=m"(counter)
:"m"(counter));
Linux中x86的内联汇编 http://www.ibm.com/developerworks/cn/linux/sdk/assemb...
第6页共9页 2011/12/2412:15
为它生成的汇编类似于:
#APP
lock
decl-24(%ebp)/*counterismodifiedonitsmemorylocation*/
#NO_APP.
您可能考虑在这里为counter使用寄存器约束。如果这样做,counter的值必须先复制到寄存器,递减,然后对其内存更新。但这
样您会无法理解锁定和原子性的全部意图,这些明确显示了使用内存约束的必要性。
使用修饰寄存器
请考虑内存拷贝的基本实现。
asm("movl$count,%%ecx;
up:lodsl;
stosl;
loopup;"
:/*nooutput*/
:"S"(src),"D"(dst)/*input*/
:"%ecx","%eax");/*clobberedlist*/
当lodsl修改%eax时,lodsl和stosl指令隐含地使用它。%ecx寄存器明确装入count。但GCC在我们通知它以前是不知道这些
的,我们是通过将%eax和%ecx包括在修饰寄存器集中来通知GCC的。在完成这一步之前,GCC假设%eax和%ecx是自由
的,它可能决定将它们用作存储其它的数据。请注意,%esi和%edi由"asm"使用,它们不在修饰列表中。这是因为已经声明
"asm"将在输入操作数列表中使用它们。这里最低限度是,如果在"asm"内部使用寄存器(无论是明确还是隐含地),既不出现在
输入操作数列表中,也不出现在输出操作数列表中,必须将它列为修饰寄存器。
结束语
总的来说,内联汇编非常巨大,它提供的许多特性我们甚至在这里根本没有涉及到。但如果掌握了本文描述的基本材料,您应该可以
开始对自己的内联汇编进行编码了。
参考资料
您可以参阅本文在developerWorks全球站点上的英文原文.
请参考UsingandPortingtheGNUCompilerCollection(GCC)手册。
请参考GNUAssembler(GAS)手册。
仔细阅读Brennan'sGuidetoInlineAssembly。
关于作者
BharataB.Rao拥有印度Mysore大学的电子和通信工程的学士学位。他从1999年就开始为IBMGlobalServices,India工作
了。他是IBMLinux技术中心的成员之一,他在该中心中主要从事LinuxRAS(可靠性、可用性和适用性)的研究。他感兴趣的其
它领域包括操作系统本质和处理器体系结构。可以通过rbharata@in.ibm.com与他联系。
关闭[x]
developerWorks:登录
Linux中x86的内联汇编 http://www.ibm.com/developerworks/cn/linux/sdk/assemb...
第7页共9页 2011/12/2412:15
如果您还没有注册到IBM注册系统,我们为给您带来的不便表示道歉,并请您马上注册。现在注册。
IBMID:
忘记IBMID?
密码:
忘记密码?
更改您的密码
登录之后:
保持登录。
单击提交则表示您同意developerWorks的条款和条件。查看条款和条件
当您初次登录到developerWorks时,将会为您创建一份概要信息。您在 developerWorks 概要信息中选择公开的信息将公开显
示给其他人,但您可以随时修改这些信息的显示状态。您的姓名(除非选择隐藏)和昵称将和您在developerWorks发布的内容一
同显示。
所有提交的信息确保安全。
关闭[x]
请选择您的昵称:
当您初次登录到developerWorks时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在developerWorks发
布的内容显示在一起。
昵称长度在 3 至 31 个字符之间。您的昵称在developerWorks社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子
邮件地址。
昵称: (长度在3至31个字符之间)
单击提交则表示您同意developerWorks的条款和条件。查看条款和条件.
所有提交的信息确保安全。
平均分(7个评分)
1星
1星
2星
2星
3星
3星
4星
4星
5星
5星
添加评论:
Linux中x86的内联汇编 http://www.ibm.com/developerworks/cn/linux/sdk/assemb...
第8页共9页 2011/12/2412:15
打印此页面 分享此页面 关注 developerWorks
技术主题
AIX and UNIX
IBM i
Information
Management
Lotus
Rational
WebSphere
Cloud computing
Java technology
Linux
Open source
SOA and web services
Web development
XML
更多...
查找软件
IBM 产品
评估方式(下载,在线试
用,Beta 版,云)
行业
技术讲座
社区
群组
博客
Wiki
文件
使用条款
报告
软件系统测试报告下载sgs报告如何下载关于路面塌陷情况报告535n,sgs报告怎么下载竣工报告下载
滥用
更多...
关于 developerWorks
反馈意见
在线投稿
投稿指南
网站导航
请求转载内容
相关资源
ISV 资源 (英语)
IBM 教育学院教育培养
计划
项目进度计划表范例计划下载计划下载计划下载课程教学计划下载
IBM
解决
方案
气瓶 现场处置方案 .pdf气瓶 现场处置方案 .doc见习基地管理方案.doc关于群访事件的化解方案建筑工地扬尘治理专项方案下载
软件
支持门户
产品文档
红皮书 (英语)
隐私条约
浏览辅助
请登录或注册后发表评论。
注意:评论中不支持HTML语法
有新评论时提醒我剩余1000字符
快来添加第一条评论
Linux中x86的内联汇编 http://www.ibm.com/developerworks/cn/linux/sdk/assemb...
第9页共9页 2011/12/2412:15