AT&T x86_32 汇编_005_附带条件判断的数据传递指令

MOV系列指令简单直接粗暴, 但程序员的工作内容一般都不是简单的数据传递, 一般都伴随着各种条件判断. 这一节介绍一些特殊的数据传递指令. 在介绍CMOV系列指令之前, 还需要介绍一些预备知识: 包括标志位寄存器, 以及在汇编中如何比较数值大小, 如何检查比较结果

EFLAGS寄存器

在详细介绍其它寄存器之前, 我们有必要先看一下这个特殊的寄存器. 你有没有想过, CPU执行数学运算的时候, 如果出现溢出, 进位, 借位等情况时, 计算机应当怎么处理? 毕竟寄存器的位宽是有限的, 在当前讨论的范畴中, 它不过最大才32位, 即便是等到以后我们接受x86_64汇编时, 寄存器也才64位, 能表达的数值范围始终是有限的. 总有意外要发生, 这个时候, 怎么办?

计算机中有一个特殊的寄存器, 就是用来标示运算结果的. 当数学运算出现溢出, 进位, 借位等状态时, 这个寄存器就会自动的把其中某个标志位置为1. 这就是所谓的"标志位寄存器". 在16位机时代, 标志位寄存器被命名为FLAGS Register, 位宽同其它寄存器一样, 是16位. 进入32位时代后, 该寄存器的位宽也变成了32位, 同时名称变更为EFLAGS Register, 相应的, 64位时代, 寄存器也有64位宽, 名称也变为RFLAGS Register

标志位寄存器并不仅仅能对数据运算的结果进行一些状态标示, 很多位还有其它方面的功能, 完整的标志位寄存器状态位说明表格如下:

标志位位置 取值掩码 惯用缩写叫法 标志位语义 标志位类别
0 0x0001 CF 若ALU运算时发生进位或借位, 则置1 状态位
1 0x0002 未使用的位, 始终为1
2 0x0004 PF 奇偶检验位. 一般情况下只检验运算结果的最低位一字节. 即若运算结果的最低一字节的二进制表达中, 1的个数为偶数, 该标志位则置1. 否则置0 状态位
3 0x0008 未使用的位. 值未定义
4 0x0010 AF 辅助carry标志位. 当ALU运算时, 若数值的低四位(bit)在运算过程中要向其它高位借位或进位, 该标志位就置1. 一般的情况下, 我们是用不到这个标志位的, 它的存在主要是为了支持Binary-coded decimal算法 状态位
5 0x0020 未使用的位. 值未定义
6 0x0040 ZF 若ALU运算结果为0, 则该位置1 状态位
7 0x0080 SF 若ALU运算结果为负数, 则该位置1.其实本质上, 寄存器也好, CPU也好, 是不关心数值是有符号还是无符号的, 这个标志位只是始终保持与运算结果的最高位相同而已. 只是对于负数的二进制表达来说, 其最高位始终为1, 所以才有了, 若去处结果为负数, 该标志位置1的说法. 状态位
8 0x0100 TF Trap flag (single step). 与支持调试相关 控制位
9 0x0200 IF Interrupt enable flag. 与硬件中断相关 控制位
10 0x0400 DF Direction flag. 在内存拷贝过程中, 该位主要控制拷贝的方向, 是先拷贝高地址, 还是先拷贝低地址. 对于src和dst内存区域有重叠时, 两种方式带来的结果是有差异的 控制位
11 0x0800 OF 若ALU运算时发生溢出, 则置1 状态位
12 - 13 0x3000 IOPL I/O privilege level (286+ only), 系统标志位
always 1 on 8086 and 186
14 0x4000 NT Nested task flag (286+ only), 系统标志位
always 1 on 8086 and 186
15 0x8000 Reserved,
always 1 on 8086 and 186,
always 0 on later models
32位寄存器上额外多出来的位
16 0x0001 0000 RF Resume flag (386+ only) 系统标志位
17 0x0002 0000 VM Virtual 8086 mode flag (386+ only) 系统标志位
18 0x0004 0000 AC Alignment check (486SX+ only) 系统标志位
19 0x0008 0000 VIF Virtual interrupt flag (Pentium+) 系统标志位
20 0x0010 0000 VIP Virtual interrupt pending (Pentium+) 系统标志位
21 0x0020 0000 ID Able to use CPUID instruction (Pentium+) 系统标志位
22 0xFFC0 0000 ID Able to use CPUID instruction (Pentium+) 系统标志位
23 - 31 0xFFCF 0000 VAD VAD Flag 系统标志位
64位寄存器上额外多出来的位
32-63 0xFFFF FFFF... 未使用的位
...0000 0000

