當試圖在 x86 程式集中為引導加載程式撰寫一些例程時,我遇到了一個錯誤,當發生除法錯誤時,程式會陷入無限回圈。通過調查,我發現呼叫int 0會正常通過例外處理程式,然后繼續執行程式的其余部分。為 x86 撰寫我自己的例外處理程式,發生除法錯誤例外時的回傳地址是指令的地址,這意味著它將永遠執行除法。這是正常行為還是 Virtualbox/我的 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
uj5u.com熱心網友回復:
原來的 8086/8088 確實推送了下面的例外指令的#DE例外。
但是所有其他 x86 CPU 都會推送錯誤div/idiv指令的起始地址。 (至少從 386 開始;我不知道 286 做了什么。)
一般來說,這對于 x86 來說是正常的:錯誤指令會推送錯誤指令的地址。x86 機器代碼不能可靠/明確地向后解碼,因此設計意圖是例外處理程式可以檢查情況并可能修復它,并重新運行錯誤指令。
請參閱Intel x86 - Interrupt Service Routine responsibility,它分解了 Faults、Trap 和 Aborts 之間的區別,甚至特別提到了和 faulting 之間的int 0區別div。
這對于#PF 頁面錯誤很有用,盡管對于 FP 和整數算術例外等情況并不現實。但如果不修復,那么至少報告故障的實際指令。例如idiv dword [fs: rdi 0xf1f7f1f7],向后拆卸會模棱兩可。f7 f1disp32 中的位元組是div ecx. 您也不會知道跳轉是否直接跳轉到idivFS 前綴之后的操作碼。因此,對于除錯和可能的其他目的來說,獲取錯誤指令開始的實際地址,而不是它的結束,這絕對是有用的。
int 0(如果 IDT 允許,如果您不在實模式下)當然會推送以下指令的 CS:[ER]IP,因為在修復情況后,它不是可以重新運行而不會出現故障的東西。
8086 的行為可能是一個有意的決定,以犧牲更糟糕的行為為代價來簡化硬體。它對最大指令長度沒有限制,AFAIK 完全避免了記住指令的開頭。如果cs rep movsb被外部中斷中斷,則中斷回傳地址在最終前綴之前,而不是實際指令開始。(即它會在rep movsb沒有前綴的情況下恢復cs,如果您按該順序放置前綴,這將是一場災難。這是最大的“更糟糕的行為”。)因為 8086 沒有任何型別的頁面錯誤或可配置的段限制,它不能在rep cs movsb或其他rep-string指令期間發生同步例外,只有異步外部中斷。
請參閱為什么呼叫和跳轉指令使用相對于下一條指令的位移,而不是當前指令?有關 8086 設計決策的更多猜測。
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/424422.html
上一篇:程式可以讀取自己嗎?
