当前位置:网站首页>gnu汇编语言使用内联汇编 扩展asm

gnu汇编语言使用内联汇编 扩展asm

2020-11-10 08:45:21 osc_qalfjuu8

Extended ASM format 扩展的asm格式:

asm关键字的扩展格式如下:

asm ("assembly code" : output operands : input operands : changed registers);

上面的扩展格式由四个部分组成,每个部分之间使用冒号隔开:

Assembly code: The inline assembly code using the same syntax used for the basic asm format
assembly code:即汇编代码部分,可以在该部分编写所需的内联汇编代码,和基本asm格式相比,扩展格式里的汇编代码除了在百分号的使用上有所不同外(下面会介绍),其他的汇编指令的书写格式都是差不多的
Output operands: A list of registers and memory locations that will contain the output values
from the inline assembly code
输出操作数:用于指定内联汇编代码的执行结果需要输出到哪个寄存器,以及需要输出到哪个C变量里
Input operands: A list of registers and memory locations that contain input values for the inline
assembly code
输入操作数:可以将C变量的值输入到指定的寄存器,这样就可以在内联汇编代码里,通过这些寄存器来使用到C变量的值了,这里的C变量既可以是全局变量,也可以是局部变量
Changed registers: A list of any additional registers that are changed by the inline code
被修改的寄存器:用于指明在内联汇编代码里修改了哪些寄存器,编译器会根据实际情况,对这些修改的寄存器进行一些push、pop之类的操作,这样,汇编代码修改的寄存器就不会影响到外面C代码的正常执行
并非每个部分都必须进行设置,假设你不需要第二个输出操作数,那么输出操作数部分就可以留空,不过需要注意的是,即便输出操作数留空,开头的两个冒号也必须保留,如:asm (“assembly code” : : input operands : changed registers);的格式,另外,如果不需要设置被修改的寄存器部分,那么最后一个冒号可以省略,如:asm (“assembly code” : output locations : input operands);的格式。









下面就介绍下扩展asm格式里输入和输出操作数的具体格式。

Specifying input and output values 输入和输出操作数:

输入和输出操作数的格式如下:

“constraint”(variable)
其中,括号里的variable表示C代码里定义的全局变量或局部变量,而引号里的constraint(约束)则表示用于存储variable变量值的寄存器或内存位置。对于输入操作数,constraint就是将variable变量的值读取到指定的寄存器,或者直接使用变量的内存位置。对于输出操作数,constraint就是先将结果存储到指定的寄存器,再将寄存器里的值设置到variable变量里,某些constraint(约束)也可以直接将结果写入到变量所在的内存里。

constraint(约束)是一个单一的字符,可用的约束符如下表所示:

Constraint
约束符 Description
描述
a Use the %eax, %ax, or %al registers.
使用%eax, %ax或%al寄存器
b Use the %ebx, %bx, or %bl registers.
使用%ebx, %bx或%bl寄存器
c Use the %ecx, %cx, or %cl registers.
使用%ecx, %cx或%cl寄存器
d Use the %edx, %dx, or $dl registers.
使用%edx, %dx或%dl寄存器
S Use the %esi or %si registers.
使用%esi或%si寄存器
D Use the %edi or %di registers.
使用%edi或%di寄存器
r Use any available general-purpose register.
使用任何可用的通用寄存器
q Use either the %eax, %ebx, %ecx, or %edx register.
使用%eax, %ebx, %ecx或%edx寄存器
A Use the %eax and the %edx registers
for a 64-bit value.
使用%eax和%edx寄存器来存储一个64位的值
f Use a floating-point register.
使用一个浮点寄存器
t Use the first (top) floating-point register.
使用第一个ST0浮点寄存器
u Use the second floating-point register.
使用第二个ST1浮点寄存器
m Use the variable’s memory location.
使用变量的内存位置
o Use an offset memory location.
使用一个带有偏移值的内存位置,例如:使用4(%edi)来表示EDI加偏移值4的内存地址,多用于数组元素的访问
V Use only a direct memory location.
使用不带偏移值的内存位置
i Use an immediate integer value.
表示操作数是一个立即数(常量整数值)
n Use an immediate integer value with a known value.
表示操作数是一个已知数值的立即数,在许多系统里,当操作数小于1个word(字)宽时,就不能使用i,而应使用n来表示立即数
g Use any register or memory location available.
可以使用任一通用寄存器或内存位置,或者操作数是一个立即数
除了上面的这些约束外,对于操作数还包含一个constraint modifier(约束修饰符),用于表示操作数是否可读写之类的,可用的约束修饰符如下表所示:







