其实与数学运算相关的标志位仅有五个: CF, PF, ZF, SF, OF这五个, 其实AF也算, 但是很少用. 除非代码逻辑里需要引入BCD算法.

CMP指令

CMP指令是一个用于比较两个数值的指令, 它的语法很简单: cmp B, A , 比较大小其实是由ALU去执行A-B运算, 比较的结果需要借助于其它的一些条件指令去使用. 或者去查看标志位寄存器.

需要注意的一点是:

在AT&T汇编中, 是右减左, 在Intel语法中, 是左减右!这简直就是AT&T最大的恶意

我们以AT&T语法为准, 来讲这个指令. 需要谨记的基本是下面两点:

  1. 被减数, 也就是AT&T语法中第二个操作数. 不能是立即数. 只能是内存值或寄存器值.
  2. 减数, 也就是AT&T语法中的第一个操作数. 可以是立即数, 可以是内存值, 也可以是寄存器值.

CMOV指令

C, 是conditional的意思, 即为"带条件的". CMOV指令也是一系列的数据传递指令, 使用的语法和MOV是一毛一样的. 但它会检查CMP指令的执行结果. 若比较结果不符合预期, 则什么也不会做.

CMOV是一系列指令, 它的完全体由三部分组成: cmov<cc><x> , 其中, <cc>是条件, 是一个至三个英文字符, <x>则指出了数据的位宽, 和mov一样.

<cc>一般有以下几种

含义
A Above. >
NA Not Above. <=
AE Above or Equal. >=
NAE Not Above or Equal. <
B Below. <
NB Not Below. >=
BE Below or Equal. <=
NBE Not Below or Equal. >
C Carry. 发生进位或借位
NC Not Carry. 没有发生进位或借位
E Equal. ==
NE Not Equal. !=
G Greater. >
NG Not Greater. <=
GE Greater or Equal. >=
NGE Not Greater or Eqeual. <
L Less. <
NL Not Less. >=
LE Less or Equal. <=
NLE Not Less or Equal. >
O Overflow. 发生溢出
NO Not Overflow. 没有发生溢出

示例程序: 获取数组中最大的数

下面是一个示例程序, 和上一节的示例程序比较相似. 不过个示例程序是通过CMP和CMOVA指令, 遍历数组的过程中, 挑出数组中最大的数输出.

.section .data
output:
    .asciz "The largest value == %d\n"

values:
    .int 11, 22, 141, 423, 53, 321, 545, 323, 546, 7        # 10个数的数组

.section .text
.globl _start
_start:
    nop

    movl values, %ebx       # %ebx == values[0]
    movl $1, %edi           # %edi == 1

loop:
    movl values(, %edi, 4), %eax        # %eax == values[i]
    cmp %ebx, %eax                      # if (%eax > %ebx)
    cmova %eax, %ebx                    #   { %ebx = %eax }

    inc %edi                            # %edi++
    cmp $10, %edi                       # if (%edi != 10)
    jne loop                            #  { goto loop }

    pushl %ebx                          # printf("The largest value == %d\n", %ebx)
    pushl $output
    call printf
    addl $8, %esp

    pushl $0                            # exit(0)
    call exit

编译, 链接, 运行如下图所示:

005_cmov

猜你喜欢

转载自www.cnblogs.com/neooelric/p/9695060.html
今日推荐