之前章节介绍的基本asm格式提供了一种简单的创建内联汇编的方法,不过这种基本格式有几个限制,首先,它里面的汇编代码只能使用C程式里定义的全局变量,无法使用局部变量。此外,还要很小心的使用寄存器,以免覆盖掉C代码里要用的寄存器的值...

    本文由zengl.com站长对
    http://pan.baidu.com/share/link?shareid=3717576860&uk=940392313 汇编教程英文版相应章节进行翻译得来。

    另外再附加一个英特尔英文手册的共享链接地址:
    http://pan.baidu.com/share/link?shareid=2345340326&uk=940392313 (在某些例子中会用到)

    本篇翻译对应汇编教程英文原著的第401页到第410页,对应原著第13章 (注意这里的页数不是页脚的页数,而是pdf电子档顶部,分页输入框中的页数,也就是包含了目录,前言部分的总页数,pdf电子档总页数是577,当前已翻译完的页数为410 / 577)。

Extended ASM 扩展的asm内联汇编格式:

    之前章节介绍的基本asm格式提供了一种简单的创建内联汇编的方法,不过这种基本格式有几个限制,首先,它里面的汇编代码只能使用C程式里定义的全局变量,无法使用局部变量。此外,还要很小心的使用寄存器,以免覆盖掉C代码里要用的寄存器的值。

    GNU编译器提供了一种扩展的asm格式,可以有效解决上面提到的两个问题,下面就具体介绍下扩展的asm格式。

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等都有两个百分号,原因在上面已经解释了。

    我们可以使用gcc -S来查看上面代码会生成的汇编指令:

...............................
...............................

	movl	40(%esp), %ecx
	movl	28(%esp), %edx
#APP
# 9 "regtest1.c" 1
	imull %edx, %ecx
	movl %ecx, %eax
# 0 "" 2
#NO_APP
	movl	%eax, %ebx
	movl	%ebx, 36(%esp)
	movl	$.LC0, %eax
	movl	36(%esp), %edx
	movl	%edx, 4(%esp)
	movl	%eax, (%esp)
	call	printf
...............................
...............................


    上面汇编输出里40(%esp)对应为data2变量的栈内存,28(%esp)则为data1变量的栈内存,#APP#NO_APP的部分是asm里写入的内联汇编代码部分,可以看到asm里写入的%%edx会被编译器转为%edx,即两个百分号放在一起转义为了一个普通的百分号。

    有的时候,如果内联汇编代码里的指令已经隐式包含了输出操作数时,就可以不需要指明asm里的输出操作数部分,例如下面的movstest.c程式:

/* movstest.c - An example of instructions with only input values */
#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;
}


    上面代码里asm部分使用的MOVS指令,它的隐式源操作数为ESI寄存器,隐式目标操作数为EDI寄存器,当rep movsb执行时,会根据ECX里的长度信息,将ESI指向的input里的字符串拷贝到EDI指向的output字符数组里,所以,在输入操作数里的"D"(output)已经指明了隐式的输出操作数为output,这样,asm的输出操作数部分就可以留空了。另外,asm使用了volatile关键字来进行修饰,这是由于某些版本的GCC编译器,可能会因为没有指定输出操作数而移除这段汇编代码(因为没有输出操作数,编译器会认为没有必要生成这段代码),加入volatile后,可以防止这种情况发生。

Using placeholders 使用占位符:

    前面我们提到过,在扩展的asm格式里,百分号有一个特殊的用途,它可以和数字一起来表示占位符,例如下面的代码片段:

