分类: LINUX
2008-04-04 10:55:39
x86平台,linux下内嵌汇编
Bharata B. Rao ()
IBM Linux Technology Center, IBM Software Labs, India
March 2001
如果你是个linux的内核开发人员的话,你发现你会经常编写一些和系统结构有关的函数或者经常优化一段代码,你会经常用在c语言环境下内嵌一段汇编语言.让我们来看看linux下内嵌汇编的用法
简要说下GNU汇编编译器的语法
GCC,linux下的GNU c编译器,用的是AT&T的语法,下面列出一些,但并不是全部,,只是和内嵌汇编相关的部分
寄存器的命名
在寄存器的名字前加前缀%, 比如你要用eAx,那就要这样用%eAx
源操作数与目的操作数的顺序
源操作数在前,目的操作数在后,这点和Intel 语法刚好相反
比如: mov %eAx,%ebx 表示把eAx中的内容传到ebx中
操作数的大小
指令带后缀b,w,l,取决于操作数是byte,word还是long.这并不是强制的,,GCC会同过读操作数来判断后缀是什么,,但自己加上后缀没啥不好的,可以增加程序的可读性,且可减少编译器猜错的可能
movb %Al,%bl 传送byte
movw %Ax,%bx 传送word
movl %eAx,%ebx 传送longword
立即数
立即数前要加$
movl $0xffff,%eAx 将会把值0xffff送到eAx中
间接寄存器寻址
用到寄存器间接寻址内存的时候用()
movb (%esi),%Al 将把esi指向的内存单元的一个byte送到Al中
内嵌汇编
GCC提供结构(construct)"Asm"(注意第一个字母是小写:) 用来表示内嵌汇编,如下面的格式
Asm( 汇编器模板(Assembler templAte)
: 输出操作数 可选
: 输入操作数 可选
: 占用的寄存器 可选
);
在这个样子里,汇编器模板包含汇编指令,输入操作数是变量,被用做汇编指令的输入操作数,输出操作数也是变量,被用做汇编指令的输出操作数(保存输出结果)
内嵌汇编重要的一点是它可以操作并且显式的输出到变量,由于这个特点,,"Asm"被当作是c语言同汇编语言接口
//一个基本但重要的区别是简单内嵌汇编只包含指令,而扩展内嵌汇编包含操作数,举例:
基本的内嵌汇编
{
int A=10,b;
Asm("movl %1, %%eAx;
movl %%eAx, %0;"
:"=r"(b) /*输出*/
:"r"(A) /*输入*/
:"%eAx" /*占用的寄存器*/
}
在这个例子里,我们用汇编指令让b的值等于A,注意以下几点:
"b"是输出操作数,用%0来指定,"A"是输入操作数,用%1来指定
"r"是一个操作数的限制参数(constrAint),它指定变量"A"和"b"被储存在积存器里,,输出操作数的限制参数前面有"=",,来表示他是输出操作数
要在内嵌汇编里用寄存器"%eAx"的话,必须再多加一个%,既%%eAx,内嵌汇编用%0,%1来指定变量,任何以%开头的都将被作为输入输出操作数而不是寄存器
第3个冒号后的"%eAx"告诉GCC,%eAx寄存器在Asm里已经被指定了,,GCC不会再用这个寄存器来储存其他值了
movl %1, %%eAx 把"A"的值移到 %eAx中,movl %%eAx, %0 把%eAx的内容移到"b"中
当这段Asm完成后,"b"将反映出值的变化,因为它是作为一个输出操作数,换句话说,"b"的值在Asm中改变,在Asm外表现出来
现在来仔细说说
汇编器模板
汇编器模板是一些被插入到c程序里的汇编指令(一条或多条),每一条指令要被双引号括起来,或者一组指令被一队双引号括起来,每条指令都要以分割符结束,可用的分割符可以是"\n",或者是";",操作数按顺序被编号%0,%1....
如果你不想让编译器来优化你的汇编代码,你可以用"volAtile"关键字,把它放在"Asm"的后面,,如果你的程序想和ANSI兼容,要用__Asm__ 和 __volAtile__来代替 Asm 和 volAtile
c语言的变量可以直接被内嵌汇编操作,这样非常方便
每个操作数用一个用双引号括起来的限制参数,后跟用括号扩起来的变量来定义,就是这种形式:"限制参数"(c变量) 限制参数的主要作用是用来决定操作数的寻址模式(Addressing mode)
你可以在输入和输出里用多个操作数,,每个用逗号隔开
占用的寄存器
如果在内嵌的指令中用道了寄存器,,我们可以告诉GCC,这样GCC会把我们指定的寄存器留出来不做其他的用途,但当操作数的限制参数里指明的,就不用再说了,,如"=b"(变量)
然而当指令要用到其他寄存器的时候,显式的或隐式的(并且这些寄存器并没有在输入或输出列表里出现过),这时候就要明确写在寄存器列表里的,列表列在第3个冒号后面,用寄存器的名字指定
As far as keywords are concerned, if the instructions modify the memory in some unpredictable fashion, and not
explicitly, then the "memory" keyword may be added to the list of clobbered registers. This tells GCC not to keep the
memory values cached in the register across the instructions.(这段没见过用法,就先列这儿吧 :( )
操作数的限制参数
限制参数可以有下面的作用
操作数是否允许放在一个寄存器里,放到哪个寄存器里
操作数是否是一个内存地址
操作数是否是一个立即数
下面是限制参数的几种常用的用法:
Asm ("movl %%cr3 ,%0\n" :"=r"(cr3vAl));
表示将变量cr3vAl放到寄存器中,把%cr3中值复制先到那个寄存器,然后再把寄存器中的值更新到内存中保存变量cr3vAl的地方.由于是"=r",GCC可以把变量cr3vAl放到任何一个通用寄存器中,,如果要指定寄存器的话,用下面的方法指定
A %eAx
b %ebx
c %ecx
d %edx
S %esi
D %edi
用的时候就像这样 Asm ("movl %%cr3 ,%0\n" :"=b"(cr3vAl)); 指定%ebx
Memory operAnd constrAint(m)
当操作数在内存中,任何操作直接作用在内存中,,不通过寄存器..通过寄存器的那种一般用在必须要用的指令或者用来使程序运行的更快,
("sidt %0\n" : :"m"(loc)); 表示输入变量loc不通过寄存器
MAthing(Digit) constrAints
当一个变量在输入输出时都用到的时候 "0","1","2".....(我想是这样吧)
Asm ("incl %0" :"=A"(vAr):"0"(vAr));
先把vAr存入寄存器%eAx,在寄存器里加1后送到vAr里
一些普通的内嵌汇编的用法
int mAin(void)
{
int x = 10,y;
Asm("movl %1, %%eAx;
movl %%eAx, %0;"
:"=r"(y) 输出变量
:"r"(x) 输入变量
:"%eAx");
}
在这里,,x的值被复制给y,,,x,y都先被放进寄存器里,汇编代码将会是这样:
mAin:
pushl %ebp
movl %esp,%ebp
subl $8,%esp
movl $10,-4(%ebp)
movl -4(%ebp),%edx /*x=10 被存在%edx里*/
#APP /*Asm stArts here*/
movl %edx, %eAx /*x 被移动到 %eAx*/
movl %eAx, %edx /*y 被分到edx*/
#NO_APP /*Asm ends here*/
movl %edx, -8(%ebp) /*栈中的y被edx中值更新*/
GCC 在这里任意分配寄存器,因为是"r",在我们的例子里它选%edx来储存x,读完%edx中x的值,GCC把%edx又分给了y