基本内联汇编
基本内联汇编是最简单的内联形式,其格式为: asm [volatile] ("assembly code")
-
asm:用于声明内联汇编表达式,这是内联汇编固定补分,不可少。asm和__asm__ 是一样的,是由gcc定义的宏:#define __asm__ asm 。 -
volatile:是可选项,它告诉gcc:“不要修改我写的汇编代码,请原样保留”。volatile 和__volatile__是一样的,是由gcc定义的宏:#define __volatile__ volatile -
“assembly code” 是咱们所写的汇编代码,它必须位于圆括号中,而且必须用双引号括起来。规则如下:
- 指令必须用双引号引起来,无论双引号中是一条指令或多条指令
- 一对双引号不能跨行,如果跨行需要在结尾用反斜杠
\ 转义。 - 指令之间用分号
; 换行符\n 或换行符加制表符\n \t 分隔。 - 当指令在多个双引号中时,除最后一个双引号外,其余双引号中的代码最后一定要有分隔符,如:
asm(“movl $9,%eax;””pushl %eax”) 正确
asm(“movl $9,%eax””pushl %eax”) 错误
扩展内联汇编
格式: asm [volatile] (“assembly code”:output : input : clobber/modify)
-
output:用来指定汇编代码的数据如何输出给C代码使用。内嵌的汇编指令运行结束后,如果想将运行结果存储到C变量中,就用此项指定输出的位置。output中每个操作数的格式为:“操作数修饰符约束名”(C 变量名) ,操作数修饰符通常为等号= 。多个操作数之间用逗号, 分隔。 -
input:用来指定C中数据如何输入给汇编使用。要向让汇编使用C中的变量作为参数,就要在此指定。input中每个操作数的格式为:“[操作数修饰符] 约束名”(C 变量名) ,操作数修饰符为可选项。多个操作数之间用逗号, 分隔。 -
clobber/modify:汇编代码执行后会破坏一些内存和寄存器资源,通过此项通知编译器,可能造成寄存器或内存数据破坏,这样gcc就知道哪些寄存器或内存需要提前保护前来。
- 格式:用双引号把寄存器名称引起来,多个寄存器之间用逗号
, 分隔,寄存器不用再加两个%% ,只要写名称即可,如:asm("movl %%eax, %0;movl %%eax,%%ebx":"=m" (ret_value)::"bx") - 如果我们的内联汇编代码修改了标志寄存器 eflags 中的标志位,同样需要在 clobber/modify 中用”cc”声明
- 如果我们修改了内存,我们需要在 clobber/modify 中”memory”声明。
- 如果我们在 output 中使用了内存约束,gcc 自然会得到哪块内存被修改。但如果被修改的内容并未在output 中,我们就需要用”memory”告诉 gcc 啦。
- 约束的作用:约束的作用是让C代码的操作数变成汇编代码能使用的操作数,所有的约束形式其实都是给汇编用的。约束是C语言中的操作数与汇编语言中的操作数之间的映射,他告诉gcc,同一个操作数在两种环境下如何变换身份,如何对接沟通。编译过程中C代码是要先变成汇编代码的,内联汇编中的约束就相当于gcc让咱们指定C中数据的编译形式。
以下是各种约束的解释:
-
寄存器约束:寄存器约束就是要求gcc使用哪个寄存器,将input或output变量约束在某个寄存器中,常见的寄存器约束有: a:表示寄存器 eax/ax/al b:表示寄存器 ebx/bx/bl c:表示寄存器 ecx/cx/cl d:表示寄存器 edx/dx/dl D:表示寄存器 edi/di S:表示寄存器 esi/si q:表示任意这 4 个通用寄存器之一:eax/ebx/ecx/edx r:表示任意这 6 个通用寄存器之一:eax/ebx/ecx/edx/esi/edi g:表示可以存放到任意地点(寄存器和内存)。相当于除了同 q 一样外,还可以让 gcc 安排在内存中 A:把 eax 和 edx 组合成 64 位整数 f:表示浮点寄存器 t:表示第 1 个浮点寄存器 u:表示第 2 个浮点寄存器 -
内存约束:内存约束是要求gcc直接将位于input和output中的C变量内存地址作为内联汇编代码的操作数,不需要寄存器做中转,直接进行内存读写,也就是汇编代码的操作数是C变量的指针。 m:表示操作数可以使用任意一种内存形式 o:操作数为内存变量,但访问它是通过偏移量的形式访问,即包括offset_address的格式。 -
立即数约束:立即数即常数,此约束要求gcc在传值的时候不通过内存和寄存器,直接作为立即数传给汇编代码。由于立即数不是变量,只能作为右值,所以只能放在input中。 i:表示操作数为整数立即数 F:表示操作数为浮点数立即数 I:表示操作数为 0~31 之间的立即数 J:表示操作数为 0~63 之间的立即数 N:表示操作数为 0~255 之间的立即数 O:表示操作数为 0~32 之间的立即数 X:表示操作数为任何类型立即数 -
通用约束: 0~9:此约束只用在 input 部分,但表示可与 output 和 input 中第 n 个操作数用相同的寄存器或内存。 -
占位符:为了方便对操作数的引用,扩展内联汇编提供了占位符,他的作用是代表约束指定的操作数(寄存器、内存、立即数),我们更多的是在内联汇编中使用占位符来引用操作数。占位符分为序号占位符和名称占位符:
-
序号占位符:序号占位符是对在output和input中的操作数,按照它们从左到右的次序从0开始编号,一直到9,也就是说最多支持10个序号占位符,引用它的格式是%0~9。占位符所表示的操作数默认情况下为 32 位数据,在%和序号之间插入字符’h’来表示操作数为ah(第 8~15 位),或者插入字符’b’来表示操作数为 al(第 0~7 位)。 h –输出寄存器高位部分中的那一字节对应的寄存器名称,如 ah、bh、ch、dh。 b –输出寄存器中低部分 1 字节对应的名称,如 al、bl、cl、dl。 w –输出寄存器中大小为 2 个字节对应的部分,如 ax、bx、cx、dx。 k –输出寄存器的四字节部分,如 eax、ebx、ecx、edx。 如: asm("addl %%ebx, %%eax":"=a"(out_sum):"a"(in_a),"b"(in_b)); 等价于 asm("addl %2, %1":"=a"(out_sum):"a"(in_a),"b"(in_b)); 其中: “=a”(out_sum)序号为 0,%0 对应的是 eax。 “a”(in_a)序号为 1,%1 对应的是 eax。 “b”(in_b)序号为 2,%2 对应的是 ebx。 -
名称占位符:名称占位符与序号占位符不同,序号占位符靠本身出现在output和input中的位置就能被编译器识别出来。而名称占位符需要在output和input中把操作数显式地起个名字,它用这样的格式来标识操作数:[名称]”约束名”(C 变量) 。这样,该约束对应的汇编操作数便有了名字,在 assembly code 中引用操作数时,采用%[名称]的形式就可以了。 -
由于扩展内联汇编中的占位符要有前缀%,为了区别占位符和寄存器,只好在寄存器前用两个%做前缀啦,这就是本节前面解释在扩展内联汇编中寄存器前面要有两个%做前缀的原因。 -
在约束中还有操作数类型修饰符,用来修饰所约束的操作数:内存、寄存器,分别在ouput 和 input中有以下几种。 在output中: =:表示操作数是只写,相当于为 output 括号中的 C 变量赋值,如=a(c_var),此修饰符相当于 c_var=eax。 +:表示操作数是可读写的,告诉 gcc 所约束的寄存器或内存先被读入,再被写入。 &:表示此 output 中的操作数要独占所约束(分配)的寄存器,只供 output 使用,任何 input 中所分配的寄存器不能与此相同。注意,当表达式中有多个修饰符时,&要与约束名挨着,不能分隔。 在input中: %:input 中的输入可以和下一个 input 操作数互换
|