asm ("assembly code"
: "=r"(result)
: "r"(data1), "r"(data2));


    在上面的assembly code汇编代码里可以使用如下几个占位符:
  • %0 will represent the register containing the result variable value.
    %0表示包含了result变量值的寄存器
  • %1 will represent the register containing the data1 variable value.
    %1表示包含了data1变量值的寄存器
  • %2 will represent the register containing the data2 variable value.
    %2表示包含了data2变量值的寄存器
    占位符里的数字是从0开始的,0对应汇编代码后面的第一个操作数,1对应第二个操作数,2对应第三个操作数,以此类推,操作数既包括输出操作数,又包括输入操作数,根据它们的先后顺序来确定占位符里的数字值,占位符既可以表示操作数所使用的寄存器(当constraint约束为"r"之类的寄存器类型时),也可以表示内存位置(当约束为"m"之类的内存类型时),还可以表示立即数(当约束为"i"之类的立即数类型时)。

    占位符的使用方式就类似于C语言里的宏,编译器在解析时,会根据占位符所对应的操作数,以及该操作数的约束类型等,将占位符替换为对应的寄存器或内存位置等。

    下面的regtest2.c程式就演示了占位符的用法:

/* regtest2.c – An example of using placeholders */
#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;
}


    上面代码里输出和输入操作数的约束都是"r",这样编译器就会为这三个操作数各自分配一个通用寄存器,汇编代码里"imull %1,%2"表示将data1寄存器和data2寄存器的值相乘,结果存储到data2的寄存器里,然后通过"movl %2,%0"将data2寄存器里的结果设置到result对应的寄存器里,result的寄存器最后会将结果输出到result变量所在的内存里。

    要知道上面那三个操作数到底被分配使用了哪些寄存器,就需要通过gcc -S来查看:

$ gcc -S regtest2.c
$ cat regtest2.s
............................
............................
	movl	$10, 28(%esp)
	movl	$20, 24(%esp)
	movl	28(%esp), %eax
	movl	24(%esp), %edx
#APP
# 9 "regtest2.c" 1
	imull %eax, %edx
	movl %edx, %ebx
# 0 "" 2
#NO_APP
	movl	%ebx, 20(%esp)
	movl	$.LC0, %eax
	movl	20(%esp), %edx
	movl	%edx, 4(%esp)
	movl	%eax, (%esp)
	call	printf
............................
............................
$


    从上面的输出可以看到,data1被分配为EAX寄存器,data2被分配为EDX寄存器,result则被分配为EBX寄存器,C程式里%1的占位符对应data1的寄存器,所以%1就被替换为%eax,同理,%2被替换为%edx,%0被替换为%ebx 。

    另外,由于data1和data2是asm里的输入操作数,所以在一开始data1和data2的值会被设置到EAX与EDX里,即上面"movl    28(%esp), %eax"和"movl    24(%esp), %edx"的代码,result是输出操作数,所以result对应的EBX寄存器里的结果最终会写入到result变量所在的内存里,即上面"movl    %ebx, 20(%esp)"的代码。

    你除了可以使用gcc -S来查看编译器会生成的汇编指令外,还可以在调试器里对asm里的汇编指令进行调试:

$ gcc -gstabs -o regtest2 regtest2.c
$ gdb -q regtest2
Reading symbols from /home/zengl/Downloads/asm_example/inline/regtest2...done.
(gdb) b 9
Breakpoint 1 at 0x80483fe: file regtest2.c, line 9.
(gdb) r
Starting program: /home/zengl/Downloads/asm_example/inline/regtest2 

