GCC 扩展内联汇编简介

基本内联汇编

基本内联汇编格式比较直观,可以直接这样写:

asm("assembly code");

例如:

asm("movl %ecx, %eax"); /* 把 ecx 内容移动到 eax */ 
__asm__("movb %bh , (%eax)"); /* 把bh中一个字节的内容移动到eax指向的内存 */

扩展内联汇编

前面讨论的基本内联汇编只涉及到嵌入汇编指令,而在扩展形式中,我们还可以指定操作数,并且可以选择输入输出寄存器,以及指明要修改的寄存器列表。对于要访问的寄存器,并不一定要显式指明,也可以留给GCC自己去选择,这可能让GCC更好去优化代码。扩展内联汇编格式如下:

asm 汇编限定符 (汇编模板
	:"限定符1"(输出操作数1),"限定符2"(输出操作数2).......	//可选的
	:"限定符1"(输入操作数1),"限定符2"(输入操作数2).......	//可选的
	:已改写寄存器列表	 					//可选的
);

其中汇编模板为汇编指令部分。括号内的操作数都是C语言表达式中常量字符串。不同部分之间使用冒号分隔。相同部分语句中的每个小部分用逗号分隔。最多可以指定10个操作数,不过可能有的计算机平台有额外的文档说明可以使用超过10个操作数。

  • 第一个关键字asm,有时为了避免命名冲突,gcc也支持写作__asm__
  • 汇编限定符,例如限定符可以是__volatile__

汇编模板

#include <stdio.h>

void foo(void)
{
	int foo = 0; bar = 10;

	__asm__ __volatile__ (
		"movl %1, %%eax;\n"
		"movl %%eax, %0;\n"
		:"=r"(foo)
		:"r"(bar)
		:"%eax");

        printf("foo = bar; foo = %d\n", foo);
}

截取自上面冒号之前的两句就是汇编模板,他们可以一行写一句,甚至加上换行符\n,\r。非常类似printf的第一个参数,格式化字符串部分。

"movl %1, %%eax;\n"
"movl %%eax, %0;\n"

操作数

"constraint" (C expression) //"=r"(result)

接下来两个冒号之后的分别是输出/入操作数和它的约束属性。

"约束属性"(操作数)
"=r"(foo)

多个操作数的描述可以以,逗号隔开

"约束属性1"(操作数1),"约束属性2"(操作数2),"约束属性3"(操作数3)............

这些操作数最终会被汇编模板里面的%0, %1, %2, %3依次引用,例如上列中,汇编模板最终变为:

movl bar, %eax;
movl %eax, foo;

是不是和printf的参数列表特像?

输出操作数

  • 当内联汇编被执行完后,作为输出操作数的变量foo,值发生了改变。在__asm__内联汇编内部对操作数foo的修改,反应在了外部的C语言中。
movl bar, %eax;	@ bar(10)赋值给eax
movl %eax, foo; @ eax(10)赋值给foo

经过上面两句,foo的值由原来的0变为了10。输出操作数必须是左值,因为它最终要被内部的内联汇编赋值的。

输入操作数

  • 同理,输入操作数就是由外部的C表达式或者变量对内部内联汇编进行一个数值的传入。
movl bar, %eax;	@ bar(10)赋值给eax
movl %eax, foo; @ eax(10)赋值给foo

bar作为输入操作数,将值10带入了内联汇编内部。

已改写寄存器

  • 这个寄存器的内容在内联汇编中已经被修改。所以gcc不会用这个寄存器来存储其它值。例如上列里面的eax寄存器。

一些指令会修改一些寄存器的值。我们必须在受影响列表中列出那些寄存器,即内联汇编第三个:后的区域。这用于指示gcc我们将使用并修改它们。所以gcc将不会假设它加载到这些寄存器中的值是合法的。我们不应该列出输入和输出寄存器,因为gcc知道内联汇编使用它们(因为它们被明确指定为限制符(constraints))。如果指令使用了任何其他寄存器,显式或隐式的(并且这些寄存器没有出现在输入和输出列表上),那么那些寄存器必须在受影响列表中指定。

约束(constraints)

你可能已经感到我们之前经常提到的constraint是个很重要的内容了。不过之前我们并没有过多的讨论。constraint中可以指明一个操作数是否在寄存器中,在哪个寄存器中;可以指明操作数是否是内存引用,如何寻址;可以说明操作数是否是立即数常量,和其可能是的值(或值范围)。

常用constraints

虽然constraints有很多,但常用的并不多。下面我们就来看看这些常用的constraints。

a. 寄存器操作数限制符("r")

如果操作数指定了这个constraints,操作数将被存储在通用寄存器中。看下面的例子:

__asm__( "movl %%eax, %0" : "=r" (myval));

