GCC内联汇编
1. 基本内联语法:
GCC用关键字asm
表示一段用汇编语言写的源代码。形式如下:
asm ("assemble language");
说明:
- 汇编指令必须用双引号扩起来
- 如果有多条汇编指令,必须用换行符隔开
\n
,每条语句之前最好加上\t
,为了区别汇编语言中的标号label
。
编译器实际上把asm
括号中双引号引起来的语句逐字的插入到编译后产生的汇编文件中.s
,并进行了相应的替换操作。
可以这样:
asm ("movl $1,%eax\n\tmovl $0,%ebx\n\tint $0x80");
看起来麻烦,可以分行,分开成多个双引号:
asm ("movl $1,%eax\n\t"
"movl $0,%ebx\n\t"
"int $0x80");
可以使用C程序中的全局变量:
#include <stdio.h>
int a = 10;
int b = 20;
int result;
int main(){
asm("pusha\n\t"
"movl a, %eax\n\t"
"movl b, %ebx\n\t"
"imull %ebx, %eax\n\t"
"movl %eax, result\n\t"
"popa");
printf("the answer is %d\n",result);
return 0;
}
有时候编译器会对变易产生的汇编代码进行优化,可能导致产生的效果与我们预想的不一样。可以在asm
之后使用volatile
关键字进行说明,让编译器不进行优化。
Note:如果使用ANSI C标准,asm
和volatile
都要写成__asm__
__volatile__
。
2. 扩展的ASM
基本形式有很多限制:输入输出只能使用C语言中的全局变量,而且不能随意地改变寄存器的值。(前面的例子中开头和结尾使用pusha
和popa
来保存寄存器)
扩展汇编形式:
asm (
"assembly code"
: output locations
: input operands
: changed registers);
这种形式有四个部分,用冒号隔开:
- 汇编代码
assembly code
:内联汇编代码,语法同基本asm格式。变量用占位符表示。 - 输出位置
output locations
:汇编代码中输出值所在的寄存器和内存单元列表。(执行汇编代码之后) - 输入操作符
input operands
:汇编代码中输入值所在的寄存器和内存单元的列表。(执行汇编代码前处理) - 改变的寄存器
changed registers
:内联汇编代码中改变的寄存器(用于提醒编译器对这些寄存器进行保护操作)。
操作符
在基本asm格式中,汇编语言中输入输出值可以直接使用C语言中的全局变量,但是在扩展asm格式中,格式不太一样:
"constraint"(variable)
variable
是一个C语言变量(全局变量或局部变量都可以),constraint
定义了从哪里取这个变量(对于输入变量来说)或往哪里存放(对输出变量)。constraint
可能的取值如下表:
constraint | description |
---|---|
a | use the %eax,%ax or %al regiters |
b | use the %ebx,%bx or %bl registers |
c | use the %ecx,%cx or %cl registers |
d | use the %edx,%dx or %dl registers |
S | use the %esi or %si registers |
D | use the %edi or %di registers |
r | use any available general-purpose register |
q | use either the %eax,%ebx,%ecx or %edx register |
A | use the %eax and the %edx regiters for a 64-bit value |
f | use a floating-point register |
t | use the first(top) floating-point register |
u | use the second floating-point register |
m | use the variable’s memory location |
o | use an offset memory location |
V | use a direct memory location |
i | use an immediate integer value |
n | use an immediate integer value with a known value |
g | use any register or memory location available |
除了这些限制,输出值还包含一个限制修饰符,它指明编译器如何处理输出值。
output modifer | description |
---|---|
+ | The operand can be both read from and written to |
= | The operand can only be written to |
% | The operand can be switched with the next operand if necessary |
& | The operand can be deleted and reused before the inline functions c |
举例:
asm ("assembly code" : "=a"(result) : "d"(data1),"c"(data2));
这个例子中C变量data1
放在%edx
,data2
放在%ecx
。输出结果会被放到%eax
,然后送到result
变量。
#include <stdio.h>
int main(){
int data1 = 10;
int data2 = 20;
int result;
asm ("imull %%edx, %%ecx\n\t"
"movl %%ecx, %%eax"
: "=a"(result)
: "d"(data1), "c"(data2)
);
printf("The result is %d\n",result);
return 0;
}
asm volatile ("cld\n\t"
"rep movsb"
:
:"S"(input),"D"(output),"c"(length));
由于没有指定输出规则,编译器会认为这段汇编是不必要的,因此要加上volatile
。
占位符
汇编语言部分assembly language
可以使用占位符placeholder
用来引用输出输入变量。格式为%0 %1 ...
用来表示输出输入列表中的变量。
asm ("imull %1, %2\n\t"
"movl %2, %0\n\t"
:"=r"(result)
:"r"(data1),"r"(data2)
);
%0
代表存放result
的寄存器;%1
代表存放data1
的寄存器;%2
代表存放data2
的寄存器。
asm ("imull %1,%0"
: "=r"(data2)
: "r"(data1),"0"(data2));
"0"表示让编译器使用第一个命名的寄存器。
int dividend = 20 ;
int divisor = 5;
int result ;
asm ("divb %2\n\t"
"movl %%eax, %0"
: "=m"(result)
: "a"(dividend),"m"(divisor));
内联汇编可以使用标签,用来进行跳转。数字可以作为局部标签。
asm ("cmp %1, %2\n\t"
"jge 0f\n\t"
"movl %1, %0\n\t"
"jmp 1f\n"
"0:\n\t"
"movl %2, %0\n"
"1:"
:"=r"(result)
:"r:(a),"r"(b));
0:
1:
都是label,jmp 0f
中f
表示forward,如果向后跳转使用b
即backward。