如果操作数超过 127,CMP 指令会表现异常吗?

CMP instruction behaving weirdly if operand is over 127?

我写了这个汇编程序:

.section .data
    1: .asciz "Hello"

.section .text

entry:
    mov [=10=]x07C0, %ax
    add [=10=]x120, %ax
    mov %ax, %ss
    mov 0x100, %sp

    mov [=10=]x7C0, %ax
    mov %ax, %ds

    # mov b, %si
    mov [=10=]xE, %ah
    mov [=10=]x0, %si
    mov [=10=]x0, %bx

    push %bp
    mov %sp, %bp
    mov %di, -20(%bp)
    mov %si, -32(%bp)

    movl [=10=]x0, -4(%ebp)
.loopcond:
    cmpl 7, -4(%ebp)
    jge .halt
.print:
    lodsb
    int [=10=]x10
    add [=10=]x1, -4(%ebp)
    jmp .loopcond
.halt:
    jmp .halt

.loopcond 部分中的第一条指令将变量与 127 进行比较(类似于循环 127 次的 for 循环)。这工作正常并在跳转到 .halt 之前运行代码 127 次。然而,当我增加要比较的值时(例如增加到 128),代码似乎立即跳转到 .halt。我不明白为什么会这样。是关于有符号整数比较的吗?

我查看了 objdump,一次是 127 和 128:

// 127:
00000037 <.loopcond>:
  37:   83 7d fc 7f             cmpl   [=11=]x7f,-0x4(%ebp)
  3b:   7d 09                   jge    46 <.halt>

// 128:
00000037 <.loopcond>:
  37:   81 7d fc 80 00 00 00    cmpl   [=11=]x80,-0x4(%ebp)
  3e:   7d 09                   jge    49 <.halt>

我注意到 cmpl 指令的操作数在 128 示例中是 4 个字节长,而在 127 示例中它只有 1 个字节。我怀疑与此有关的某些东西是导致此错误的原因。

您的问题可能与使用不明确操作数大小的 add [=10=]x1, -4(%ebp) 有关。如果 GAS 选择字节操作数大小,那可能会导致问题吗?尽管如果高位字节为零,它只是零扩展到双字。你的问题的原因并不明显,但奇怪的是你为 BP 和 EBP 混合了 16 位和 32 位地址大小。

说真的,只要把一个数字放在寄存器中,然后像正常人一样用 dec reg / jnz 循环。

或者使用调试器查看内存并找出发生了什么。您的 cmpl 7, -4(%ebp) 确实指定了一个操作数大小,因此它肯定是在进行双字比较,而不是将 128 视为 -128 和 8 位 2 的补码。

I noticed that the operand of the cmpl instruction is 4 bytes long in the 128 example, while it's only 1 byte in the 127 example. I suspect that something about that is the cause of this error.

这不是错误。大多数基本的 x86 整数 ALU 指令都有一个版本的操作码,一个是 32 位立即数,另一个是 符号扩展的 8 位立即数.

在原始 8086 上,这为 cmp r/m16, imm8cmp r/m16, imm16 等指令节省了 1 个字节。在 32/64 位代码中,这为 imm8 与 imm32 节省了 3 个字节。 https://www.felixcloutier.com/x86/cmp 列出可用的表格。

分界点当然是-128 .. +127,因为它是符号-扩展立即数。您的汇编程序总是为给定的 asm 源代码行选择可能的最小编码,因此一切都按预期工作。


如果您正在为 32 位模式汇编,但 运行 宁为 16 位模式,cmpl $imm32, r/m32 将以与其余代码不同的方式中断.

无论模式如何,其他指令的长度都相同,但 运行 具有相反的操作数大小(16 对 32)。但是 cmplcmpw 的操作码是相同的;区别仅在于操作数大小(切换为非默认模式值 66 前缀)。

因此,当您的 cmpl 在 16 位模式下为 32 位解码进行汇编时,会留下 2 个字节的立即数。这些字节是 00 00,这是一个内存目标 add [something], al(我忘记了 00 modrm 在 16 位寻址模式中编码的寄存器。)这将破坏来自 cmp.

使用.code16或命令行选项生成 16 位机器代码。