选项 Rom 代码无法使用 Qemu 仿真打印预期的字符串

Option Rom code failing to print intended string using Qemu Emulation

一直在尝试测试使用 FASM 或 NASM 编译的简单 ISA Option Rom 程序,该程序仅打印出简单的 'Hello World' 消息。

问题是在 QEMU 中测试它时,我得到了几个意想不到的字符,而不是打印的字符串。然而,这些属性有效并改变了文本颜色,但在 Google 搜索数小时后无法解决这个问题。

最好的猜测是需要设置 ORG 命令,因为使用 LODSBSI 将错误的内存地址复制到 AL。有什么想法吗??

use16        ; ISA module operates in the 16-bit segment.

DB      55h, 0AAh          ; Boot signature
DB      01h               ; Block size in sectors (200h each)



xor ax, ax ; make it zero
mov ds, ax

start:

mov si, text_string     ; Put string position into SI
call print_string       ; Call our string-printing routine

.bounce:
jmp .bounce                   ; Jump here - infinite loop!

print_string:                   ; Routine: output string in SI to screen

.repeat:
   ;mov ah, 09h             ; int 10h 'print char' function
   ; mov bh, 0x00
   ; mov bl, 0x03
   ; mov cx, 1h

lodsb                   ; Get character from string

or al,al
jz .done

mov ah, 0x0E
int 0x10

; If char is zero, end of string
; int 10h                 ; Otherwise, print it

mov bh, 00h
mov ah, 03h
int 10h
mov ah, 02h
mov bh, 00h
inc dl
int 10h
jmp .repeat

.done:
mov al, 'D'
mov bh,0x00
mov cx,1
mov ah,0ah
int 10h

ret

text_string db 'Hello World!',,0
times 512-($-$$) db 0   

主要问题是您将 DS 初始化为 0。当 BIOS 将控制权交给您的 init 例程时,它将对选项 ROM 位置 +3 执行 FAR CALL。 FAR 调用会将 CS 设置为加载选项 ROM 的段,并将 IP(指令指针)设置为 3。3 是刚好超过签名和大小字节的偏移量。

通过将 DS 设置为零,您将访问与 0x0000 段相关的字符串。您要使用加载选项 ROM 的段。为此,您将 DS 初始化为 CS 寄存器的值。而不是:

xor ax, ax ; make it zero
mov ds, ax

你这样做:

mov ax, cs              ; CS contains segment we are running in
mov ds, ax              ;    so copy it to DS

您还应该使用 CLD 设置字符串指令的方向标志以转发。你不能保证 BIOS 在调用我们的选项 ROM 代码之前设置它。

因为我从来没有写过选项 ROM,而且我找不到任何关于调用约定的特定文档,所以我不确定您是否需要保留所有更改的寄存器。我在自己的电脑上查看了选项 ROM using the ree program under Linux. What I noticed is that they use pusha and popa to save and restore all the general purpose registers and they push/pop to save/restore individual segment registers. It is probably good practice to do this in your own option ROM. One requirement that dates back to the old Phoenix BIOS 是在大小字节之后需要有一个 NEAR JMP 到初始化代码的入口点。

完成选项 ROM 初始化例程后,您 return 使用 retf (FAR return) 返回 BIOS,以便 BIOS 可以继续扫描其他选项 ROM 并完成启动顺序。

我稍微修正了您的代码,因为打印例程中存在一些小故障。此代码应该有效:

use16                       ; ISA module operates in the 16-bit segment.

DB      55h, 0AAh           ; Boot signature
DB      01h                 ; Block size in sectors (200h each)
jmp start                   ; NearJMP part of Phoenix BIOS specification

start:
    pushf                   ; Save the flags as we modify direction bit
    pusha                   ; Save all general purpose registers
    push ds                 ; we modify DS so save it

    cld                     ; Ensure forward string direction
    mov ax, cs              ; CS contains segment we are running in
    mov ds, ax              ;    so copy it to DS

    mov si, text_string     ; Put string position into SI
    call print_string       ; Call our string-printing routine
    pop ds                  ; Restore all registers and flags we saved
    popa
    popf

    retf                    ; Far return to exit option init routine

