【问题标题】:What causes a CPU hang in this function?是什么导致 CPU 在此函数中挂起?
【发布时间】:2022-01-11 03:57:40
【问题描述】:

我的代码中有一个函数可以加载到 FAT 和根目录中。此函数会导致 PCem 模拟器上的某种 CPU 挂起,但不会导致 QEMU 或 PCjs 等其他模拟器上的 CPU 挂起。如果这不是 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

【问题讨论】:

  • 您是否在代码的其他地方初始化了ds 寄存器?不同的 BIOS 并不总是一致地设置它。
  • 我在代码开头设置了。
  • 也许您应该发布整个代码 (minimal reproducible example) 以便人们对其进行测试,而不必猜测您遗漏的部分。
  • PCem 没有内置调试器吗?如果没有,那么您可以使用一些技巧,例如将唯一字节插入视频内存以指示您的代码已经走了多远。
  • @apersonwithacompiler 不要添加 pastebin 链接。 Stack Overflow 问题必须是自包含的,因此应该包含解决问题所需的所有链接。您链接的粘贴将消失,然后您的问题将毫无用处。

标签: assembly emulation x86-16 bios pc


【解决方案1】:

在 loadfilesystem 函数中,AL 寄存器(要读取的扇区)设置得太高,导致 BIOS 读取超过磁道边界。某些 BIOS 不支持此功能,会导致读取错误。

【讨论】:

    【解决方案2】:
    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 例程,但这一次是来自ALCX 的必要输入不再存在
    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 次。
    • 不使用子程序会保存CALLRET 指令。
    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
    

    【讨论】:

    • 该代码似乎不适用于我的引导扇区。我已经尝试在 pcjs 中运行它并且可以工作,但是 pcem 仍然不喜欢使用新功能启动。我也无法使用 LBA 功能,因为代码是 286。
    • 我还注意到 AH 设置为 0x04 表示未找到该扇区。这意味着什么?
    • @apersonwithacompiler “我也无法使用 LBA 函数,因为代码是 286” 我的答案中的代码仅为 100% 8086。也许 PCem 不喜欢您的 0x28 的 ExtendedBootSignature 实际上应该是 0x29(因为存在 VolumeLabelFileSystem 字段)。我不使用 PCem,事实上它已经停产了。
    • @apersonwithacompiler 您需要准确找出代码出错的地方。将代码减少到绝对最小值并尝试读取磁盘上的第二个扇区,仅此而已。看看这是否也会给你错误AH=4。 ps 在您的帖子中添加这样的尝试,以便我们进行验证。
    • 读取磁盘时扇区值过大导致崩溃。但是,LBA 到 CHS 功能在其他支持这种媒体类型的模拟器上是兼容的。这是因为 PCem 设置不正确吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-10-28
    • 1970-01-01
    • 1970-01-01
    • 2022-07-22
    • 2017-11-18
    • 1970-01-01
    • 2017-12-25
    相关资源
    最近更新 更多