You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

汇编读取磁盘十六进制值打印异常问题排查与解决

十六进制打印函数重复旧值的bug修复

我在实现Nick Blundell所著《从零开始写简单操作系统》中32-33页的例程时,遇到了打印异常问题:

  • 预期输出:0x19890x0604
  • 实际输出:0x19890x1604

进一步测试发现,当待打印值的高位为0时,该位置会被前一次打印的高位值覆盖;若高位非0则无此问题。

原代码

org 0x7C00 ; BIOS loads boot sector to 0x7C00
bits 16 ; 16 bit mode
%define ENDL 0x0D, 0x0A ; new line characters
hex_buffer: db '0x0000', 0 ; buffer to store hex string

start:
    jmp main ; jump to main

; load DH sectors to ES:BX from drive DL
; Output: None
disk_load :
    push dx ; Store DX on stack so later we can recall
    ; how many sectors were request to be read ,
    ; even if it is altered in the meantime
    mov ah , 0x02 ; BIOS read sector function
    mov al , dh ; Read DH sectors
    mov ch , 0x00 ; Select cylinder 0
    mov dh , 0x00 ; Select head 0
    mov cl , 0x02 ; Start reading from second sector ( i.e.
    ; after the boot sector )
    int 0x13 ; BIOS interrupt

    ; check for error
    jc disk_error ; Jump if error ( i.e. carry flag set )
    pop dx ; Restore DX from the stack
    cmp dh , al ; if AL ( sectors read ) != DH ( sectors expected )
    jne disk_error ; display error message
    
    ; if no error, print success message
    lea si, [disk_success_msg] ; load address of disk_success_msg to SI
    call print ; call print function

    ret


; print string
; Input: SI = pointer to string
; Output: None
;
print:
    pusha ; save registers
.loop:
    lodsb ; load byte from [SI] to AL and increment SI
    or al, al ; check if AL is 0 (end of string)
    jz .done ; if AL is 0, jump to done

    mov ah, 0x0E ; teletype output
    int 0x10 ; call BIOS interrupt

    jmp .loop ; loop

.done:
    popa ; restore registers
    ret ; return

; print hex value 
; Input: DX = value to print
; Output: None
;
print_hex:
    pusha
    mov bx, hex_buffer ; store pointer to hex value
    add bx, 0x5     ; move pointer to end of string

loop:
    mov cl, dl  ; copy dx  value into cl
    cmp cl, 0   ; check if 0
    je end_loop ; if 0 then we're done

    and cl, 0xF ; mask first half byte
    cmp cl, 0xA ; compare to A
    jl lt_A     ; if <  A go to lt_A
    jmp gte_A   ; if >= A go to gte_A

lt_A: ; add 48 
    add cl, 48  ; add 48 to bring it to ascii '0'
    jmp end_cmp ; go to end of this code block
gte_A: ; add 55
    add cl, 55  ; add 55 to bring it to ascii 'A'
    jmp end_cmp ; go to end of this code block

end_cmp:
    mov [bx], cl  ; put value into dereferenced pointer
    sub bx, 1     ; decrement pointer
    shr dx, 4     ; shift right 4 bits to get the next half byte
    jmp loop      ; go back to start of loop

end_loop:
    mov si, hex_buffer   ; print the string pointed to
    call print ; by SI
    popa
    ret


main:
    ; setup data segment
    mov ax, 0 
    mov ds, ax  ; set data segment to 0
    mov es, ax  ; set extra segment to 0

    ; setup stack
    mov ss, ax  ; set stack segment to 0
    mov sp, 0x7C00 ; set stack pointer to 0x7C00

    ; print message
    lea si, [msg] ; load address of msg to SI
    call print ; call print function

    ; load sectors
    mov [BOOT_DRIVE], dl ; save boot drive

    mov bp, 0x8000 ; base pointer to 0x8000
    mov sp, bp ; stack pointer to 0x8000

    mov bx, 0x9000 ; buffer address
    mov dh, 5 ; read 5 sectors
    mov dl, [BOOT_DRIVE] ; boot drive
    call disk_load ; call disk_load function

    mov dx , [0x9000] ; Print out the first loaded word, which
    call print_hex ; we expect to be 0x1989 , stored
    ; at address 0x9000

    mov dx , [0x9000 + 512] ; Also, print the first word from the second
    call print_hex ; 2nd loaded sector : should be 0x0604


end:
    hlt ; halt the CPU
    jmp end ; loop forever

msg: db "System booting", ENDL, 0 ; message to print



disk_error_msg: db "Disk error", ENDL, 0 ; disk error message

disk_success_msg: db "Disk read successfully", ENDL, 0 ; disk success message

disk_error:
    lea si, [disk_error_msg] ; load address of disk_error_msg to SI
    call print ; call print function
    jmp end ; loop forever

; Global variables
BOOT_DRIVE : db 0 ; boot drive

times 510-($-$$) db 0 ; fill the rest of sector with 0s
dw 0xAA55 ; boot signature

; We know that BIOS will load only the first 512 - byte sector from the disk ,
; so if we purposely add a few more sectors to our code by repeating some
; familiar numbers , we can prove to ourselfs that we actually loaded those
; additional two sectors from the disk we booted from.
times 256 dw 0x1989
times 256 dw 0x0604

问题原因

print_hex函数使用全局的hex_buffer存储转换后的十六进制字符串,但每次调用时没有重置缓冲区。第一次打印0x1989后,缓冲区的高位字节被设置为'1';第二次打印0x0604时,由于值的高位是0,循环会提前结束,缓冲区中残留的旧值'1'没有被覆盖,导致输出错误。

修复后的代码

修改print_hex函数,在每次转换前重置hex_buffer为初始状态'0x0000':

print_hex:
    pusha
    mov bx, hex_buffer ; store pointer to hex value
    add bx, 0x5     ; move pointer to end of string
    ; 重置hex_buffer为初始的'0x0000',避免残留旧值
    mov byte [hex_buffer + 2], 48 
    mov byte [hex_buffer + 3], 48 
    mov byte [hex_buffer + 4], 48 
    mov byte [hex_buffer + 5], 48 
    
loop:
    mov cl, dl  ; copy dx  value into cl
    cmp cl, 0   ; check if 0
    je end_loop ; if 0 then we're done

    and cl, 0xF ; mask first half byte
    cmp cl, 0xA ; compare to A
    jl lt_A     ; if <  A go to lt_A
    jmp gte_A   ; if >= A go to gte_A

lt_A: ; add 48 
    add cl, 48  ; add 48 to bring it to ascii '0'
    jmp end_cmp ; go to end of this code block
gte_A: ; add 55
    add cl, 55  ; add 55 to bring it to ascii 'A'
    jmp end_cmp ; go to end of this code block

end_cmp:
    mov [bx], cl  ; put value into dereferenced pointer
    sub bx, 1     ; decrement pointer
    shr dx, 4     ; shift right 4 bits to get the next half byte
    jmp loop      ; go back to start of loop

end_loop:
    mov si, hex_buffer   ; print the string pointed to
    call print ; by SI
    popa
    ret

添加重置缓冲区的代码后,每次打印前缓冲区都会被清空为初始状态,问题得到解决。

内容的提问来源于stack exchange,提问作者Akizuki Kagurazaka

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 10:59:50