Chinaunix首页 | 论坛 | 博客

分类: C/C++

2014-06-01 20:24:13

原文地址:c内联汇编 作者:dolinux

上一次听完李则良讲得c内嵌汇编,感觉讲得很好,下面是我的一些收获,拿出来和大家分享一下,其实主要还是对李则良所讲得内容的一个总结,如果有哪里说的不对还请大家给指点。
一:
      首先我们应该知道linux下采用的是AT&T的汇编语法格式,这和我们平时所学的Intel汇编是不同的。二者的主要区别在于:

1.指令操作数的赋值方向是不同的
   Intel:第一个是目的操作数,第二个是源操作数
   AT&T:第一个是源操作数,第二个是目的操作数
2.指令前缀
   AT&T:寄存器前边要加上%,立即数前要加上$
   Intel:没有这方面的要求
3.内存单元操作数
   Intel:基地址使用[]
   AT&T:  基地址使用()
      比如:intel中  mov  ax,[bx]
        AT&T中    movl (%eax),%ebx
4.操作码的后缀
     AT&T中操作码后面有一个后缀字母:“l” 32位,“w” 16位,
     “b” 8位
     Intel却使用了在操作数前面加dword ptr, word ptr, byte ptr
          的格式
   例如:mov al,bl (Intel)
     movb %bl %al (AT&T)
            
5.AT&T中跳转指令标号后的后缀 表示跳转方向,“f”表示向前,“b”表示向后

二:gcc提供的这种扩展语法(c内嵌汇编)的简单格式:__asm__("");如__asm__("nop");nop这条指令什么都不做只是让cpu空转一个指令执行周期,这里可以是多条指令,但是每条指令要用"\n\t"分开。如:

            __asm__("movl $1,%eax\n\t"
                     "movl $4,%ebx\n\t"
                     "int $ox80");
   完整的汇编格式:  __asm__(""
                        :输出部分
                        :输入部分
                        :改变的寄存器列表                     
            );
       这里第一部分是汇编指令,第二部分是汇编指令的运算结果要输出到哪些C操作数中,第三部分是指示汇编指令要从哪些C操作数中获取输入,第四部分是汇编指令中被修改过的寄存器列表,指示编译器哪些寄存器的值在执行这条__asm__语句时会改变。后边这三个部分是可选的,如果有就写上,没有就空着。

比如这样一个例子
#include
int main()
{
    int a=10,b,c=2;
    __asm__("imull %1,%2\n\t"
            "movl %2,%0"
            :"=r"(b)
            :"r"(a),"r"(c)
           
           );      /*在这里用到的是占位符*/
    printf("result:%d\n",b);
    return 0;
}
可以看到这个程序的汇编代码中对这条内嵌汇编的表达式是这样处理的:
imull %edx,%eax
movl %eax,%eax

当把程序改为:
#include
int main()
{
    int a=10,b,c=2;
    __asm__("imull %1,%2\n\t"
            "movl %2,%0"
            :"=r"(b)
            :"r"(a),"r"(c)
            :"%eax"          
           );      /*在这里用到的是占位符*/
    printf("result:%d\n",b);
    return 0;
}
可以看到这个程序的汇编代码中对这条内嵌汇编的表达式是这样处理的:
  imull %ecx,%edx 
  movl %edx,%edx
这是因为我们在寄存器列表部分加上了“%eax”

关于寄存器缩写的约定
     
a   %eax/%ax/%al
b   %ebx/%bx/%bl
c   %ecx/%cx/%cl
d   %edx/%dx/%dl
S   %esi/%si
D   %edi/%di
m   memory
“r”将变量放入通用寄存器,也就是eax ,ebx,ecx,edx,esi,edi中的一个
q 由编译器在a、b、c、d中任意选择
寄存器还可以使用数字形式的缩写,0~9 (%0-%9),所以会有最多10个项的限制。

四:占位符的使用:由于在使用一个寄存器时我们无法知道寄存器之前是否已经被使用了,这就需要我们将该寄存器中的内容先保存起来,
      为了克服这个不便gnu中使用占位符,让编译器自己判断该使用哪个寄存器。
格式为:__asm__("汇编代码"
               :"=r"(output)
        :"r"(in1),"r"(in2)
        );
这里使用%0占位out1,%1占位in1,%2占位in2


其他的就不说了,就说说在我自己尝试写程序时遇到的一些问题。
我这个程序就是想要实现a和c的相加,并将结果存入c中然后输出。

#include
#define DEBUG 0

