上一篇介绍了MOV和CMOV指令,这一篇介绍数据移动中的数据交换指令 Exchanging Data (数据交换) 如果想在程序中交换两个位置的数据元素,例如想将两个寄存器里的值进行交换,如果使用MOV指令的话,就必须使用一个额外的中间寄存器...

    本文由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指令是数据交换指令中最简单的指令,它用于交换两个通用寄存器的值,或者交换一个寄存器和一个内存位置的值,下面是该指令的格式:

xchg operand1, operand2

    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指令的格式如下:

xadd source, destination

    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

    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~~
上下篇

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

上一篇: Moving Data 汇编数据移动 (二)

相关文章

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

汇编中使用文件 (二)

优化汇编指令 (一)

汇编流程控制 (三) 流程控制结束篇

IA-32平台(二)

汇编开发相关工具 (二)