assembly 在这个X86模拟器中,为什么将0xFFFF添加到0xFFFF时设置了溢出标志?

xfb7svmp  于 2023-03-18  发布在  其他
关注(0)|答案(2)|浏览(151)

我正在构建一个x86模拟器,我使用this online x86 sandbox来检查我的工作。在我的模拟器中,运行以下代码:

mov AX, 0xFFFF
add AX, AX

...将AX设置为0xFFFE,并将溢出标志设置为0。
在沙箱中,相同的代码将溢出标志设置为1。这对我来说没有意义,因为我读过的OF的每个定义都解释,如果1)源操作数和目标操作数具有相同的符号,且2)结果的符号!=源操作数/目标操作数的符号,则设置溢出标志。
这里,我们将0 b1111 1111 1111 1111与0 b1111 1111 1111相加。结果为0 b1111 1111 1111 1110。源、目标和结果的符号位均为1。我们是否将其解释为0xFFFF + 0xFFFF = 0xFFFE(隐式为0x 1FFFE),或者-1 + -1 = -2,则符号不变。
这个沙盒实现有错吗?如果没有,你能帮助我理解我错过了什么吗?

sg3maiej

sg3maiej1#

Z:\>debug
-a
0DAB:0100 MOV AX, FFFF
0DAB:0103 ADD AX, AX
0DAB:0105
-g 0105
AX=FFFE BX=0000 CX=0000 DX=0000 SP=FFFE BP=0000 SI=0000 DI=0000
DS=0DAB ES=0DAB SS=0DAB IP=0105 NV UP EI NG NZ AC PO CY

你说得对。沙盒错了。
我在尝试之前就预料到了这一点,因为逻辑上(-1 + -1 = -2)没有发生有符号溢出。

qzlgjiam

qzlgjiam2#

模拟器是错误的,源代码显示了一个完全错误的实现,只是用与CF相同的条件设置OF。这些标志是分开的,这是有原因的!而且,每当CF被设置时,它们就设置SF,即使16位结果的最高位是零。
它是开源的,https://github.com/YJDoc2/8086-Emulator-Web是8086模拟器库的前端,由编写前端的同一个人用Rust编写(编译为wasm)。
有一个开源的模拟器可以单步调试,有一个很好的GUI,这是很好的,但是如果他们把这个最基本的东西弄错了,谁知道还会有什么其他的bug呢?我不会相信它,也不会推荐给任何人使用,直到它通过了一些压力测试,尝试检查许多输入值的每条指令的所有结果。我听说过各种8位ISA的这样的测试,我想8086号有。
我看了一下他们的8086-Emulator库,幸运的是在第一个看起来像src/lib/instructions/arithmetic.rs的文件中找到了add的仿真代码。
代码根据相同的条件设置CF和OF:将输入零扩展到32位,然后检查是否大于0xffff的无符号值,这对于进位输出应该有效,但它无法检测0x7fff + anything中的有符号溢出,以及像您这样将两个小负数相加时的假阳性。

pub fn word_add(vm: &mut VM, op1: u16, op2: u16) -> u16 {
    let res = op1 as u32 + op2 as u32;        // 32-bit sum of zero-extended inputs
    set_all_flags(
        vm,
        FlagsToSet {
            zero: res == 0,                      // correct
            overflow: res > u16::MAX as u32,     // BUG
            parity: has_even_parity(res as u8),  // correct, PF is set from the low 8
            sign: res >= 1 << 15,                // BUG
            carry: res > u16::MAX as u32,        // correct
            auxillary: ((op1 & 0xF) + (op2 & 0xF)) > 0xF, // correct, carry-out from the low nibble.  Typo for "auxilliary", though.
        },
    );
    res as u16
}

Rust有很多方法可以得到正确的结果,比如(a as i16).wrapping_add(b as i16) == a as i16 as i32 + b as i16 as i32,或者任何其他方法,在加法得到真值之前,将两边符号扩展到32位,然后与 Package 的16位加法比较,或者将真值截断回16位。
或者(a as i16).checked_add(b),并检查结果不是None(a as i16).overflowing_add(b),以获得单独的i16bool输出(https://doc.rust-lang.org/std/primitive.i16.html#method.overflowing_add)。
另请参见http://teaching.idallen.com/dat2343/10f/notes/040_overflow.txt re:了解结转的更多方式(无符号溢出)与有符号溢出的比较。对于加法,只有当两个输入具有相同的符号,而结果具有相反的符号时,才可能发生有符号溢出。或者,对于加法和减法始终有效的一种简单方法是对输入进行符号扩展,并执行更广泛的运算,并检查整个结果可在不改变值的情况下缩窄回8或16位(例如,res as i16 as i32以从16位重新进行符号扩展,以获得32位值来与整个结果进行比较)。

SF条件也是错误的。他们会在0x8000 + 0x8000上设置SF,这会回零,因为他们在比较0x10000u >= 0x8000u。他们只需要测试该位,比如(res >> 15) & 1,而不是任何可能有进位输出的高位。

他们使用更广泛的算法的技巧确实使他们很容易实现adc,即使是正确的(我认为)进位标志处理,但同样错误的OF处理。
(只使用与adc相同宽度的运算来模拟adc是相当困难的,因为a+b < a的无符号进位公式不适用于a+b + CF < a;把0xFFFFu + 1u加到某个东西上和加0是一样的,如果你使用的是16位加法,那么你必须分别检查每一步加法。除非你有更高的位来保存进位。或者你有语言或硬件支持,Rust在新的API中用carrying_add(self, rhs: i16, carry: bool) -> (i16, bool)暴露了这个问题,而这个API仍然只在夜间构建中使用。对于i64来说,这个问题将完全解决。)

相关问题