我写了这个汇编程序:
.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个字节长。我怀疑这是导致此错误的原因。
1条答案
按热度按时间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, imm8
与cmp 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位机器代码。