如果操作数超过 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, imm8
与 cmp 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)。但是 cmpl
和 cmpw
的操作码是相同的;区别仅在于操作数大小(切换为非默认模式值 66
前缀)。
因此,当您的 cmpl
在 16 位模式下为 32 位解码进行汇编时,会留下 2 个字节的立即数。这些字节是 00 00
,这是一个内存目标 add [something], al
(我忘记了 00
modrm 在 16 位寻址模式中编码的寄存器。)这将破坏来自 cmp
.
使用.code16
或命令行选项生成 16 位机器代码。
我写了这个汇编程序:
.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, imm8
与 cmp 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)。但是 cmpl
和 cmpw
的操作码是相同的;区别仅在于操作数大小(切换为非默认模式值 66
前缀)。
因此,当您的 cmpl
在 16 位模式下为 32 位解码进行汇编时,会留下 2 个字节的立即数。这些字节是 00 00
,这是一个内存目标 add [something], al
(我忘记了 00
modrm 在 16 位寻址模式中编码的寄存器。)这将破坏来自 cmp
.
使用.code16
或命令行选项生成 16 位机器代码。