有时候需要在C语言里使用汇编语言,或者是提高性能,或者是因为某些功能不能由系统调用实现。而在内核里,C语言里嵌入汇编是非常普遍的。如何在C语言里嵌入汇编语言呢?
- int main()
- {
- __asm__ __volatile__ (
- "movl %eax,%ebx\n\taddl %eax %ebx\n"
- );
- return 0;
- }
使用__asm__宏就可以嵌入汇编,__volatile__指示不让gcc优化下面的汇编代码。
- .file "gccasm.c"
- .text
- .globl main
- .type main, @function
- main:
- pushl %ebp
- movl %esp, %ebp
- #APP
- # 3 "gccasm.c" 1
- movl %eax,%ebx
- addl %eax,%ebx
- # 0 "" 2
- #NO_APP
- movl $0, %eax
- popl %ebp
- ret
红色的部分就是我们嵌入的代码。
上面的指令确实执行了我们嵌入的两条指令,可是这两条指令只是执行了。没给我们任何结果。如果我们想用汇编实现两个内存数的相加,怎么办?
- int main()
- {
- int a,b,sum;
- scanf("%d %d",&a,&b);
- __asm__ __volatile__ (
- ??????????????
- );
- printf("sum=%d\n",sum);
- return 0;
- }
中间要怎么填呢?
- __asm__ (
- "addl %2,%1\n\tmovl %1,%0\n"
- :"=m"(sum)
- :"r"(a),"r"(b)
- );
上面的代码是什么意思呢?
实际上,嵌入汇编的标准格式是下面这个样子:
__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
前边两条是编译器加上的。
输出部分就是说,在这段汇编执行完后,需要把结果保存到这个变量中。如果变量是内存数(=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
实际上只在最后加了一句movl %ebx, sum,前边那一句理论上是应该加的,但是由于并没有对它进行破坏,不需要加。
最后的破坏部分是指该嵌入汇编代码段可能会影响哪些寄存器,也可以是内存,告诉编译器要注意保护这些地方。由逗号格开的字符串组成,每个字符串描述一种情况,一般是寄存器名;除寄存器外还有 “memory”。例如:“%eax”,“%ebx”,“memory” 等。
全局变量
如果使用全局变量就不用这么麻烦了。
- int a,b,sum;
- int main()
- {
- scanf("%d,%d",&a,&b);
- asm("movl a,%eax\n"\
- "\taddl b,%eax\n"\
- "\tmovl %eax,sum\n");
- printf("sum=%d\n",sum);
- 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的两个操作数可以交换顺序(当然两个操作数都不能是立即数) |
|
# |
部分注释,从该字符到其后的逗号之间所有字母被忽略 |
|
* |
表示如果选用寄存器,则其后的字母被忽略 |