GNU的C编译器使用asm关键字:asm段格式如下:
asm ("assembly code");
一些汇编器使用制表符字符缩进指令以便区分和标签。GNU编译器不需要这样做,单为保持一致使用这样方式。
asm("mov $1, %eax\n\tmov $0, %ebx\n\tint $0x80");
这样格式有些混乱,下面这样方式书写:
asm("mov $1, %eax\n\t" "mov $0, %ebx\n\t" "int $0x80");
将asm放入到代码中例如:
#include <stdio.h> int main() { int a = 10; int b = 20, result; result = a * b; asm ("nop"); printf("The result is %d\n", result); return 0; }
C源代码中生成的汇编代码,其中#APP
和 #NO_APP
符号表示是asm生成的段落 内容。
基本内联汇编代码,可以使用程序定义的全局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修饰符的添加不希望优化这个代码段。
asm volatile("assembly code");
ANSI C规范把关键字asm用于其他用途,不能用于内联汇编。ANSI C想使用内联汇编,则使用__asm__
关键字替代asm
__asm__("assembly code");
基本asm有其局限性:
GNU汇编器提供asm段的扩展格式来帮助解决这些问题。asm扩展版的格式如下:
asm("assembly code" :ouput locations :input operands :changed registers);
有4个部分组成,使用冒号组成:
其中,输入和输出可以为空,只有最后一个冒号可以省略。
扩展格式,可以从寄存器和内存位置给输入
值和输出
值赋值。输入值和输出值列表的格式是:
"constraint" (variable)
其中:
约束 | 描述 |
---|---|
a | 使用%eax,%ax或者%al寄存器 |
b | 使用%ebx,%bx或者%bl寄存器 |
c | 使用%ecx,%cx或者%cl寄存器 |
d | 使用%edx,%dx或者%dl寄存器 |
S | 使用%esi或者%si寄存器 |
D | 使用%edi或者%di寄存器 |
r | 使用任何通用的寄存器 |
q | 使用%eax,%ebx,%ecx,或者%edx寄存器之一 |
A | 对于64位值使用%eax和%ebx寄存器 |
f | 使用浮点寄存器 |
t | 使用第一个(顶部的)浮点寄存器 |
u | 使用第二个浮点寄存器 |
m | 使用变量的内存位置 |
o | 使用偏移内存位置 |
V | 只是用直接内存位置 |
i | 使用立即整数值 |
n | 使用值已知的立即整数值 |
g | 使用任何可用的寄存器或者内存位置 |
除了约束之外,输出值包含约束修饰符,指示编译器如何处理输出值,可以使用如下来修饰输出值:
输出修饰符 | 描述 |
---|---|
+ | 可以读取和写入操作数 |
= | 只能写入操作数 |
% | 如果必要,操作数可以和下一个操作数切换 |
& | 在内联函数完成之前,可以删除或者重新使用操作数 |
例如:asm("assembly code" : "=a"(result) :"d"(data1), "c"(data2));
注意
:输入是C变量给寄存器,而输出是寄存器给C变量。
如果输入和输出都给寄存器,几乎和平常一样使用寄存器,需注意的是扩展asm中,在汇编代码中引用寄存器,必须使用两个百分号符号。
#include <stdio.h> int main() { int data1 = 10; int data2 = 20; int result; // edx为data1,ecx为data2 // eax赋值给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; }
MOVS指令输入值包含输出位置,volatile很重要,编译器因为没有输出值,认为这个asm没有必要而删除asm段:
#include <stdio.h> int main() { char input[30] = {"This is a test message.\n"}; char output[30]; int length = 25; asm volatile ( "cld\n\t" "rep movsb" : :"S"(input), "D"(output), "c"(length) ); printf("%s", output) return 0; }
很多输入和输出的情况,可以使用占位符(placeholder)。占位符是前面加上百分号的数字。按照内联汇编代码中列出的每个输入值和输出值在列表中的位置。每个值被赋予一个从零开始的数字。然后可以在汇编代码中使用占位符表示值。
asm("assembly code" :"=r"(result) :"r"(data1), "r"(data2));
其中:
注意
:占位符提供在内联汇编代码中利用寄存器和内存位置的方法。汇编代码中使用占位符只作为原始的数据:
imull %1, %2 movl %2, %0
示例如下:
#include <stdio.h> int main() { int data1 = 10; int data2 = 20; int result; asm ("imull %1, %2\n\t" "movl %2, %0" :"=r"(result) :"r"(data1),"r"(data2)); printf("The result is %d\n", result) return 0; }
gdb调试:info reg
整个asm被看作为一条语句,使用stepi来调试asm汇编
如果内联汇编代码中的输入值和输出值共享程序中相同的C变量,可以指定使用占位符作为约束值。
修改上面个汇编代码如下:
asm("imull %1, %0" :"=r"(data2) :"r"(data1),"0"(data2));
如果很多占位符,就会混乱。从GNU3.1开始允许声明替换名称作为占位符。格式如下:%[name]"constraint"(variable)
定义的值name成为内联汇编代码中的变量新的占位符标识符,下面例子:
asm("imull %[value1], %[value2]" :[value2]"=r"(data2) :[value1]"r"(data1),"0"(data2));
使用占位替换占位符名称的方式和使用普通的占位符相同。
改动寄存器列表:如果改动了不在输入和输出列表中的寄存器,要进行申明这些寄存器。
#include <stdio.h> int main() { int data1 = 10; int result; asm ("imull %1, %2\n\t" "movl %2, %0" :"=r"(result) :"r"(data1),"0"(result) :"%eax"); printf("The result is %d\n", result) return 0; }
让编译器正确避免使用eax寄存器,因为在内联汇编代码中声明了要使用它。
在改动寄存器列表中使用memory,通知编译器这个内存位置在内联汇编代码中被改动。
虽然内联汇编代码使用寄存器比较快,但也可以使用C变量的内存位置。约束m
用于引用输入和输出值中的内存位置。要求使用寄存器的汇编指令,仍然必须使用寄存器,所以不许不得不定义保存数据的中间寄存器。
#include <stdio.h> int main() { int dividend = 20; int divisor = 5; int result; asm("divb %2\n\t" "movl %%eax, %0" :"m"(result) :"a"(dividend), "m"(divisor)); printf("The result is %d\n", result); return 0; }
内联汇编代码中是使用标签有两个限制。
#include <stdio.h> int main() { int a= 10; int b= 20; int result; asm ("cmp %1, %2\n\t" "jge greater\n\t" "movl %1, %0\n\t" "jmp end\n" "greater:\n\t" "movl %2, %0\n" "end:" :"=r"(result) :"r"(a),"r"(b)); printf("The larger value is %d\n", result) return 0; }
使用局部标签方法:条件分支和无条件分支都允许指定一个数字加上方向标志作为标签,方向标志指出处理器应该向那个方向查找数字型标签。第一个遇到的标签会被采用。
#include <stdio.h> int main() { int a= 10; int b= 20; int result; 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)); printf("The larger value is %d\n", result) return 0; }
backward和forward
和对待C宏函数一样,可以声明包含内联汇编的宏函数。内联汇编代码必须使用扩展asm格式,便于输入正确的输入值和输出值。
定义一个内联汇编宏函数的一个例子:
#define GREATER(a, b, result) ({\ 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));})