x86除法异常-return地址

x86 division exception-return address

当尝试在 x86 程序集中为引导加载程序编写一些例程时,我遇到了一个错误,当发生除法错误时,程序会陷入无限循环。通过调查,我发现调用 int 0 会正常通过异常处理程序,然后继续执行程序的其余部分。自己写的x86异常处理程序,除法错误异常发生时的return地址就是指令的地址,意味着它只会一直执行除法,一直循环下去。这是正常行为还是 Virtualbox/my cpu 的错误?

org 0x7c00      ;put all label addresses at offset 0x7c00

xor ax, ax      ;set up all segment registers
mov ds, ax
mov ax, 0x9000
mov ss, ax
mov sp, 0x1000
mov ax, 0xB800  ;video text memory starts at this address
mov es, ax

mov ah, 0x00
mov al, 0x02
int 0x10        ;go into 80x25 monochrome text

mov [0x0000], word DivideException
mov [0x0002], word 0x0000

xor di, di
xor bx, bx

;int 0   ;this and the divide CX below will cause a division error exception

mov ax, 0
mov cx, 0 ;when exception is handled it prints out
div cx    ;"a divide by zero error happened 0000:7C2D 0000:7C2F
          ;the first address is the division instruction and the second one is 2 bytes after
          ;when int 0 is uncommented out then it will have the two same addresses
jmp $

ToHex:
push bp
mov bp, sp
push bx

mov ax, word [bp+6]
mov bx, word [bp+4]
add bx, 3
mov cx, 16

.Loop:
xor dx, dx
div cx
add dx, 48
cmp dx, 58
jb .Skip
add dx, 7
.Skip:
mov byte [bx], dl
dec bx
cmp ax, 0
jne .Loop

.Ret:
pop bx
mov sp, bp
pop bp
ret

PrintStr:
push bp
mov bp, sp
push bx

mov bx, word [bp+6]
mov ah, byte [bx]
mov bx, word [bp+4]

.PrintLoop:
mov al, byte [bx]

mov word [es:di], ax
inc di
inc di
inc bx
cmp byte [bx], 0x00
jne .PrintLoop

pop bx
mov sp, bp
pop bp
ret

DivideException:
push bp
mov bp, sp
push bx

push word ColorAttributes1
push word String3
call PrintStr
add sp, 4

push word [bp+4]
push word String1
call ToHex
add sp, 4

push word [bp+2]
push word String2
call ToHex
add sp, 4

push word ColorAttributes1
push word String1
call PrintStr

push ds
mov ds, word [bp+4]
mov bx, word [bp+2]

cmp byte [ds:bx], 0xF7  ;checks if theres a 0xF7 byte at the return address
jne .DontAdd            ;for some reason the return address when calling int 0
add word [bp+2], 2      ;directly is the address after the instruction while
.DontAdd:               ;causing a divide error exception through divsion will
pop ds                  ;put the return address at the division leading to an
                        ;infinite loop
push word [bp+4]
push word String1
call ToHex
add sp, 4

push word [bp+2]
push word String2
call ToHex
add sp, 4

push word ColorAttributes1
push word String1
call PrintStr

add sp, 4

pop bx
mov sp, bp
pop bp
iret



String1: db "0000:";, 0x00
String2: db "0000 ", 0x00
String3: db "a divide by zero error happened ", 0x00
ColorAttributes1: db 0x0F ; first nibble is backround color
                         ;second nibble is foreground


times 2048-2- ($-$$) db 0  ;fills the rest with 0's until 510 bytes
dw 0xAA55               ;magic boot sector number

Original 8086/8088 does#DE个异常推送下面指令的异常。
但是所有其他 x86 CPU 都会推送错误 div/idiv 指令的起始地址。(至少从 386 开始;我不知道 286 做了什么.)

这对于 x86 通常是正常的:错误指令压入错误指令的地址。 x86 机器代码无法 reliably/unambiguously 向后解码,因此设计意图是异常处理程序可以检查情况并可能修复它,并且 re-run 错误指令。

请参阅 ,其中详细说明了故障、陷阱和中止之间的区别,甚至还特别提到了 int 0 和故障 div 之间的区别。

这对于 #PF 页面错误很有用,尽管 对于 FP 和整数算术异常之类的事情。但如果不修复,则至少报告出错的实际指令。例如idiv dword [fs: rdi + 0xf1f7f1f7] 向后反汇编会产生歧义。 disp32 中的 f7 f1 字节是 div ecx 的编码。您也不知道跳转是否直接跳转到 FS 前缀后的 idiv 操作码。因此,拥有错误指令开始的实际地址而不是其结束对于调试和可能的其他目的绝对有用。

int 0(如果您不在实模式下,如果 IDT 允许)推送 CS:[ER]IP 的后续指令,当然,因为它不是可以 re-run情况修复后无故障


8086 的行为可能是以更糟糕的行为为代价来简化硬件的有意决定。它对最大指令长度没有限制,并且 AFAIK 完全避免了必须记住指令的开始。如果cs rep movsb被外部中断中断,interrupt-return地址在final前缀之前,而不是实际的指令开始。 (即它会恢复为 rep movsb 没有 cs 前缀,如果你按那个顺序放置前缀,那将是一场灾难。这是最大的“更糟糕的行为”。)因为 8086 没有任何一种 page-faults 或可配置的 segment-limits,它不能在 rep cs movsb 或其他 rep-string 指令期间发生同步异常,只能异步外部中断。

有关 8086 设计决策的更多猜测,请参阅