#if DEBUG==0
  int a=10,c=2;
#endif
int main()
{
   #if DEBUG
      int a=10,c=2;
   #endif
    __asm__(
             "movl a,%eax\n\t"
             "movl c,%ebx\n\t"
             "addl %eax,%ebx\n\t"
             "movl %ebx,c\n\t"
           
           
           );   
    printf("result:%d\n",c);
    return 0;
}
这个程序可以正常执行,结果为12

但是当我尝试把DEBUG改为1时,编译却出现了错误:
testalong1.c:(.text+0x20): undefined reference to `a'
testalong1.c:(.text+0x26): undefined reference to `c'
testalong1.c:(.text+0x2e): undefined reference to `c'
collect2: ld 返回 1
make: *** [testalong1] 错误 1

不知这是为什么。根据虎才将的是因为全局变量和局部变量在内存中的存储位置不同造成的。不知大家有什么意见?


这个应该是链接的问题。全局变量是放到.data或者.
bss中的某个位置中的,在编译完后,
a,c的地址还未决定,所以这时的汇编代码还不完成,
需要进行链接,链接完后a,c的地址
也就决定了,整个程序也就可以执行了。

局部变量就不行,因为它们要么是放到寄存器中,
要么是放到堆栈中的。





当我使用下面这种方法时:
#include
int main()
{
    int a=10,c=2;
    __asm__("addl %1,%0\n\t"           
            :"=r"(c)
            :"r"(a)          
           );   
    printf("result:%d\n",c);
    return 0;
}
 程序执行结果却为20,查看汇编代码发现汇编代码是中是这样处理这条内嵌汇编表达式的:addl %eax,%eax
显然是直接把a和a相加。
查看汇编代码可以看到:
        movl    $10, -8(%ebp)
        movl    $2, -12(%ebp)
        movl    -8(%ebp), %eax
#APP
        addl %eax,%eax

#NO_APP
        movl    %eax, -12(%ebp)

这里先将a和c分别存入 -8(%ebp)所指单元和-12(%ebp)所指的单元,再将-8(%ebp)所指单元中的内容(也就是a的值)存入eax,
再执行 addl %eax,%eax,并且将最后结果存入了-12(%ebp)所指的单元
因为这里在输入输出部分我用的是“r”,所以编译器随机给a和c分配寄存器,结果a和c都分到了eax寄存器。

这时只需要将上边的汇编表达式改为
 __asm__(
            "addl %1,%2\n\t"
            "movl %2,%0"
            :"=r"(c)
            :"r"(a),"r"(c)
           
           );   
即可正常执行。结果为12

但是我发现改为下面这种形式:

 __asm__("addl %1,%0\n\t"           
            :"=r"(c)
            :"r"(a),"r"(c)          
           );   
   
也可以正常执行,不知道为什么?(按照我的理解这里应该没有给c分配寄存器)还请高手指点。

这里因为使用的是“r”这个选项,所以由编译器分配寄存器,这时编译器给输入的c和输出的c都分配的是eax这个寄存器,所以程序也是可以正常执行的。

另外一个程序可以证明这个问题:

#include
int main()
{
    int a=10,c=2,b=8;
    __asm__("addl %1,%0\n\t"           
            :"=r"(c)
            :"r"(a) ,"r"(c),"r"(b)         
           );   
    printf("result:%d\n",c);
    return 0;
}
这个程序执行结果是:18
查看汇编代码可以看到
     movl    $10, -8(%ebp)
     movl    $2, -12(%ebp)
     movl    $8, -16(%ebp)
     movl    -8(%ebp), %ecx
     movl    -12(%ebp), %edx
     movl    -16(%ebp), %eax
 #APP
     addl %ecx,%eax
 
 #NO_APP
编译器给b分配的是eax,同时输出时的c也分配的是eax


如果将程序改为:
#include
int main()
{
    int a=10,c=2,b=8;
    __asm__("addl %1,%0\n\t"           
            :"=r"(c)
            :"r"(a) ,"r"(b),"r"(c)         
           );   
    printf("result:%d\n",c);
    return 0;
}
程序结果为:12
汇编代码是:
      movl    $10, -8(%ebp)
      movl    $2, -12(%ebp)
      movl    $8, -16(%ebp)
      movl    -8(%ebp), %ecx
      movl    -16(%ebp), %edx
      movl    -12(%ebp), %eax
  #APP
      addl %ecx,%eax

这里看出编译器给输入的c和输出的c都分配的是eax寄存器所以程序结果为12
 
阅读(1739) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~