是什么导致 CPU 在此函数中挂起?
What causes a CPU hang in this function?
我的代码中有一个函数加载到 FAT 和根目录中。此函数会导致某种 CPU 在 PCem 模拟器上挂起,但不会在 QEMU 或 PCjs 等其他模拟器上挂起。如果这不是 PCem 的错误,那么我的程序为什么会这样?
FAT加载函数:
loadfilesystem:
mov ax,0x0050
mov word [fatseg],ax
mov es,ax
xor bx,bx
mov cx,word [reserved_sects] ;start of fat
mov ax,word [sects_per_fat]
call readsectors ;read fat
;calculate start of root directory on disk
mov ax,word [sects_per_fat]
mov bl,byte [num_fats]
mul bl
add ax,[reserved_sects]
mov cx,ax
push cx ;1
;also calculate where in memory to put the data
xor dx,dx
div bx ;only 1 fat is loaded
mov cl,5
shl ax,cl
add ax,0x50
mov word [rootdirectoryseg],ax
mov es,ax ;the segment
xor bx,bx
mov ax,word [num_rootentries] ;get size of root directory
mov cl,4
shr ax,cl
pop cx ;1
push ax ;1 save size of root directory
call readsectors
“读取扇区”函数
readsectors: ;input: cx for lba, al for sectors to read, es:bx for buffer and dl for drive numbers
call lbatochs ;convert lba to chs
mov ah,0x02 ;read sectors
mov dl,byte [drive_num] ;load drive number
clc ;clear carry for error checking
int 13h
jc short readsectorerror ;error
ret ;success
整个计划:
cpu 8086
bits 16
jmp short bootstart ;fat 12 entrypoint code
nop
;fat 12 bpb
oem_label db "OS_BOOT " ;oem label (8 bytes)
bytes_per_sect dw 512 ;bytes per sector
sects_per_cluster db 1 ;sectors per cluster
reserved_sects dw 1 ;reserved sectors
num_fats db 2 ;num of fats
num_rootentries dw 320 ;num of root entries
sect_count dw 320 ;sector count
media_type db 0xfe ;media type (0xfe = 5.25 inch, 160kb)
sects_per_fat dw 1 ;sects per fat
sects_per_track dw 8 ;sects per track
num_heads dw 1 ;num of heads
hidden_sects dd 0 ;hidden sectors
large_sects dd 0 ;large sectors when the disk has more than 65535 sectors
drive_num dw 0 ;drive number
signature db 0x28 ;floppy signature
volume_id dd 0 ;volumeid
volume_label db "OS_BOOT51/4" ;volume label (11 bytes)
file_system db "FAT12 " ;file system (8 bytes)
bootstart:
cli ;clear interrupts until they can be used
cld ;clear direction flag for text and other
mov ax,0x07c0
mov ds,ax ;data segment initialisation
mov byte [drive_num],dl ;save drive number stored in dl by the bios
mov ax,0x6c0 ;4096 bytes below bootsector
mov ss,ax
mov sp,0x1000
sti
loadfilesystem:
mov ax,0x0050
mov word [fatseg],ax
mov es,ax
xor bx,bx
mov cx,word [reserved_sects] ;start of fat
mov ax,word [sects_per_fat]
call readsectors ;read fat
;calculate start of root directory on disk
mov ax,word [sects_per_fat]
mov bl,byte [num_fats]
mul bl
add ax,[reserved_sects]
mov cx,ax
push cx ;1
;also calculate where in memory to put the data
xor dx,dx
div bx ;only 1 fat is loaded
mov cl,5
shl ax,cl
add ax,0x50
mov word [rootdirectoryseg],ax
mov es,ax ;the segment
xor bx,bx
mov ax,word [num_rootentries] ;get size of root directory
mov cl,4
shr ax,cl
pop cx ;1
push ax ;1 save size of root directory
call readsectors
;now calculate location of the data area
mov ax,word [sects_per_fat]
mov bl,byte [num_fats]
mul bl
mov bx,ax
pop ax ;1 load size of root directory
push ax ;1 save for later but keep ax for a bit
add ax,bx ;add size of fat
add ax,word [reserved_sects] ;add reserved sectors
;we now have the lba of the data area
mov word [datalba],ax ;save it
;we now need to find the base segment to load the data at
pop ax ;1
mov cl,5
shl ax,cl
add ax,[rootdirectoryseg]
mov word [dataseg],ax ;save the base segment
findfile:
mov ax,word [rootdirectoryseg]
mov es,ax ;now at the offset of the root directory table
xor di,di
mov bx,word [num_rootentries] ;number of entries to search through
findfileloop:
mov si,filename
mov cx,11 ;number of bytes the filename is
repe cmpsb
je short filefound
test bx,bx
je bootfailed ;out of retries
mov ax,es
add ax,0x02 ;increase by 2 segments aka 32 bytes
mov es,ax
xor di,di
dec bx ;number of tries left minus one
jmp short findfileloop
filefound:
mov ax,word [es:di+0x0f] ;get cluster number
xor bx,bx ;data load offset
xor di,di ;fat read offset
push ax ;1 save ax
readcluster:
;set buffer for data
mov cx,word [dataseg]
mov es,cx
;load sector
sub ax,2 ;minus 2 clusters
mov cl,[sects_per_cluster]
mul cl
add ax,word [datalba]
mov cx,ax
mov al,byte [sects_per_cluster]
call readsectors
;increase buffer
mov al,byte [sects_per_cluster]
mov ah,0
mov cl,9
shl ax,cl
add bx,ax
pop ax ;1 restore ax
;set buffer for fat
mov cx,[fatseg]
mov es,cx
mov cl,3 ;multiply by three
mul cl
shr ax,1 ;divide by two
mov di,ax
mov ax,word [es:di] ;get cluster
test al,1 ;even or odd cluster
jnz short evenclus
oddclus:
mov cl,4
shr ax,cl
jmp short evaluatecluster
evenclus:
and ax,0x0fff
evaluatecluster:
cmp ax,0x0ff8 ;end of chain
jae short finishboot
;do nothing to cluster and load
push ax
jmp short readcluster
finishboot:
;this effectively jumps to a pointer
mov ax,word [dataseg]
mov ds,ax
push ax ;push segment
xor ax,ax
push ax ;push offset
retf ;return to offset and segment on stack
readsectors: ;input: cx for lba, al for sectors to read, es:bx for buffer and dl for drive numbers
call lbatochs ;convert lba to chs
mov ah,0x02 ;read sectors
mov dl,byte [drive_num] ;load drive number
clc ;clear carry for error checking
int 13h
jc short readsectorerror ;error
ret ;success
readsectorerror:
pop dx ;1 clean out stack if error
call resetdrive
jmp short readsectors
lbatochs: ;input: cx for lba, output: cx for cylinder and sector, and dh for head
push ax ;1 save for later
push bx ;2 save bx
;find temp variable
xor dx,dx
mov ax,cx ;ax now has lba
push ax ;3 save lba
mov bx,word [sects_per_track] ;sectors per track
div bx ;ax is now temp
;cylinder
push ax ;4 save temp
xor dx,dx
mov bx,word [num_heads] ;number of heads
div bx ;ax is now cylinder
mov cx,ax ;cx stores cylinder
pop ax ;4 retrieve temp
;cx is now cylinder
;head
push dx ;4 heads already in dx
;sector
pop dx ;4 pop dx to get stack value underneath it
pop ax ;3 retrieve lba
push dx ;3 push dx back on
push cx ;4 save cylinder
xor dx,dx
mov word bx,[sects_per_track]
div bx
inc dx ;dx now has sectors
mov bx,dx ;now bx has sectors
pop cx ;4
pop dx ;3
;put params together
mov ch,cl ;cylinder in ch
mov cl,bl ;sector in cl
mov dh,dl ;head in dh
mov dl,0 ;erase dl
pop bx ;2 load old bx
pop ax ;1 load old ax
ret
resetdrive:
mov byte dl,[drive_num] ;get drive number
mov ah,0x00 ;reset disk interrupt
int 13h
jc bootfailed
ret
bootfailed:
mov si,bootfailmsg
call printstring
jmp hangcpu
printstring: ;video mode is set by bios so no need to set it
lodsb ;load byte from si into al
test al,al ;compare al with 0
jz return ;jump if zero to return
mov bx,0x0007 ;page = bh, color = bl
mov ah,0xe ;type char interrupt
int 10h
jmp short printstring ;go back to start
return:
ret ;return from subroutine
hangcpu:
hlt ;dont run cpu unless interrupt
jmp short hangcpu
;variables for booting
bootfailmsg db "FAILURE!",0
filename db "BOOT BIN"
rootdirectoryseg dw 0
fatseg dw 0
dataseg dw 0
datalba dw 0
times 510-($-$$) db 0
bootsignature dw 0xaa55
readsectors: ;input: cx for lba, al for sectors to read, es:bx for buffer and dl for drive numbers
call lbatochs ;convert lba to chs
mov ah,0x02 ;read sectors
mov dl,byte [drive_num] ;load drive number
clc ;clear carry for error checking
int 13h
jc short readsectorerror ;error
ret ;success
readsectorerror:
pop dx ;1 clean out stack if error
call resetdrive
jmp short readsectors
resetdrive:
mov byte dl,[drive_num] ;get drive number
mov ah,0x00 ;reset disk interrupt
int 13h
jc bootfailed
ret
经常需要重新读取扇区。这很正常,但是在您的 readsectors 代码中有几个错误:
- 当 BIOS.ReadSector 函数 02h 失败时,代码转到 readsectorerror,其中有一个不匹配的
pop dx
(早期编辑遗留的)无法返回主程序。
- 当 resetdrive 代码成功重置驱动器时,代码会重新启动 readsectors 例程,但是这次来自
AL
和 CX
的必要输入不再存在 。
readsectors: ;input: cx LBA, al Sectors to read, es:bx Buffer
push ax
push cx
call lbatochs ; -> CH CL DH
mov dl, [drive_num]
clc ;clear carry for error checking
mov ah, 0x02 ;read sectors
int 13h
jnc readsectorOK
mov ah, 0x00 ;reset disk
int 13h
pop cx
pop ax
jnc readsectors
jmp bootfailed
readsectorOK:
pop cx
pop ax
ret
lbatochs 例程有几个冗余指令,太复杂了!
知道这个引导扇区位于 5.25" 160KB 单面双密度软盘上,您可以大大缩短代码(我相信这是您的目标)。
- 只有1个头;因此除以 NumberOfHeads 将是多余的。
- SectorsPerTrack只有8个;不需要除法 - 只需右移 3 次。
- 不使用子程序可以节省
CALL
和 RET
指令。
readsectors: ;input: cx LBA, al Sectors to read, es:bx Buffer
push ax
push cx
mov dx, cx ; LBA
shr dx, 1
shr dx, 1
shr dx, 1 ; -> DH is Head == 0
and cx, 7
inc cx ; -> CL is Sector
mov ch, dl ; -> CH is Cylinder
mov dl, [drive_num]
clc ;clear carry for error checking
mov ah, 0x02 ;read sectors
int 13h
jnc readsectorOK
mov ah, 0x00 ;reset disk
int 13h
pop cx
pop ax
jnc readsectors
jmp bootfailed
readsectorOK:
pop cx
pop ax
ret
在加载文件系统函数中,AL 寄存器(要读取的扇区)设置得太高,导致 BIOS 读取超过磁道边界。这不受某些 BIOS 的支持,会导致读取错误。
我的代码中有一个函数加载到 FAT 和根目录中。此函数会导致某种 CPU 在 PCem 模拟器上挂起,但不会在 QEMU 或 PCjs 等其他模拟器上挂起。如果这不是 PCem 的错误,那么我的程序为什么会这样?
FAT加载函数:
loadfilesystem:
mov ax,0x0050
mov word [fatseg],ax
mov es,ax
xor bx,bx
mov cx,word [reserved_sects] ;start of fat
mov ax,word [sects_per_fat]
call readsectors ;read fat
;calculate start of root directory on disk
mov ax,word [sects_per_fat]
mov bl,byte [num_fats]
mul bl
add ax,[reserved_sects]
mov cx,ax
push cx ;1
;also calculate where in memory to put the data
xor dx,dx
div bx ;only 1 fat is loaded
mov cl,5
shl ax,cl
add ax,0x50
mov word [rootdirectoryseg],ax
mov es,ax ;the segment
xor bx,bx
mov ax,word [num_rootentries] ;get size of root directory
mov cl,4
shr ax,cl
pop cx ;1
push ax ;1 save size of root directory
call readsectors
“读取扇区”函数
readsectors: ;input: cx for lba, al for sectors to read, es:bx for buffer and dl for drive numbers
call lbatochs ;convert lba to chs
mov ah,0x02 ;read sectors
mov dl,byte [drive_num] ;load drive number
clc ;clear carry for error checking
int 13h
jc short readsectorerror ;error
ret ;success
整个计划:
cpu 8086
bits 16
jmp short bootstart ;fat 12 entrypoint code
nop
;fat 12 bpb
oem_label db "OS_BOOT " ;oem label (8 bytes)
bytes_per_sect dw 512 ;bytes per sector
sects_per_cluster db 1 ;sectors per cluster
reserved_sects dw 1 ;reserved sectors
num_fats db 2 ;num of fats
num_rootentries dw 320 ;num of root entries
sect_count dw 320 ;sector count
media_type db 0xfe ;media type (0xfe = 5.25 inch, 160kb)
sects_per_fat dw 1 ;sects per fat
sects_per_track dw 8 ;sects per track
num_heads dw 1 ;num of heads
hidden_sects dd 0 ;hidden sectors
large_sects dd 0 ;large sectors when the disk has more than 65535 sectors
drive_num dw 0 ;drive number
signature db 0x28 ;floppy signature
volume_id dd 0 ;volumeid
volume_label db "OS_BOOT51/4" ;volume label (11 bytes)
file_system db "FAT12 " ;file system (8 bytes)
bootstart:
cli ;clear interrupts until they can be used
cld ;clear direction flag for text and other
mov ax,0x07c0
mov ds,ax ;data segment initialisation
mov byte [drive_num],dl ;save drive number stored in dl by the bios
mov ax,0x6c0 ;4096 bytes below bootsector
mov ss,ax
mov sp,0x1000
sti
loadfilesystem:
mov ax,0x0050
mov word [fatseg],ax
mov es,ax
xor bx,bx
mov cx,word [reserved_sects] ;start of fat
mov ax,word [sects_per_fat]
call readsectors ;read fat
;calculate start of root directory on disk
mov ax,word [sects_per_fat]
mov bl,byte [num_fats]
mul bl
add ax,[reserved_sects]
mov cx,ax
push cx ;1
;also calculate where in memory to put the data
xor dx,dx
div bx ;only 1 fat is loaded
mov cl,5
shl ax,cl
add ax,0x50
mov word [rootdirectoryseg],ax
mov es,ax ;the segment
xor bx,bx
mov ax,word [num_rootentries] ;get size of root directory
mov cl,4
shr ax,cl
pop cx ;1
push ax ;1 save size of root directory
call readsectors
;now calculate location of the data area
mov ax,word [sects_per_fat]
mov bl,byte [num_fats]
mul bl
mov bx,ax
pop ax ;1 load size of root directory
push ax ;1 save for later but keep ax for a bit
add ax,bx ;add size of fat
add ax,word [reserved_sects] ;add reserved sectors
;we now have the lba of the data area
mov word [datalba],ax ;save it
;we now need to find the base segment to load the data at
pop ax ;1
mov cl,5
shl ax,cl
add ax,[rootdirectoryseg]
mov word [dataseg],ax ;save the base segment
findfile:
mov ax,word [rootdirectoryseg]
mov es,ax ;now at the offset of the root directory table
xor di,di
mov bx,word [num_rootentries] ;number of entries to search through
findfileloop:
mov si,filename
mov cx,11 ;number of bytes the filename is
repe cmpsb
je short filefound
test bx,bx
je bootfailed ;out of retries
mov ax,es
add ax,0x02 ;increase by 2 segments aka 32 bytes
mov es,ax
xor di,di
dec bx ;number of tries left minus one
jmp short findfileloop
filefound:
mov ax,word [es:di+0x0f] ;get cluster number
xor bx,bx ;data load offset
xor di,di ;fat read offset
push ax ;1 save ax
readcluster:
;set buffer for data
mov cx,word [dataseg]
mov es,cx
;load sector
sub ax,2 ;minus 2 clusters
mov cl,[sects_per_cluster]
mul cl
add ax,word [datalba]
mov cx,ax
mov al,byte [sects_per_cluster]
call readsectors
;increase buffer
mov al,byte [sects_per_cluster]
mov ah,0
mov cl,9
shl ax,cl
add bx,ax
pop ax ;1 restore ax
;set buffer for fat
mov cx,[fatseg]
mov es,cx
mov cl,3 ;multiply by three
mul cl
shr ax,1 ;divide by two
mov di,ax
mov ax,word [es:di] ;get cluster
test al,1 ;even or odd cluster
jnz short evenclus
oddclus:
mov cl,4
shr ax,cl
jmp short evaluatecluster
evenclus:
and ax,0x0fff
evaluatecluster:
cmp ax,0x0ff8 ;end of chain
jae short finishboot
;do nothing to cluster and load
push ax
jmp short readcluster
finishboot:
;this effectively jumps to a pointer
mov ax,word [dataseg]
mov ds,ax
push ax ;push segment
xor ax,ax
push ax ;push offset
retf ;return to offset and segment on stack
readsectors: ;input: cx for lba, al for sectors to read, es:bx for buffer and dl for drive numbers
call lbatochs ;convert lba to chs
mov ah,0x02 ;read sectors
mov dl,byte [drive_num] ;load drive number
clc ;clear carry for error checking
int 13h
jc short readsectorerror ;error
ret ;success
readsectorerror:
pop dx ;1 clean out stack if error
call resetdrive
jmp short readsectors
lbatochs: ;input: cx for lba, output: cx for cylinder and sector, and dh for head
push ax ;1 save for later
push bx ;2 save bx
;find temp variable
xor dx,dx
mov ax,cx ;ax now has lba
push ax ;3 save lba
mov bx,word [sects_per_track] ;sectors per track
div bx ;ax is now temp
;cylinder
push ax ;4 save temp
xor dx,dx
mov bx,word [num_heads] ;number of heads
div bx ;ax is now cylinder
mov cx,ax ;cx stores cylinder
pop ax ;4 retrieve temp
;cx is now cylinder
;head
push dx ;4 heads already in dx
;sector
pop dx ;4 pop dx to get stack value underneath it
pop ax ;3 retrieve lba
push dx ;3 push dx back on
push cx ;4 save cylinder
xor dx,dx
mov word bx,[sects_per_track]
div bx
inc dx ;dx now has sectors
mov bx,dx ;now bx has sectors
pop cx ;4
pop dx ;3
;put params together
mov ch,cl ;cylinder in ch
mov cl,bl ;sector in cl
mov dh,dl ;head in dh
mov dl,0 ;erase dl
pop bx ;2 load old bx
pop ax ;1 load old ax
ret
resetdrive:
mov byte dl,[drive_num] ;get drive number
mov ah,0x00 ;reset disk interrupt
int 13h
jc bootfailed
ret
bootfailed:
mov si,bootfailmsg
call printstring
jmp hangcpu
printstring: ;video mode is set by bios so no need to set it
lodsb ;load byte from si into al
test al,al ;compare al with 0
jz return ;jump if zero to return
mov bx,0x0007 ;page = bh, color = bl
mov ah,0xe ;type char interrupt
int 10h
jmp short printstring ;go back to start
return:
ret ;return from subroutine
hangcpu:
hlt ;dont run cpu unless interrupt
jmp short hangcpu
;variables for booting
bootfailmsg db "FAILURE!",0
filename db "BOOT BIN"
rootdirectoryseg dw 0
fatseg dw 0
dataseg dw 0
datalba dw 0
times 510-($-$$) db 0
bootsignature dw 0xaa55
readsectors: ;input: cx for lba, al for sectors to read, es:bx for buffer and dl for drive numbers call lbatochs ;convert lba to chs mov ah,0x02 ;read sectors mov dl,byte [drive_num] ;load drive number clc ;clear carry for error checking int 13h jc short readsectorerror ;error ret ;success readsectorerror: pop dx ;1 clean out stack if error call resetdrive jmp short readsectors resetdrive: mov byte dl,[drive_num] ;get drive number mov ah,0x00 ;reset disk interrupt int 13h jc bootfailed ret
经常需要重新读取扇区。这很正常,但是在您的 readsectors 代码中有几个错误:
- 当 BIOS.ReadSector 函数 02h 失败时,代码转到 readsectorerror,其中有一个不匹配的
pop dx
(早期编辑遗留的)无法返回主程序。 - 当 resetdrive 代码成功重置驱动器时,代码会重新启动 readsectors 例程,但是这次来自
AL
和CX
的必要输入不再存在 。
readsectors: ;input: cx LBA, al Sectors to read, es:bx Buffer
push ax
push cx
call lbatochs ; -> CH CL DH
mov dl, [drive_num]
clc ;clear carry for error checking
mov ah, 0x02 ;read sectors
int 13h
jnc readsectorOK
mov ah, 0x00 ;reset disk
int 13h
pop cx
pop ax
jnc readsectors
jmp bootfailed
readsectorOK:
pop cx
pop ax
ret
lbatochs 例程有几个冗余指令,太复杂了!
知道这个引导扇区位于 5.25" 160KB 单面双密度软盘上,您可以大大缩短代码(我相信这是您的目标)。
- 只有1个头;因此除以 NumberOfHeads 将是多余的。
- SectorsPerTrack只有8个;不需要除法 - 只需右移 3 次。
- 不使用子程序可以节省
CALL
和RET
指令。
readsectors: ;input: cx LBA, al Sectors to read, es:bx Buffer
push ax
push cx
mov dx, cx ; LBA
shr dx, 1
shr dx, 1
shr dx, 1 ; -> DH is Head == 0
and cx, 7
inc cx ; -> CL is Sector
mov ch, dl ; -> CH is Cylinder
mov dl, [drive_num]
clc ;clear carry for error checking
mov ah, 0x02 ;read sectors
int 13h
jnc readsectorOK
mov ah, 0x00 ;reset disk
int 13h
pop cx
pop ax
jnc readsectors
jmp bootfailed
readsectorOK:
pop cx
pop ax
ret
在加载文件系统函数中,AL 寄存器(要读取的扇区)设置得太高,导致 BIOS 读取超过磁道边界。这不受某些 BIOS 的支持,会导致读取错误。