assembly 如果操作数超过127,CMP指令会表现得很奇怪吗?

8ljdwjyq  于 2022-11-13  发布在  其他
关注(0)|答案(1)|浏览(121)

我写了这个汇编程序:

.section .data
    1: .asciz "Hello"

.section .text

entry:
    mov $0x07C0, %ax
    add $0x120, %ax
    mov %ax, %ss
    mov 0x100, %sp

    mov $0x7C0, %ax
    mov %ax, %ds

    # mov $1b, %si
    mov $0xE, %ah
    mov $0x0, %si
    mov $0x0, %bx

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

    movl $0x0, -4(%ebp)
.loopcond:
    cmpl $127, -4(%ebp)
    jge .halt
.print:
    lodsb
    int $0x10
    add $0x1, -4(%ebp)
    jmp .loopcond
.halt:
    jmp .halt

.loopcond段中的第一条指令将变量与127进行比较(就像一个for循环,迭代127次)。这样做很好,在跳转到.halt之前运行代码127次。(例如,到128),代码似乎立即跳转到.halt。我不明白为什么会发生这种情况。是关于有符号整数比较的吗?
我看了看objdump,曾经用127和128:

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

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

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

368yc8dk

368yc8dk1#

你告诉GAS为32位模式进行汇编,但是然后用16位模式的CPU运行机器码,所以事情解码错误。
之前的猜测是,您的问题可能与add $0x1, -4(%ebp)有关,它使用了不明确的操作数大小。如果GAS选择字节操作数大小,这可能会导致问题?尽管如果高位字节为零,它将零扩展到双字。问题的原因并不明显,但您将BP和EBP的16位和32位地址大小混合在一起,这很奇怪。
(更新:除mov以外,操作数大小不明确得指令在GAS中默认为dword(至少对于32位或64位模式).对于16位模式,它默认为字长,即不使用66操作数大小前缀得非字节操作码.对于mov,这是一个错误.最新得GAS版本警告说,但仍然执行默认值。像NASM这样更好的汇编程序会将其视为错误。)
说真的,只要把一个数字放在寄存器里,然后像正常人一样用dec reg/jnz循环。
或者使用调试器来查看内存并找出发生了什么。您的cmpl $127, -4(%ebp)确实指定了操作数大小,因此它肯定是在进行双字比较,而不是将128视为具有8位2的补码的-128
我注意到在128例子中cmpl指令的操作数是4个字节长,而在127例子中只有1个字节长。我怀疑这是导致这个错误的原因。
这不是一个错误。大多数基本x86整数ALU指令都有一个32位立即数版本的操作码,还有一个符号扩展的8位立即数版本的操作码。
在原始8086上,这为cmp r/m16, imm8cmp r/m16, imm16之类得指令节省了1个字节.在32/64位代码中,这为imm 8与imm 32节省了3个字节. https://www.felixcloutier.com/x86/cmp列出了可用得格式.
截止点当然是-128.. +127,因为它是一个 sign 扩展立即数。对于给定的asm源代码行,汇编器总是选择尽可能小的编码,所以一切都按预期工作。

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

其他指令的长度都相同,与模式无关,但以相反的操作数大小运行(16对32)。差别仅在于操作数大小(通过66前缀切换到非模式默认值)。
因此,当cmpl在16位模式下为32位解码而组装时,会留下2个立即数字节,这些字节是00 00,它是内存目标add [something], al(我忘记了00 modrm在16位寻址模式下编码哪些寄存器了)。

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

相关问题