print_string:               ; Routine: output string in SI to screen
    mov ah, 0eh             ; BIOS tty Print
    xor bx, bx              ; Set display page to 0 (BL)
    jmp .getch
.repeat:
    int 10h                 ; print character
.getch:
    lodsb                   ; Get character from string
    test al,al              ; Have we reached end of string?
    jnz .repeat             ;     if not process next character
.end:
    ret

; String ends with 0dh (Carriage return) and 0ah (linefeed) to
; advance cursor to the beginning of next line
text_string db 'Hello World!', 0dh, 0ah, 0
times 512-($-$$) db 0

注意:该代码使用了仅在 80186+ 处理器上可用的 pusha/popa 指令。如果您的目标是 8086/8088,那么您需要单独推送和弹出您修改的每个寄存器。


也可以不使用 DS 寄存器段并用 CS[=72= 覆盖 LODSB ] 覆盖。可以修改为cs lodsb。通过这样做,您不需要保存和恢复 DS 因为 DS 将保持不变。您也不需要将 CS 复制到 DS。如果将 cs lodsb 替换为:

,则可以类似地放弃保存和恢复标志并使用 CLD 设置方向位
    mov al, [cs:si]
    inc si 

简化代码可能如下所示:

use16                       ; ISA module operates in the 16-bit segment.

DB      55h, 0AAh           ; Boot signature
DB      01h                 ; Block size in sectors (200h each)
jmp start                   ; NearJMP part of Phoenix BIOS specification

start:
    pusha                   ; Save all generel purpose registers
    mov si, text_string     ; Put string position into SI
    call print_string       ; Call our string-printing routine
    popa                    ; Restore all general purpose registers

    retf                    ; Far return to exit option init routine

print_string:               ; Routine: output string in SI to screen
    mov ah, 0eh             ; BIOS tty Print
    xor bx, bx              ; Set display page to 0 (BL)
    jmp .getch
.repeat:
    int 10h                 ; print character
.getch:
    mov al, [cs:si]
    inc si
    test al,al              ; Have we reached end of string?
    jnz .repeat             ;     if not process next character
.end:
    ret

; String ends with 0x0d (Carriage return) and 0x0a (linefeed) to
; advance cursor to the beginning of next line
text_string db 'Hello World!', 0dh, 0ah, 0
times 512-($-$$) db 0

我用来在 Debian Linux 上测试的 Bochs 配置文件是:

# configuration file generated by Bochs
plugin_ctrl: unmapped=1, biosdev=1, speaker=1, extfpuirq=1, parallel=1, serial=1, iodebug=1
config_interface: textconfig
display_library: x
memory: host=32, guest=32
romimage: file="/usr/local/share/bochs/BIOS-bochs-latest", address=0x0, options=none
vgaromimage: file="/usr/local/share/bochs/VGABIOS-lgpl-latest"
# no floppyb
ata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14
ata0-master: type=none
ata0-slave: type=none
ata1: enabled=1, ioaddr1=0x170, ioaddr2=0x370, irq=15
ata1-master: type=none
ata1-slave: type=none
ata2: enabled=0
ata3: enabled=0
optromimage1: file="optrom.bin", address=0xd0000
pci: enabled=1, chipset=i440fx
vga: extension=vbe, update_freq=5, realtime=1
cpu: count=1:1:1, ips=4000000, quantum=16, model=bx_generic, reset_on_triple_fault=1, cpuid_limit_winnt=0, ignore_bad_msrs=1, mwait_is_nop=0
cpuid: level=6, stepping=3, model=3, family=6, vendor_string="GenuineIntel", brand_string="              Intel(R) Pentium(R) 4 CPU        "
cpuid: mmx=1, apic=xapic, simd=sse2, sse4a=0, misaligned_sse=0, sep=1, movbe=0, adx=0
cpuid: aes=0, sha=0, xsave=0, xsaveopt=0, x86_64=1, 1g_pages=0, pcid=0, fsgsbase=0
cpuid: smep=0, smap=0, mwait=1
print_timestamps: enabled=0
debugger_log: -
magic_break: enabled=0
port_e9_hack: enabled=0
private_colormap: enabled=0
clock: sync=none, time0=local, rtc_sync=0
# no cmosimage
# no loader
log: -
logprefix: %t%e%d
debug: action=ignore
info: action=report
error: action=report
panic: action=ask
keyboard: type=mf, serial_delay=250, paste_delay=100000, user_shortcut=none
mouse: type=ps2, enabled=0, toggle=ctrl+mbutton
speaker: enabled=1, mode=system
parport1: enabled=1, file=none
parport2: enabled=0
com1: enabled=1, mode=null
com2: enabled=0
com3: enabled=0
com4: enabled=0