上面变量myval会被被保存在一个由GCC自己选择的寄存器中,eax中的值被拷贝到这个寄存器中去,并且在内存中的myval的值也会按这个寄存器值被更新。当constraints ”r” 被指定时,GCC可能会在任何一个可用的通用寄存器中保存这个值。当然,你也可以指定具体使用那个寄存器,用下表所列出的constraints:

限制符 寄存器
r 随机的通用寄存器
a %eax, %ax, %al
b %ebx, %bx, %bl
c %ecx, %cx, %cl
d %edx, %dx, %dl
S %esi, %si
D %edi, %di

b. 内存操作数限制符("m")

当操作数是在内存中时,任何在它上的操作将直接在内存位置进行,而寄存器限制符,则优先存于寄存器而后修改再写回内存。但寄存器限制符通常只在指令必需或者明显提升性能时使用。当C变量需在asm中修改且无需寄存器保持其值时,内存限制符可最大化性能。如,将idtr的值存储于loc的内存位置中:

__asm__("sidt %0\n" : :"m"(loc));

c. 匹配(数字)限制符

有时,一个单独变量既是输入也是输出操作符,这时可使用匹配限制符。

asm ("incl %0" :"=a"(var):"0"(var));

我们在操作数一节看到了类似的例子,在这个例子中寄存器%eax既是输入也是输出变量。var输入读入%eax并更新到%eax最后在自增后存入var。这里的"0"指定了和输出变量一样的第0个限制符。也就是说,它指定了var的输出过程应该只存于%eax中。这类限制符可用于:

  • 输入输出是统一变量,或变量被修改并被写会同一变量时。
  • 将输入和输出操作符分开是不必要的时候。

使用匹配限制符最重要的效果是使可用寄存器的使用更有效。

一些其他的限制符有:

  • m: 接受内存操作数,机器支持任意的地址。
  • o: 接受内存操作数,只接受偏移地址(offsettable)。即对某个合法地址添加一个微小的偏移量。
  • V: 非偏移内存操作数。换句话说,任何符合"m"但不符合"o"限制符的地址。
  • i: 立即整型操作数,允许在编译期(assembly-time)可知常量符号。
  • n: 立即整型操作数,允许已知数字值。许多系统不支持小于16-bit的(word wide)编译期(assembly-time)常量作为操作数。这些操作数应该使用n而不是i。
  • g: 任何寄存器,内存或立即整型操作数都可用,要求寄存器不是常规寄存器(general registers)。
  • cc:如果我们的指令可以修改条件码寄存器(the condition code register),我们必须增加cc到受影响寄存器列表。
  • memory:如果我们的指令用一个不可预期的方法(fashion)修改了内存,添加memory到受影响寄存器。这会使gcc在汇编指令期间不在寄存器内保持内存值的缓存。我们也必须添加__volatile__关键字,如果内存影响(memory affected)未列在asm的输入和输出中。

对于ARM处理器核,GCC v4提供了如下一系列的限定性字符串:

Constraint Usage in ARM state Usage in Thumb state
f Floating point registers f0 … f7 Not available
h Not available Registers r8…r15
G Immediate floating point constant Not available
H Same a G, but negated Not available
I(大写i) Immediate value in data processing instructions e.g. ORR R0, R0, #operand Constant in the range 0 … 255 e.g. SWI operand
J Indexing constants -4095 … 4095 e.g. LDR R1, [PC, #operand] Constant in the range -255 … -1 e.g. SUB R0, R0, #operand
K Same as I, but inverted Same as I, but shifted
L Same as I, but negated Constant in the range -7 … 7 e.g. SUB R0, R1, #operand
l(小写L) Same as r Registers r0…r7 e.g. PUSH operand
M Constant in the range of 0 … 32 or a power of 2 e.g. MOV R2, R1, ROR #operand Constant that is a multiple of 4 in the range of 0 … 1020 e.g. ADD R0, SP, #operand
m Any valid memory address
N Not available Constant in the range of 0 … 31 e.g. LSL R0, R1, #operand
O Not available Constant that is a multiple of 4 in the range of -508 … 508 e.g. ADD SP, #operand
r General register r0 … r15 e.g. SUB operand1, operand2, operand3 Not available
w Vector floating point registers s0 … s31 Not available
X Any operand

限制符之前可以添加单个的限定性修饰符。如果不添加修饰符,则表明该操作数是只读的。可以使用的修饰符如下所示:

修饰符 说明
默认操作数是只读的
= 只写操作数,通常被用来修饰输出操作数
+ 可读可写操作数,只能用来修饰输出操作数
& 意味着这个操作数为一个早期的改动操作数,其在该指令完成前通过使用输入操作数被修改了。因此,这个操作数不可以位于一个被用作输出操作数或任何内存地址部分的寄存器。如果在旧值被写入之前它仅用作输入而已,一个输入操作数可以为一个早期改动操作数。

猜你喜欢

转载自www.cnblogs.com/thammer/p/12591383.html