在無系統日誌、無事件檢視器的 DOS 年代,磁碟讀寫錯誤 (扇區損壞、磁頭定位失敗、CRC 錯誤) 往往只會導致程式掛起或回傳錯誤碼,使用者難以即時感知。DSKWATCH.ASM (Steve Holzner, PC Magazine 1985) 實作一個 TSR:攔截 INT 13h (BIOS 磁碟服務) 向量,在原始處理常式返回後檢查 Carry Flag (CF=1 表示錯誤),若發生錯誤則在螢幕固定位置 (右上角) 顯示錯誤類型 (無回應、定位失敗、NEC 錯誤、CRC 錯誤、DMA 溢位、扇區未找到、位址標記遺失、寫保護、未知錯誤)。程式展示了中斷鏈結、螢幕直接寫入 (文字/圖形模式自動偵測)、錯誤碼解碼、TSR 常駐等實用技術。
load_watch:
INT 13h*4 保存原始向量至 old_disk_intdisk_watchINT 10h, AH=15h 取得當前視訊模式,計算螢幕右上角起始位置 (first_position)screen_seg_offset = 0 或 8000h)INT 27h 常駐 (保留初始化碼前所有區塊)disk_watch:
PUSHF / CALL old_disk_int 執行原始 BIOS 磁碟服務flags,檢查 CFAH 錯誤碼、顯示訊息、恢復暫存器/旗標、返回msg_part_1 db 'Disk error: '
msg_part_2 db 'No response Failed Seek NEC Error '
db 'Bad CRC SeenDMA Overrun Impos Sector'
db 'No Addr MarkW. ProtectedErr Unknown '
msg_part_2 為 12 字元寬度固定欄位訊息表,對應錯誤碼 (AH) 的位元權重:
| Bit | 錯誤碼 (AH) | 訊息 |
|---|---|---|
| 0 | 01h | No response |
| 1 | 02h | Failed Seek |
| 2 | 04h | NEC Error |
| 3 | 08h | Bad CRC |
| 4 | 10h | DMA Overrun |
| 5 | 20h | Impos Sector |
| 6 | 40h | No Addr Mark |
| 7 | 80h | W. Protected |
解碼迴圈:MOV DX, 80h / MOV CX, 7 / CMP AH, DH / JE FOUND / ADD SI, 12 / SHR DH, 1 / LOOP — 從最高位 (80h) 向下測試,匹配則顯示對應 12 字元訊息。若無匹配 (AH=03h 等非單一位錯誤碼) 顯示 "Unknown "。
write_to_screen proc near
mov cx, 12
w_loop:
movs es:byte ptr[di], cs:[si] ; 複製字元
mov al, 7
mov es:[di], al ; 屬性 = 白底黑字? (07h)
inc di
loop w_loop
ret
使用 MOVS 字串指令高效複製字元+屬性。屬性 `07h` (白字黑底) 在單色/彩色文字模式通用。
fin:
pop ret_addr_word ; 彈出原 CALL 返回位址 (IP)
pop ret_addr_word[2] ; 彈出原 CALL 返回位址 (CS)
add sp, 2 ; 跳過原 PUSHF 推入的旗標
push flags ; 推入我們保存的旗標
popf ; 恢復旗標 (含 CF=1)
jmp ret_addr ; 遠端跳轉返回原呼叫者
關鍵技巧:原始 `CALL old_disk_int` 推入 `CS:IP`,`PUSHF` 推入旗標。我們需保持原呼叫者看到 CF=1 (錯誤),故在返回前移除原旗標、推入保存的旗標、再 `JMP` 返回。此過程不破壞任何暫存器 (已全部 push/pop)。
mov ah, 15
int 10h ; AL=模式, AH=列數, BH=頁面
sub ah, 25 ; 從右邊倒數 25 字元
shl ah, 1 ; *2 (字元+屬性)
mov byte ptr first_position, ah
test al, 4 ; Bit 2=1 表示單色模式 (Mode 7)
jnz exit
mov screen_seg_offset, 8000h ; 彩色文字模式基址 B800h - B000h = 8000h
單色模式 (Mode 7) 顯存位於 B000:0000,彩色文字模式 (Mode 2/3) 位於 B800:0000。程式以 `ES` 固定為 `B000h` (screen segment),`screen_seg_offset` 作為額外偏移 (0 或 8000h) 加至 `DI`。
interrupts segment at 0h
org 13h*4
disk_int label dword
interrupts ends
screen segment at 0B000h
screen ends
code_seg segment
assume cs:code_seg
org 0100h
first: jmp load_watch
msg_part_1 db 'Disk error: '
msg_part_2 db 'No response Failed Seek NEC Error '
db 'Bad CRC SeenDMA Overrun Impos Sector'
db 'No Addr MarkW. ProtectedErr Unknown '
first_position dw ?
flags dw ?
screen_seg_offset dw 0
old_disk_int dd ?
ret_addr label dword
ret_addr_word dw 2 dup(?)
disk_watch proc far
assume cs:code_seg
pushf
call old_disk_int
pushf
pop flags
jc error
jmp fin
error:
push ax, cx, dx, di, si, es
lea si, msg_part_1
assume es:screen
mov dx, screen
mov es, dx
mov di, screen_seg_offset
add di, first_position
call write_to_screen
mov dx, 80h
mov cx, 7
e_loop:
cmp ah, dh
je e_found
add si, 12
shr dh, 1
loop e_loop
cmp ah, 3
je e_found
add si, 12
e_found:
call write_to_screen
pop es, si, di, dx, cx, ax
fin:
pop ret_addr_word
pop ret_addr_word[2]
add sp, 2
push flags
popf
jmp ret_addr
disk_watch endp
write_to_screen proc near ... endp
load_watch proc near
assume ds:interrupts
mov ax, interrupts
mov ds, ax
mov ax, disk_int
mov old_disk_int, ax
mov ax, disk_int[2]
mov old_disk_int[2], ax
mov disk_int, offset disk_watch
mov disk_int[2], cs
mov ah, 15
int 10h
sub ah, 25
shl ah, 1
mov byte ptr first_position, ah
test al, 4
jnz exit
mov screen_seg_offset, 8000h
exit:
mov dx, offset load_watch
int 27h
load_watch endp
code_seg ends
end first
DSKWATCH 以約 150 行程式碼,實作了專業級磁碟健康監控工具,其「攔截 → 執行原操作 → 檢查結果 → 解碼錯誤 → 即時顯示 → 透明返回」閉環邏輯,是理解底層錯誤處理、硬體抽象層設計、系統監控代理開發的經典範例。