此配置假定可选 ROM 在文件 optrom.bin 中,并将加载到内存地址 0xd0000

Option ROM 必须计算校验和并将其放在映像文件的最后一个字节中。 QEMU provides a script 可用于该目的。要更新图像的校验和,您可以执行以下操作:

python signrom.py inputimagefile outputimagefile

解决了打印到屏幕的问题。必须在代码之前加上这个:

  pusha
  cli
  mov   ax,cs
  mov   ds,ax
  mov   es,ax

这里是供任何人学习的最终代码:

use16        ; ISA module operates in the 16-bit segment.

ROM_SIZE_IN_BLOCK = 1  ; 1 means ROM size is 1 block (512 bytes)
ROM_SIZE_IN_BYTE = ROM_SIZE_IN_BLOCK * 512

DB      55h, 0AAh          ; Boot signature
DB      01h               ; Block size in sectors (200h each)





start:


      pusha
      cli
      mov   ax,cs
      mov   ds,ax
      mov   es,ax
      call cls


    mov si, text_string     ; Put string position into SI
    call print_string       ; Call our string-printing routine
    ;retf

   .bounce:
    jmp .bounce                   ; Jump here - infinite loop!





print_string:                   ; Routine: output string in SI to screen

.repeat:
    mov ah, 09h             ; int 10h 'print char' function
    mov bh, 0x00
    mov bl, 0x03
    mov cx, 01h
    cld
    lodsb                   ; Get character from string
    cmp al, 0
    je .done


    ; If char is zero, end of string
    int 10h                 ; Otherwise, print it
    mov bh, 00h
    mov ah, 03h
    int 10h
    mov ah, 02h
    mov bh, 00h
    inc dl
    int 10h
    jmp .repeat

 .done:

ret

clrscr:
   mov dh, 0
   mov dl, 0
   call set_cursor
   mov ah, 0x0a
   mov al, ' '
   mov bh, 0
   mov cx, 2000
   int 0x10
   ret


cls:
  pusha
  mov ah, 0x00
  mov al, 0x03  ; text mode 80x25 16 colours
  int 0x10
  popa
  ret

set_cursor:
   mov ah, 0x02
   int 0x10
   ret


text_string db "Hello World!",0

 ;times 512-($-$$) db 0

times (ROM_SIZE_IN_BYTE-$) db 0 ; use 00h as the padding bytes until we reach the ROM size

 ;patch_byte is calculated and automagically inserted below
PREV_CHKSUM = 0
repeat $
   load CHKSUM byte from %-1
   CHKSUM = (PREV_CHKSUM + CHKSUM) mod 0x100
   PREV_CHKSUM = CHKSUM
end repeat
store byte (0x100 - CHKSUM) at ($-1)  ; store the patch_byte         

仍然无法让它与 BOCHS 一起工作,但 Qemu 现在很喜欢它!