本文由zengl.com站长对
http://pan.baidu.com/share/link?shareid=3717576860&uk=940392313 汇编教程英文版相应章节进行翻译得来。
另外再附加一个英特尔英文手册的共享链接地址:
http://pan.baidu.com/share/link?shareid=2345340326&uk=940392313 (在某些例子中会用到)
本篇翻译对应汇编教程英文原著的第141页到第149页 (注意这里的页数不是页脚的页数,而是pdf电子档顶部,分页输入框中的页数,也就是包含了目录,前言部分的总页数,pdf电子档总页数是577,当前已翻译完的页数为149 / 577)。
上一篇介绍了MOV和CMOV指令,这一篇介绍数据移动中的数据交换指令。
Exchanging Data (数据交换):
如果想在程序中交换两个位置的数据元素,例如想将两个寄存器里的值进行交换,如果使用MOV指令的话,就必须使用一个额外的中间寄存器,如下图所示:
图1
上图演示了使用中间寄存器来交换两个寄存器里的数据,使用MOV的指令代码如下:
movl %eax, %ecx
movl %ebx, %eax
movl %ecx, %ebx |
代码中使用ECX作为中间寄存器来交换EAX和EBX里的值,如果使用数据交换指令的话,就不需要什么中间寄存器了,也不需要写这么多行代码,下面就具体的介绍数据交换指令。
The data exchange instructions (数据交换指令):
下表描述了各种数据交换指令的作用:
Instruction
交换指令 |
Description
描述 |
XCHG |
Exchanges the values of two registers,
or a register and a memory location
交换两个寄存器的值,或者交换一个寄存器和一个内存位置的值 |
BSWAP |
Reverses the byte order in a 32-bit register
反转32位寄存器里的字节顺序 |
XADD |
Exchanges two values and stores the
sum in the destination operand
交换两个操作数的值,并将这两个值的和存储到目标操作数 |
CMPXCHG |
Compares a value with an external value
and exchanges it with another
将al\ax\eax中的值与目标操作数比较,如果相等,则将源操作数的值加载到目标操作数,并将ZF标志位置1,如果不相等,则将目标操作数的值加载到al\ax\eax寄存器,并将ZF标志位清0 |
CMPXCHG8B |
Compares two 64-bit values and exchanges
it with another
将EDX:EAX对应的64位值与目标操作数进行比较,如果两个值相等,则将ECX:EBX对应的64位值加载到目标操作数,如果不相等,则将目标操作数的值加载到EDX:EAX,注意目标操作数必须是8字节大小即64位,EDX和ECX对应64位值的高32位,EAX和EBX对应64位值的低32位 |
|
下面详细的描述每种指令。
XCHG指令:
XCHG指令是数据交换指令中最简单的指令,它用于交换两个通用寄存器的值,或者交换一个寄存器和一个内存位置的值,下面是该指令的格式:
operand1和operand2两个操作数既可以是一个通用寄存器,也可以是一个内存位置(不过需要注意的是,不可以两个同时都是内存位置)。该指令可以用于处理8位,16位或者32位的寄存器,不过两个操作数必须是相同的尺寸大小,即要么都是8位,要么都是16位等。
当指令中有一个操作数是内存位置时,系统会自动产生一个LOCK信号,将该内存位置暂时锁住,以防止其他处理器在xchg指令进行数据交换时访问该内存位置。
注意:由于LOCK锁操作是非常耗时的,这会降低你的程序的执行性能,所以需要谨慎的使用XCHG进行内存数据交换。
BSWAP指令:
当你在不同的字节序的系统下进行开发时(前面提到过有big-endian即大字节序和little-endian即小字节序),BSWAP就会是一个很强大的指令,该指令可以将寄存器里的字节顺序进行反转,以适应另一种字节序的环境。在BSWAP指令作用下,会将寄存器的0到7位字节和24到31位字节进行交换,同时将8到15位字节和16到23位字节进行交换。如下图所示:
图2
需要注意的是,BSWAP交换的是独立的字节,即第一个字节和第四个字节交换,第二个字节和第三个字节交换,单个字节里面的位还是保持不变,并没反转,通过字节顺序反转,就可以让值由big-endian(大字节序)变为little-endian(小字节序),反之亦然。
下面是BSWAP的测试代码:
# swaptest.s – An example of using the BSWAP instruction
.section .text
.globl _start
_start:
nop
movl $0x12345678, %ebx
bswap %ebx
movl $1, %eax
int $0x80 |
代码中将十六进制值0x12345678加载到EBX寄存器,然后通过BSWAP指令来反转该寄存器里的值,下面是调试器中的输出情况:
$ gdb -q swaptest
(gdb) break *_start+1
Breakpoint 1 at 0x8048075: file swaptest.s, line 5.
(gdb) run
Starting program: /home/rich/palp/chap05/swaptest
Breakpoint 1, _start () at swaptest.s:5
5 movl $0x12345678, %ebx
Current language: auto; currently asm
(gdb) step
_start () at swaptest.s:6
6 bswap %ebx
(gdb) print/x $ebx
$1 = 0x12345678
(gdb) step
_start () at swaptest.s:7
7 movl $1, %eax
(gdb) print/x $ebx
$2 = 0x78563412
(gdb) |
在执行BSWAP指令前,EBX寄存器里的值是0x12345678 ,执行完BSWAP指令后,EBX里的值就变为
0x78563412 。
XADD指令:
XADD指令用于交换两个寄存器里的值,或者交换一个内存位置和一个寄存器里的值,然后将两操作数的值相加,并将相加的结果存储到目标操作数中(目标操作数可以是一个寄存器,也可以是一个内存位置),XADD指令的格式如下:
source源操作数必须是一个寄存器,而destination目标操作数可以是寄存器,也可以是一个内存位置,destination目标操作数中将包含相加的结果,寄存器可以是8位,16位或32位的大小,另外,XADD指令是从80486处理器开始引入的。
CMPXCHG指令:
CMPXCHG指令将AL , AX 或者 EAX中的值与目标操作数进行比较,如果相等,则将源操作数的值加载到目标操作数,并将ZF标志位置1,如果不相等,则将目标操作数的值加载到AL , AX 或者 EAX寄存器,并将ZF标志位清0 ,CMPXCHG指令只能用于80486及以后的处理器上。
GNU汇编器中,CMPXCHG指令的格式如下:
cmpxchg source, destination |
指令格式中源和目标操作数的顺序是和Intel文档中的intel汇编语法相反的,destination目标操作数可以是一个8位 , 16位或32位的寄存器,也可以是一个内存位置,source源操作数则必须是一个寄存器,并且尺寸大小要和destination目标操作数相一致。
下面的cmpxchgtest.s是CMPXCHG指令的示例代码:
# cmpxchgtest.s - An example of the cmpxchg instruction
.section .data
data:
.int 10
.section .text
.globl _start
_start:
nop
movl $10, %eax
movl $5, %ebx
cmpxchg %ebx, data
movl $1, %eax
int $0x80 |
代码中CMPXCHG指令将EAX里的值和data标签所在内存位置的值进行比较,由于EAX里的值是10,同时data内存位置里的值也是10,两者相等,所以就将源操作数EBX里的值5加载到data内存位置处,下面是调试器中的输出情况:
(gdb) run
Starting program: /home/rich/palp/chap05/cmpxchgtest
Breakpoint 1, _start () at cmpxchgtest.s:9
9 movl $10, %eax
Current language: auto; currently asm
(gdb) step
10 movl $5, %ebx
(gdb) step
11 cmpxchg %ebx, data
(gdb) x/d &data
0x8049090 <data>: 10
(gdb) s
12 movl $1, %eax
(gdb) print $eax
$3 = 10
(gdb) print $ebx
$4 = 5
(gdb) x/d &data
0x8049090 <data>: 5
(gdb) |
从上面输出可以看到,在执行11行的CMPXCHG指令之前,data内存位置的值为10 ,在执行完CMPXCHG指令后,data内存位置处的值就变为了5,和EBX里的值一样了。你还可以将data标签处的值改为其他值,让data里的值和EAX中的不相等,然后调试程序,可以发现在不相等的情况下,data内存位置里的值就不会发生变化,而EAX寄存器的值则会变为data内存位置的值(因为CMPXCHG在EAX和data目标操作数不相等时,就会将data目标操作数的值加载到EAX中)。
CMPXCHG8B指令:
从名字就可以看出来,CMPXCHG8B指令和CMPXCHG指令类似,不过它处理的是8字节的数据,所以是8B结尾,表示8 Byte 。该指令不能在奔腾之前的处理器上使用,该指令的格式如下:
CMPXCHG8B只接受一个destination的目标操作数,destination引用的是一个内存位置,通过将该内存位置里的8字节值与EDX : EAX里的值进行比较(其中EDX对应8字节值的高32位,EAX对应低32位)。如果两个值相等,则将ECX : EBX里的8字节值(ECX里对应8字节值的高32位,EBX对应低32位)加载到destination目标操作数所在的内存位置,如果不相等,则将destination目标内存位置里的8字节值加载到EDX : EAX中。
下面的例子cmpxchg8btest.s演示了该指令的用法:
# cmpxchg8btest.s - An example of the cmpxchg8b instruction
.section .data
data:
.byte 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88
.section .text
.globl _start
_start:
nop
movl $0x44332211, %eax
movl $0x88776655, %edx
movl $0x11111111, %ebx
movl $0x22222222, %ecx
cmpxchg8b data
movl $0, %ebx
movl $1, %eax
int $0x80 |
上面代码中,在data标签处定义了8个字节的数据,由于EAX里的值0x44332211和data的低4字节值相同,EDX里的值0x88776655和data的高4字节值相同,所以在执行完CMPXCHG8B指令后,ECX : EBX对应的8字节值会覆盖掉data内存位置里的值。
该程序在调试器中的输出情况如下:
$ gdb -q cmpxchg8btest
(gdb) break *_start+1
Breakpoint 1 at 0x8048075: file cmpxchg8btest.s, line 10.
(gdb) run
Starting program: /home/rich/palp/chap05/cmpxchg8btest
Breakpoint 1, _start () at cmpxchg8btest.s:10
10 movl $0x44332211, %eax
Current language: auto; currently asm
(gdb) x/8b &data
0x804909c <data>: 0x11 0x22 0x33 0x44 0x55 0x66 0x77 0x88
(gdb) s
11 movl $0x88776655, %edx
(gdb) s
12 movl $0x11111111, %ebx
(gdb) s
13 movl $0x22222222, %ecx
(gdb) s
14 cmpxchg8b data
(gdb) s
15 movl $0, %ebx
(gdb) x/8b &data
0x804909c <data>: 0x11 0x11 0x11 0x11 0x22 0x22 0x22 0x22
(gdb) |
从上面的输出可以看出来,在CMPXCHG8B指令执行前,data里的字节值是
0x11 0x22 0x33 0x44 0x55 0x66 0x77 0x88 ,在执行完CMPXCHG8B指令后,data里的字节值就变为
0x11 0x11 0x11 0x11 0x22 0x22 0x22 0x22 ,该值和ECX : EBX 里的值是一致的,ECX对应高4个字节,EBX对应低4个字节。
Using the data exchange instruction (使用数据交换指令):
使用数据交换指令的经典例子就是排序程序,有很多种算法可以用于将数组里的数据进行排序,下面的例子使用冒泡法将一组整数进行从小到大的排序:
# bubble.s - An example of the XCHG instruction
.section .data
values:
.int 105, 235, 61, 315, 134, 221, 53, 145, 117, 5
.section .text
.globl _start
_start:
movl $values, %esi
movl $9, %ecx
movl $9, %ebx
loop:
movl (%esi), %eax
cmp %eax, 4(%esi)
jge skip
xchg %eax, 4(%esi)
movl %eax, (%esi)
skip:
add $4, %esi
dec %ebx
jnz loop
dec %ecx
jz end
movl $values, %esi
movl %ecx, %ebx
jmp loop
end:
movl $1, %eax
movl $0, %ebx
int $0x80 |
上面的汇编代码如果使用高级语言,则等效代码如下:
for(out = array_size-1; out>0, out--)
{
for(in = 0; in < out; in++)
{
if (array[in] > array[in+1])
swap(array[in], array[in+1]);
}
} |
这里有两层循环,每次内层循环都会找出一个最大值,例如第一次内层循环会从10个数中找出最大的值,并将其放置到最后一个位置,第二次内层循环则从剩下的9个数中找出最大的值,并将其放置到倒数第二个位置,第三次内层循环则从剩下8个数中找出最大值,并放到倒数第三个位置,以此类推,直到把所有内层循环的最大值都找出来,这样数组就从小到大依次排列了。
汇编代码中的EBX寄存器相当于高级语言代码中内层循环的计数器in变量,ECX相当于高级代码外层循环的计数器out变量。汇编中loop标签到skip标签之间的代码相当于高级代码中的if结构和swap函数。
汇编代码中有些还没详细讲解过的指令,都会在后面的章节中进行介绍,这里只需了解XCHG之类的数据交换指令的用法即可。
该程序不会产生任何打印信息,所以需要在调试器中运行,下面是调试器里的输出情况:
$ as -gstabs -o bubble.o bubble.s
$ ld -o bubble bubble.o
$ gdb -q bubble
(gdb) break *end
Breakpoint 1 at 0x80480a5: file bubble.s, line 28.
(gdb) x/10d &values
0x80490b4 <values>: 105 235 61 315
0x80490c4 <values+16>: 134 221 53 145
0x80490d4 <values+32>: 117 5
(gdb) run
Starting program: /home/rich/palp/chap05/bubble
Breakpoint 1, end () at bubble.s:28
28 movl $1, %eax
Current language: auto; currently asm
(gdb) x/10d &values
0x80490b4 <values>: 5 53 61 105
0x80490c4 <values+16>: 117 134 145 221
0x80490d4 <values+32>: 235 315
(gdb) |
在end结束位置设置断点,查看最终的输出结果,最开始values内存位置处的值为:105, 235, 61, 315, 134, 221, 53, 145, 117, 5 ,经过冒泡法排序后,值变为
5 53 61 105 117 134 145 221 235 315 ,可以看到结果将数组从小到大进行了排序。
限于篇幅本章先到这里,下节介绍和栈操作相关的汇编指令。
OK,到这里,休息,休息一下 o(∩_∩)o~~