Inline Assembly 内联汇编
A. asm关键字
要声明和使用内联汇编函数,应当使用关键字asm。内联汇编能够操作C语言变量并使其输出可见,因此,asm关键字可以看做汇编指令和包含它的C程序之间的接口。
B. AT&T汇编语法
GCC内联函数使用AT&T汇编语法,这是我们常见汇编代码的语法格式,有以下几个特点:
1. 操作数方向:第一个操作数是源,第二个操作数是目标。
2. 寄存器命名:寄存器名称的前缀是%e,例如要使用eax,就写%eax。
3. 立即数:C语言程序中的常量(常数),在汇编语言中是一个以$开头的操作数。
4. 操作数大小:AT&T语法中,内存操作数的大小由操作指令的最后一个字符决定。以“b”,“w”和“l”为后缀的操作指令分别指定字节(8位),字(16位),字长(32位)的寄存器引用。
5. 内存操作数:AT&T语法中,间接内存引用通常的形式为:disp(base,index,scale)。
C. 基本内联格式
- 单行内联函数:asm(“内联指令”)
asm("movl %ecx %eax");
_asm_("movl %ecx %eax");
说明:asm和asm都是合法形式:当asm与程序中某些变量重名时使用后者。
- 多行内联函数:asm(“第一条内联指令\n\t”“第二条内联指令\n\t”“最后一条内联指令”)
asm("movl %eax, %ebx\n\t"
"movl $56, %esi\n\t"
"movl %ecx, $label(%edx,%ebx,$4)\n\t"
"movb %ah, (%ebx)");
D. 扩展内联格式
基本内联汇编中只有指令,扩展汇编中可以指定操作数。
asm ( assembler template
: output operands /* optional */
: input operands /* optional */
: list of clobbered registers /* optional */
);
int a=10, b;
asm ("movl %1, %%eax;
movl %%eax, %0;"
:"=r"(b) /* output */
:"r"(a) /* input */
:"%eax" /* clobbered register */
);
这段代码得作用是使’b’的值等于’a’的值,其中要说明几点:
- “b”是输出操作数,由%0表示,“a”表示由%1表示的输入操作数。
- “r”是操作数的一个约束。稍后我们会看到约束条件。目前,“r”表示GCC使用任何寄存器来存储操作数。输出操作数约束应该有一个约束修饰符“=”。这个修饰符说,它是输出操作数,是只写的。
- 在寄存器名称前面有两个%的前缀。这有助于GCC区分操作数和寄存器。操作数只有一个%作为前缀。
- 第三个冒号后的破折号寄存器%eax告诉GCC%eax的值在“asm”内被修改,所以GCC不会使用这个寄存器来存储任何其他的值。
- 当“asm”执行完成时,“b”将反映更新值,因为它被指定为输出操作数。换句话说,“asm”内部对“b”所做的改变应该反映在“asm”之外。
E. 操作数
操作数的一般形式:
“约束”(C表达式)
- 约束决定操作数的寻址模式和使用的寄存器
- 对于输出操作数,引号内还有一个约束修饰符
- 在汇编程序模板中,每个操作数都由数字引用,编号规则如下:如果总共有n个操作数(包括输入和输出),则第一个输出操作数编号为0,按递增顺序继续,最后一个输入操作数编号为n-1。
- 输出操作数表达式必须是左值。输入操作数不像这样受限制。
F. 常用约束和约束修饰符
- 寄存器操作数约束(r)
r | Registers |
---|---|
a | %eax, %ax, %al |
b | %ebx, %bx, %bl |
c | %ecx, %cx, %cl |
d | %edx, %dx, %dl |
S | %esi, %si |
D | %edi, %di |
- 内存操作数约束(m)
如果C变量需要在“asm”内部更新,并且你不想用寄存器来保存它的值,那么内存约束可以被最有效的使用。例如,idtr的值存储在内存位
asm("sidt %0\n" : :"m"(loc));
- 匹配约束
这种约束在以下两种情况下使用:
- 从变量读取输入或者变量被修改并且修改被写回到相同变量。
- 不需要单独的输入和输出操作数实例。
例:
asm ("incl %0" :"=a"(var):"0"(var));
- 约束修饰符
约束修饰符用于更精准地控制约束地影响,常用的约束修饰符有两种:
- “=”:表示该操作数对于该指令是只写的; 之前的值被丢弃并被输出数据替代。
- “&”:表示这个操作数是一个早期的操作数,它在指令完成之前使用输入操作数被修改。因此,该操作数可能不在用作输入操作数或作为任何存储器地址的一部分的寄存器中。如果输入操作数仅用作输入,则在早期结果写入之前可以将输入操作数绑定到早期操作数。