assembly x86除法例外返回地址

f0ofjuux  于 2022-12-27  发布在  其他
关注(0)|答案(1)|浏览(107)

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

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
raogr8fs

raogr8fs1#

对于#DE异常,Original 8086/8088 does压入 * 下一条指令 * 的地址。

但所有其它x86 CPU都会压入出错的div/idiv指令的起始地址。(至少从386开始;但286很可能与386相同。)

这对于x86来说是正常的:出错指令会将出错指令的地址推入。x86机器码无法可靠地/明确地反向译码,因此设计意图是异常处理程序可检查情况并可能修复它,以及重新执行出错指令。
请参阅Intel x86 - Interrupt Service Routine responsibility,它详细说明了Faults、Traps和Abort之间的区别,甚至还特别提到了int 0和出错div之间的区别。
这对于#PF页面错误很有用,尽管not as realistic对于FP和整数运算异常很有用。则至少报告出错的实际指令。例如,idiv dword [fs: rdi + 0xf1f7f1f7]对于向后反汇编将是不明确的。disp 32中的f7 f1字节是div ecx的编码。您还将我不知道跳转是否直接跳转到FS前缀后的idiv操作码,因此,对于调试和其他可能的目的来说,获得出错指令的实际起始地址(而不是结束地址)肯定是有用的。
当然,int 0(如果IDT允许,且您不在真实的模式下)压入以下指令的CS:[ER]IP,因为它不是在修复情况后可以无故障地重新运行的指令。一般来说,int的工作方式与call类似,都是在之后返回到指令。
8086的行为似乎是一个故意的决定,以更坏的行为为代价来简化硬件。它没有最大指令长度的限制,并且完全避免记住指令的开始,无论是在CPU内部的任何地方(Ken Shirriff在回答 * 8086中的中断、指令指针和指令队列 * 时引用了英特尔的一项专利)。
如果cs rep movsb被外部中断中断,中断返回地址在 final 前缀之前,而不是实际的指令开始。(也就是说,它将恢复为没有cs前缀的rep movsb,如果你按照这个顺序放置前缀,这将是一场灾难。这是最大的“更糟糕的行为”;您可以通过将rep cs movsb放入循环中来解决它。)由于8086没有任何类型的页面错误或可配置的段限制,因此它不能在rep cs movsb或其他rep字符串指令期间发生同步异常,只能发生异步外部中断。
有关8086设计决策的更多猜测,请参见Why do call and jump instruction use a displacement relative to the next instruction, not current?

相关问题