Pet*_*ith 4 x86 assembly flags eflags x86-emulation
我正在构建一个 x86 模拟器,并使用这个在线 x86 沙箱来检查我的工作。在我的模拟器中,运行以下代码:
mov AX, 0xFFFF
add AX, AX
Run Code Online (Sandbox Code Playgroud)
...将 AX 设置为 0xFFFE 并将溢出标志设置为 0。
在沙箱中,相同的代码将溢出标志设置为 1。这对我来说没有意义,因为我读过的 OF 的每个定义都解释说,如果 1)源操作数和目标操作数具有相同的符号,则它会被设置,以及 2)结果的符号 != 源/目标操作数的符号。
这里,我们将 0b1111 1111 1111 1111 添加到 0b1111 1111 1111 1111。结果是 0b1111 1111 1111 1110。源、目标和结果的符号位均为 1。我们是否将其解释为 0xFFFF + 0xFFFF = 0xFFFE (隐式为 0x1FFFE),或 -1 + -1 = -2,符号不变。
这个沙箱实现是错误的吗?如果没有,你能帮我理解我所缺少的吗?
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
Run Code Online (Sandbox Code Playgroud)
你说得对。沙盒错误。
我在尝试之前就预料到了这一点,因为逻辑上(-1 + -1 = -2)并且没有发生有符号溢出。V 是有符号溢出。
模拟器是错误的,源代码显示了完全错误的实现,只是将OF设置为与CF相同的条件。这些标志是分开的有原因的!此外,只要设置 CF,它们就会设置 SF,即使 16 位结果的最高位为零。
它是开源的,https://github.com/YJDoc2/8086-Emulator-Web是 8086-Emulator 库的前端,由编写前端的同一个人用 Rust 编写(编译为 wasm)。
拥有单步调试和漂亮的 GUI 的开源模拟器真是太好了,但如果他们犯了这个非常基本的错误,谁知道可能还存在哪些其他错误?在它通过一些压力测试(尝试检查许多输入值的每条指令的所有结果)之前,我不会信任它或推荐它给任何人使用。我听说过针对各种 8 位 ISA 的此类测试,我假设它们存在于 8086 中。
我查看了他们的 8086 仿真器库,幸运的是add
在第一个文件中找到了似乎可能的仿真代码src/lib/instructions/arithmetic.rs
。
该代码根据相同的条件设置 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
}
Run Code Online (Sandbox Code Playgroud)
Rust 有很多方法可以让他们正确地做到这一点,比如(a as i16).wrapping_add(b as i16) == a as i16 as i32 + b as i16 as i32
. 或者在相加之前将两边符号扩展至 32 位以获得真实值,然后与包装的 16 位加法进行比较,或者将真实值截断回 16 位。
或者(a as i16).checked_add(b)
并检查结果是否不是None
或(a as i16).overflowing_add(b)
以获得单独的i16
和bool
输出。(https://doc.rust-lang.org/std/primitive.i16.html#method.overflowing_add)。i16 输出可以轻松地用于更简单地获得 SF,如signed_sum < 0
.
另请参阅http://teaching.idallen.com/dat2343/10f/notes/040_overflow.txt回复:了解进位(无符号溢出)与有符号溢出的更多方法。对于加法,只有当两个输入具有相同符号且结果具有相反符号时才会发生有符号溢出。或者,始终适用于加法和减法的一种简单方法是对输入进行符号扩展并执行更广泛的运算,并检查完整结果是否可以缩小到 8 或 16 位而不更改值。(例如res as i16 as i32
,从 16 位重做符号扩展,以获得 32 位值与完整结果进行比较。)
SF条件也是错误的。他们将 SF 设置0x8000 + 0x8000
为 0,因为他们正在比较0x10000u >= 0x8000u
. 他们只需要测试该位,例如(res >> 15) & 1
,而不是任何可能有进位的更高位。
他们使用更广泛的算术的技巧确实使他们很容易实现,即使adc
使用正确的(我认为)进位标志处理,但同样错误地处理 OF。
(adc
仅使用与 相同宽度的操作进行模拟adc
非常困难,因为通常的 unsigned 进位公式a+b < a
不适用于a+b + CF < a
;如果您使用包装 16,则将 0xFFFFu + 1u 添加到某些内容与添加零相同位加法,所以你必须单独检查每个加法步骤。除非你有更高的位来保存该进位。或者如果你有语言或硬件支持,Rustcarrying_add(self, rhs: i16, carry: bool) -> (i16, bool)
在新的 API 中公开了这些支持,但该 API 仍然只在夜间构建中.那i64
将完全解决问题。)