Breakpoint 1, main () at regtest2.c:9
9		asm ("imull %1, %2\n\t"
(gdb) disassemble 
Dump of assembler code for function main:
...................................
...................................
   0x080483ee <+10>:	movl   $0xa,0x1c(%esp)
   0x080483f6 <+18>:	movl   $0x14,0x18(%esp)
=> 0x080483fe <+26>:	mov    0x1c(%esp),%eax
   0x08048402 <+30>:	mov    0x18(%esp),%edx
   0x08048406 <+34>:	imul   %eax,%edx
   0x08048409 <+37>:	mov    %edx,%ebx
   0x0804840b <+39>:	mov    %ebx,0x14(%esp)
...................................
...................................
(gdb) stepi
0x08048402	9		asm ("imull %1, %2\n\t"
(gdb) disassemble
...................................
...................................
   0x080483ee <+10>:	movl   $0xa,0x1c(%esp)
   0x080483f6 <+18>:	movl   $0x14,0x18(%esp)
   0x080483fe <+26>:	mov    0x1c(%esp),%eax
=> 0x08048402 <+30>:	mov    0x18(%esp),%edx
   0x08048406 <+34>:	imul   %eax,%edx
   0x08048409 <+37>:	mov    %edx,%ebx
...................................
...................................
(gdb) si
0x08048406	9		asm ("imull %1, %2\n\t"
(gdb) disassemble 
Dump of assembler code for function main:
...................................
...................................
   0x080483ee <+10>:	movl   $0xa,0x1c(%esp)
   0x080483f6 <+18>:	movl   $0x14,0x18(%esp)
   0x080483fe <+26>:	mov    0x1c(%esp),%eax
   0x08048402 <+30>:	mov    0x18(%esp),%edx
=> 0x08048406 <+34>:	imul   %eax,%edx
   0x08048409 <+37>:	mov    %edx,%ebx
   0x0804840b <+39>:	mov    %ebx,0x14(%esp)
...................................
...................................


    上面先使用gcc -gstabs -o regtest2 regtest2.c命令生成带调试信息的regtest2可执行文件(必须有-gstabs选项,才会有调试信息),在gdb里运行到asm内联汇编代码处时,可以使用disassemble命令来查看反汇编代码,输出的汇编代码里会用=>箭头来标识出下一条将要执行的汇编代码位置,可以使用stepisi命令来单步执行一条汇编指令。

    如果你觉得反复使用disassemble命令太繁琐的话,可以使用layout asm命令来切换到gdb的tui视图(该视图下有点类似图形界面的IDE调试窗口):


图1

    在该视图下,有一个窗口可以显示源代码,我这里使用的是layout asm命令,所以查看到的是汇编指令,可以使用上下键来让窗口上下滚动以查看其他部分的指令,使用si命令单步执行时,窗口里的箭头会自动下移,这就比反复输入disassemble命令要方便很多,还可以使用layout src来只查看C源代码,或者可以使用layout split来同时查看C源码与对应的汇编指令:
 

图2

    要退出该视图,可以通过同时按下left ctrl + x + a 即同时按下左ctrl键,X键和A键这三个键来恢复到原来的普通文本调试模式。

    通过layout split同时查看C源码和汇编指令,就可以很清楚的看到源码里的%1对应替换为了%eax,%2被替换为了%edx 。

Referencing placeholders 引用占位符:

    有时候当我们的输入操作数和输出操作数是同一个变量时,为了尽可能减少通用寄存器的使用(通用寄存器的数目有限),我们就需要告诉编译器这两个操作数是同一个变量,它们可以使用相同的寄存器,要实现这点,可以在操作数的约束里指明另一个同变量的操作数的占位符:

/* regtest3.c – An example of using placeholders */
#include <stdio.h>

int main()
{
	int data1 = 10;
	int data2 = 20;
	int result = 3;
	asm ("imull %1, %2\n\t"
	    "movl %2, %0"
	    : "=r"(result)
	    : "r"(data1), "0"(result));
	printf("The result is %d\n", result);
	return 0;
}


    上面代码里输入和输出操作数里都用到了result变量,所以将result里的约束设置为输出操作数的占位符"0",这样asm里的%0和%2就会使用相同的寄存器,通过gcc -S的输出结果如下:

$ gcc -S regtest3.c 
$ cat regtest3.s
...........................
...........................
	movl	$10, 28(%esp)
	movl	$20, 24(%esp)
	movl	$3, 20(%esp)
	movl	28(%esp), %eax
	movl	20(%esp), %edx
	movl	%edx, %ebx
#APP
# 9 "regtest3.c" 1
	imull %eax, %ebx
	movl %ebx, %ebx
# 0 "" 2
#NO_APP
	movl	%ebx, 20(%esp)
	movl	$.LC0, %eax
	movl	20(%esp), %edx
	movl	%edx, 4(%esp)
	movl	%eax, (%esp)
	call	printf
...........................
...........................
$


    从上面的输出可以看到,"imull %1, %2"和“movl %2, %0”里的%2与%0都被替换为了%ebx 。

Alternative placeholders 替代占位符:

    当你有很多输入,输出操作数时,asm里的汇编代码要使用的占位符数目就会变多,有时候就容易混淆(占位符全是数字,容易弄混对应的操作数),庆幸的是,从GCC 3.1的版本开始,可以给占位符取个有意义的别名,这些别名就可以取代那些数字。例如下面的alttest.c程式:

/* alttest.c – An example of using alternative placeholders */
#include <stdio.h>

int main()
{
	int data1 = 10;
	int data2 = 20;
	asm ("imull %[data1], %[data2]"
	: [data2] "=r"(data2)
	: [data1] "r"(data1), "0"(data2));
	printf("The result is %d\n", data2);
	return 0;
}


    上面代码里使用%[data1]来替代原来的%1,用%[data2]来替代原来的%0 。从上面代码可以看出来,定义占位符别名的格式如下:

[name]"constraint"(variable)

    只需在约束的左侧用中括号将别名括起来即可,如上面代码里的[data2] "=r"(data2)与[data1] "r"(data1) ,这里的别名可以随便设置什么名字,上面代码为了方便起见,就直接使用的变量名。

    在内联汇编里调用占位符别名的格式如下:

%[name]

    和占位符类似,同样使用%作为前缀,然后是中括号,中括号里为别名的名称,例如上面代码里的%[data1]与%[data2] 。

Changed registers list 扩展asm格式里被修改的寄存器部分:

[zengl pagebreak]

Changed registers list 扩展asm格式里被修改的寄存器部分:

    在之前的例子里,你可能注意到,这些例子里只有输出和输入操作数部分,还没用到扩展asm格式的被修改的寄存器部分,这是因为,默认情况下,GCC会假定输出和输入操作数所使用的寄存器会被修改,所以就不需要再在被修改的寄存器列表里指出来,而且如果你将输入和输出操作数所使用的寄存器,写入到被修改的寄存器列表里,则GCC还会报错,例如下面的badregtest.c程式:

/* badregtest.c – An example of incorrectly using the changed registers list */
#include <stdio.h>

int main()
{
	int data1 = 10;
	int result = 20;
	asm ("addl %1, %0"
	: "=d"(result)
	: "c"(data1), "0"(result)
	: "%ecx", "%edx");
	printf("The result is %d\n", result);
	return 0;
}


    上面的例子里,输出操作数的约束为"=d",即输出操作数会使用EDX寄存器来存储result的值,data1的约束为"c",即输入操作数data1会使用ECX寄存器来存储data1变量的值,在asm的最后一个冒号的后面是被修改的寄存器列表部分,该部分将输出操作数和输入操作数所使用的ECX和EDX寄存器列举到这里,这种做法会导致GCC报如下错误:

$ gcc -o badregtest badregtest.c
badregtest.c: In function ‘main’:
badregtest.c:8:2: error: can't find a register in class ‘DREG’ while reloading ‘asm’
badregtest.c:8:2: error: ‘asm’ operand has impossible constraints

$

    所以只有那些在内联汇编里使用到的,但是又没有在输出和输入操作数部分指明的寄存器,才需要加入到被修改的寄存器列表里,另外,上面的例子虽然报了错,但是被修改寄存器部分的写法并没错,例如,要将ECX加入到被修改的寄存器列表里,可以使用百分号加寄存器名称的形式,如:asm(".... " : .... : .... : "%ecx"),也可以省略掉百分号,如:asm(".... " : .... : .... : "ecx") 。

    我们对上面的badregtest.c做些调整,得到如下的right_regtest.c程式,就可以正常编译:

/* right_regtest.c – An example of incorrectly using the changed registers list */
#include <stdio.h>

int main()
{
	int data1 = 10;
	int result = 20;
	asm ("addl %1, %0\n\t"
	"movl %1,%%esi"
	: "=d"(result)
	: "c"(data1), "0"(result)
	: "%esi");
	printf("The result is %d\n", result);
	return 0;
}


    上面代码里ESI寄存器在内联汇编里被使用过,但是又没在输入和输出操作数部分进行指明,所以%esi就需要添加到最后的被修改的寄存器部分,可以使用gcc -S来查看上面代码对应的汇编指令:

$ gcc -o right_regtest right_regtest.c
$ ./right_regtest 
The result is 30
$ gcc -S right_regtest.c
$ cat right_regtest.s
.............................
.............................
	pushl	%esi
.............................
.............................
	movl	$10, 28(%esp)
	movl	$20, 24(%esp)
	movl	28(%esp), %eax
	movl	24(%esp), %edx
	movl	%edx, %ebx
	movl	%ebx, %edx
	movl	%eax, %ecx
#APP
# 8 "right_regtest.c" 1
	addl %ecx, %edx
	movl %ecx,%esi
# 0 "" 2
#NO_APP
	movl	%edx, %ebx
	movl	%ebx, 24(%esp)
	movl	$.LC0, %eax
	movl	24(%esp), %edx
.............................
.............................
	popl	%esi
.............................
.............................
$


    可以看到,除了可以编译通过外,gcc还对ESI寄存器作了push,pop之类的保存和恢复的操作,从而让ESI寄存器可以在内联汇编里放心的使用。

    此外,前面提到过,在输入和输出操作数的约束里使用了"r"之类的约束符时,表示GCC会从通用寄存器里选择一个寄存器来用,如果在被修改的寄存器列表里出现了某个通用寄存器的话,那么GCC就不会为输入和输出操作数选择被修改的寄存器列表里出现过的通用寄存器,如下面的changedtest.c程式:

/* changedtest.c – An example of setting registers in the changed registers list */
#include <stdio.h>

int main()
{
	int data1 = 10;
	int result = 20;
	asm ("movl %1, %%eax\n\t"
	"addl %%eax, %0"
	: "=r"(result)
	: "r"(data1), "0"(result)
	: "%eax");
	printf("The result is %d\n", result);
	return 0;
}


    使用gcc -S来查看上面代码对应的汇编指令:

$ gcc -S changedtest.c
$ cat changedtest.s
............................
............................
	movl	$10, 28(%esp)
	movl	$20, 24(%esp)
	movl	28(%esp), %edx
	movl	24(%esp), %eax
	movl	%eax, %ebx
#APP
# 8 "changedtest.c" 1
	movl %edx, %eax
	addl %eax, %ebx
# 0 "" 2
#NO_APP
	movl	%ebx, 24(%esp)
	movl	$.LC0, %eax
	movl	24(%esp), %edx
	movl	%edx, 4(%esp)
	movl	%eax, (%esp)
	call	printf
............................
............................
$


    从上面的输出可以看到,%1被替换为EDX寄存器,%0被替换为EBX寄存器,原C程式里asm的操作数的约束都是"r",GCC在选择通用寄存器时避开了EAX寄存器,因为EAX寄存器出现在了被修改的寄存器列表里。

    另外,被修改的寄存器部分还有两个特殊的字符串:"cc"和"memory",当你的汇编指令有权限,且会去修改condition code register(条件代码寄存器)时,就需要在被修改的寄存器部分加入"cc",如:asm ("....." : ..... : ..... : "cc");

    当你不希望内存变量的值被缓存到寄存器里时,可以加入"memory",如:asm ("....." : ..... : ..... : "memory"); 另外,当受"memory"影响的内存变量没有列举在输入和输出操作数里时,还需要添加volatile关键字,如:asm volatile ("....." : ..... : ..... : "memory");

    限于篇幅,本章就到这里,下一篇介绍扩展asm格式里的其他约束的用法等。

    OK,就到这里,休息,休息一下 o(∩_∩)o~~
上下篇

下一篇: 使用内联汇编 (三) 内联汇编结束篇

上一篇: 使用内联汇编 (一)

相关文章

优化汇编指令 (二)

调用汇编模块里的函数 (三) 静态库、共享库、本章结束篇

Moving Data 汇编数据移动 (四) 结束篇 栈操作

汇编函数的定义和使用 (三) 汇编函数结束篇

基本数学运算 (四) 基本运算结束篇

调用汇编模块里的函数 (一)