Constraint modifier
约束修饰符 Description
描述

  • The operand can be both read from
    and written to.
    说明操作数是可读写的,例如"+r",指的是在内联汇编指令开始前,必须先将变量的值赋值给对应的寄存器(也就是读的过程),在执行完汇编指令后,再将寄存器的值写入变量(即写的过程)
    = The operand can only be written to.
    说明操作数是只写的,例如"=r",就是说,只需将结果写入变量,至于内联汇编指令执行前,对应寄存器里的值则可以是任意值,无需在执行前读取变量的值。
    % The operand can be switched with the
    next operand if necessary.
    在必要时,操作数可以和下一个操作数进行交换,下面会举例说明
    & use ‘&’ for each output operand that may
    not overlap an input
    &修饰符是用来强制编译器为输入操作数与输出操作数分配不同的寄存器
    可以参考
    http://blog.csdn.net/bokee/article/details/7029353
    该链接里的文章
    上面的约束修饰符里"+"、"="、"&“这三个只能用于修饰输出操作数,不能用于修饰输入操作数,而”%“则刚好相反,只能用于修饰输入操作数,”%"的用法可以参考下面的代码:













int main(int __argc, char* __argv[])
{
int __in1 = 8, __in2 = 4, __out = 3;
asm (“addl %1, %0\n\t”
: “=r”(__out)
: “%r” (__in1), “0” (__in2));
return 0;
}
在此例中,由于指令是一个加法运算,相当于等式__out = __in1 + __in2,而它与等式__out = __in2 + __in1没有什么不同。所以使用百分号修饰,让GCC知道__in1和__in2可以互换,也就是说GCC可以自动将本例的内联汇编改变为:







asm (“addl %1, %0\n\t”

“=r”(__out)

“%r” (__in2), “0” (__in1));
上面的代码例子来源于:http://tieba.baidu.com/p/310375553 ,另外上面例子里的%1和%0等是占位符,占位符的含义会在下面的介绍里进行说明。

用的较多的是第一个"+“和第二个”="修饰符,例如下面的代码模板:

asm (“assembly code” : “=a”(result) : “d”(data1), “c”(data2));
上面的代码模板,表示将data1变量的值读取到EDX寄存器,将data2变量的值读取到ECX寄存器,内联汇编代码在执行完后,会将结果保存到EAX寄存器,最后,EAX里的值会写入到result变量里,这里必须要在"a"约束前面添加"="修饰符,表示result操作数可以写入数据,否则,由于在没有修饰符的情况,该操作数表示只读,GCC编译器就会报"output operand constraint lacks ‘=’“的错误,即输出操作数的约束缺少”=“修饰符。当然也可以使用”+"修饰符来表示输出操作数可读写。

Using registers 在扩展asm格式里使用寄存器:

在上一篇文章里介绍的基本asm内联汇编格式里,寄存器的使用方式和在单独的汇编文件里的使用方式一样,只需要在寄存器名称前加一个百分号即可,但是在本章介绍的扩展asm格式里,内联汇编代码里要使用寄存器时,必须在寄存器的名称前加两个百分号,例如:%%eax 这样的形式,第一个百分号用于对第二个百分号进行转义,这样两个百分号就可以表示一个普通的百分号来修饰eax,之所以要这么做,是因为%在扩展asm格式里有特殊的用途,就像C语言字符串里的"\n"字符里的"\"字符用于和"n"字符一起来表示特殊的换行符一样,单个百分号用于和数字一起来表示下面会提到的占位符。

下面的regtest1.c程式就演示了如何在扩展的asm格式里使用寄存器:
/* regtest1.c – An example of using registers */
#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;
}

上面的代码里,在将data1和data2局部变量的值分别加载到EDX和ECX后,在asm的汇编代码里,就会通过imull %%edx, %%ecx指令将EDX和ECX寄存器的值相乘,计算结果存储到ECX里,然后通过movl %%ecx, %%eax指令将ECX里的值输出到EAX里,最后EAX里的值会写入到result局部变量里,这样在随后的C代码里就可以使用printf函数来将result的值给显示出来。这里需要注意寄存器的写法,如:%%ecx等都有两个百分号,原因在上面已经解释了。

参考文章
https://www.zengl.com/a/201404/146.html

版权声明
本文为[osc_qalfjuu8]所创,转载请带上原文链接,感谢
https://my.oschina.net/u/4389636/blog/4710497