有时候需要在C语言里使用汇编语言,或者是提高性能,或者是因为某些功能不能由系统调用实现。而在内核里,C语言里嵌入汇编是非常普遍的。如何在C语言里嵌入汇编语言呢?

  1. int main() 
  2.  
  3. __asm__ __volatile__ (  
  4. "movl %eax,%ebx\n\taddl %eax %ebx\n" 
  5. ); 
  6. return 0; 

使用__asm__宏就可以嵌入汇编,__volatile__指示不让gcc优化下面的汇编代码。

  1. .file "gccasm.c" 
  2. .text 
  3.   .globl main 
  4. .type main, @function 
  5. main:  
  6. pushl %ebp  
  7. movl %esp, %ebp 
  8.  
  9. #APP 
  10. # 3 "gccasm.c" 1 
  11. movl %eax,%ebx 
  12. addl %eax,%ebx 
  13. # 0 "" 2 
  14. #NO_APP 
  15.  
  16. movl $0, %eax 
  17. popl %ebp 
  18. ret 

红色的部分就是我们嵌入的代码。

上面的指令确实执行了我们嵌入的两条指令,可是这两条指令只是执行了。没给我们任何结果。如果我们想用汇编实现两个内存数的相加,怎么办?

  1. int main() 
  2.  
  3.  
  4. int a,b,sum; 
  5.  
  6. scanf("%d %d",&a,&b); 
  7.  
  8. __asm__ __volatile__ ( 
  9.  
  10. ?????????????? 
  11.  
  12. ); 
  13.  
  14. printf("sum=%d\n",sum); 
  15.  
  16. return 0; 
  17.  

中间要怎么填呢?

  1. __asm__ ( 
  2.  
  3. "addl %2,%1\n\tmovl %1,%0\n" 
  4.  
  5. :"=m"(sum) 
  6.  
  7. :"r"(a),"r"(b) 
  8.  
  9. ); 

上面的代码是什么意思呢?

实际上,嵌入汇编的标准格式是下面这个样子:

__asm__(

      汇编语句模板

      : 输出部分

      : 输入部分

      : 破坏描述部分);

第一个冒号前边是汇编语句模版,%0,%1,%2被称为占位符,%0是下面出现的第一个变量,%1是第二个,依次类推,一共可以有10个,%0-%9。下面的三个变量出现顺序是sum,a,b,因此%0,%1,%2分表代表sum,a,b。

那是不是这两句汇编就相当于addl b,a; movl a,sum?明显不是,因为一条指令是不能出现两个内存数的。

接下来就是输出部分和输入部分了。这两部分主要就是说使用了哪些变量,一次列出,比如这里就列出了sum,a,b,分别用%0,%1,%2表示,但是每个变量前边还有个标志,是告诉编译器怎么使用这些变量。

输出部分前边还要加个“=”。这些标志是什么意思呢?前边如果是r,那么这个变量要事先处理一下,先载入一个寄存器,然后再使用我们嵌入的汇编,因此前边要加一个指令,例如a和b,都是寄存器数,前边还需要添加movl a,%eax movl b, %ebx(r表示让编译器选寄存器,可以使用其他标志选特定的寄存器)。而sum是内存数,直接使用即可。因此这段代码实际上对应的汇编代码是:

movl a, %eax

movl b, %ebx

addl %ebx,%eax

movl %eax, sum

前边两条是编译器加上的。

gcc嵌入汇编_asm

输出部分就是说,在这段汇编执行完后,需要把结果保存到这个变量中。如果变量是内存数(=m),那就不用管了,但如果变量在汇编指令执行过程中使用了寄存器,那么需要将这个寄存器的值存入变量,例如使用了eax,那么这段汇编后需要加上一条指令 movl %eax, sum。

如果我们将sum也使用寄存器,"=r",那么会多加两条语句

movl sum,%ecx

movl a, %eax

movl b, %ebx

addl %ebx,%eax

movl %eax, %ecx

movl %ecx, sum

gcc嵌入汇编_asm_02

实际上只在最后加了一句movl %ebx, sum,前边那一句理论上是应该加的,但是由于并没有对它进行破坏,不需要加。

最后的破坏部分是指该嵌入汇编代码段可能会影响哪些寄存器,也可以是内存,告诉编译器要注意保护这些地方。由逗号格开的字符串组成,每个字符串描述一种情况,一般是寄存器名;除寄存器外还有 “memory”。例如:“%eax”,“%ebx”,“memory” 等。

全局变量

如果使用全局变量就不用这么麻烦了。

  1. int a,b,sum; 
  2. int main() 
  3.          scanf("%d,%d",&a,&b); 
  4.          asm("movl a,%eax\n"
  5.              "\taddl b,%eax\n"
  6.              "\tmovl %eax,sum\n"); 
  7.          printf("sum=%d\n",sum); 
  8.          return 0; 

注意:如果没有出现%0,%1这样的占位符,寄存器就用%eax,%ebx,如果出现了,就用%%eax,%%ebx。

 

限制字符

限制字符有很多种,有些是与特定体系结构相关,此处仅列出常用的限定字符和i386中可能用到的一些常用的限定符。它们的作用是指示编译器如何处理其后的 C 语言变量与指令操作数之间的关系。

 

分类

限定符

描述

通用寄存器

“a”

将输入变量放入eax

 

“b”

将输入变量放入ebx

 

“c”

将输入变量放入ecx

 

“d”

将输入变量放入edx

 

“s”

将输入变量放入esi

 

“d”

将输入变量放入edi

 

“q”

将输入变量放入eax,ebx,ecx,edx中的一个

 

“r”

将输入变量放入通用寄存器,即eax,ebx,ecx,edx,esi,edi之一

 

“A”

把eax和edx合成一个64 位的寄存器(use long longs)

内存

“m”

内存变量

 

“o”

操作数为内存变量,但其寻址方式是偏移量类型, 也即基址寻址

 

“V”

操作数为内存变量,但寻址方式不是偏移量类型

 

“ ”

操作数为内存变量,但寻址方式为自动增量

 

“p”

操作数是一个合法的内存地址(指针)

寄存器或内存

“g”

将输入变量放入eax,ebx,ecx,edx之一,或作为内存变量

 

“X”

操作数可以是任何类型

立即数

“I”

0-31之间的立即数(用于32位移位指令)

 

“J”

0-63之间的立即数(用于64位移位指令)

 

“N”

0-255之间的立即数(用于out指令)

 

“i”

立即数

 

“n”

立即数,有些系统不支持除字以外的立即数,则应使用“n”而非 “i”

匹配

“ 0 ”

表示用它限制的操作数与某个指定的操作数匹配

 

“1” ...

也即该操作数就是指定的那个操作数,例如“0”

 

“9”

去描述“%1”操作数,那么“%1”引用的其实就是“%0”操作数,注意作为限定符字母的0-9 与指令中的“%0”-“%9”的区别,前者描述操作数, 后者代表操作数。

 

&

该输出操作数不能使用过和输入操作数相同的寄存器

操作数类型

“=”

操作数在指令中是只写的(输出操作数)   

 

“+”

操作数在指令中是读写类型的(输入输出操作数)

浮点数

“f”

浮点寄存器

 

“t”

第一个浮点寄存器

 

“u”

第二个浮点寄存器

 

“G”

标准的80387浮点常数

 

%

该操作数可以和下一个操作数交换位置,例如addl的两个操作数可以交换顺序(当然两个操作数都不能是立即数)

 

#

部分注释,从该字符到其后的逗号之间所有字母被忽略

 

*

表示如果选用寄存器,则其后的字母被忽略