BIOS int 10h在QEMU上打印垃圾

h0m*_*0m3 2 assembly bios nasm bootloader x86-16

编写在QEMU中作为引导加载程序运行的x86实模式汇编程序时遇到问题.我正在尝试通过BIOS中断0x10打印文本.我的代码是:

print:
    pusha
.loop:
    mov AL, [SI]
    cmp AL, 0
    je .end
    call printChar
    inc SI
    jmp .loop
.end:
    popa
    ret

printChar:
    pusha
    mov AH, 0x0E
    mov BH, 0
    mov BL, 0x0F
    int 0x10
    popa
    ret
Run Code Online (Sandbox Code Playgroud)

[ORG 0x7c00]用作原点.我测试了printChar标签,并在AL中用一些字母调用它,它工作正常.当我尝试将内存地址加载到这样的消息时:

loadMsg      db "Loading",0
mov SI, loadMessage
call print
Run Code Online (Sandbox Code Playgroud)

我在QEMU仿真器上输出像'U'这样的垃圾作为输出.昨天,我编写了一个与此类似的代码,完全没有问题.是什么导致了我的问题以及如何解决?

Mic*_*tch 5

我最近在Stackoverflow的答案中写了一些常用的Bootloader提示,可能对你有用.可能的提示#1适用于您的问题:

当BIOS跳转到您的代码时,您不能依赖具有有效或预期值的CS,DS,ES,SS,SP寄存器.应在引导加载程序启动时正确设置它们.您只能保证将从物理地址0x00007c00加载并运行引导加载程序,并将引导驱动器号加载到DL寄存器中.

基于printChar工作的事实,并且写出整个字符串并不表示DS:SI没有指向字符串所在的内存中的正确位置.通常的原因是,当BIOS跳转到引导加载程序时,开发人员错误地认为CS和/或DS寄存器已正确设置.必须手动设置.在原点为0x7c00的情况下,DS需要设置为0.在16位实模式下,使用公式从segment:offset对计算物理内存地址(segment<<4)+offset.在您的情况下,您使用的偏移量为0x7C00.所述的值DS的0将产生正确的物理地址(0 << 4)+ 0x7c00 = 0x07c00.

您可以在程序开头将DS设置为0,例如:

xor ax, ax       ; set AX to zero
mov ds, ax       ;     DS = 0  
Run Code Online (Sandbox Code Playgroud)

QEMU的情况下,BIOS跳转到0x07c0:0x0000.这也表示相同的物理内存位置(0x07c0 << 4)+0 = 0x07c00.这样的跳转将设置CS = 0x07c0(不是CS = 0).由于有许多段:偏移对映射到相同的物理内存位置,因此需要适当地设置DS.您不能依赖CS作为您期望的价值.所以在QEMU中,这样的代码甚至不能正确设置DS(使用时ORG 0x7c00):

mov ax, cs
mov ds, ax       ; Make DS=CS
Run Code Online (Sandbox Code Playgroud)

这可能适用于某些仿真器,如DOSBOX和一些物理硬件,但不是全部.此代码可以工作的环境是BIOS跳转到0x0000:0x7c00.在这种情况下,当CS到达您的引导加载程序代码时它将为零,并且将CS复制到DS将起作用.不要认为CS在所有环境中都是零是我正在制定的要点.始终将段寄存器设置为您想要的显式.

应该有效的代码示例如下:

    BITS  16
    ORG   0x7c00
    GLOBAL main

main:
    xor ax, ax        ; AX = 0
    mov ds, ax        ; DS = 0
    mov bx, 0x7c00

    cli               ; Turn off interrupts for SS:SP update
                      ; to avoid a problem with buggy 8088 CPUs
    mov ss, ax        ; SS = 0x0000
    mov sp, bx        ; SP = 0x7c00
                      ; We'll set the stack starting just below
                      ; where the bootloader is at 0x0:0x7c00. The
                      ; stack can be placed anywhere in usable and
                      ; unused RAM.
    sti               ; Turn interrupts back on

    mov SI, loadMsg
    call print

    cli
.endloop:
    hlt
    jmp .endloop      ; When finished effectively put our bootloader
                      ; in endless cycle

print:
    pusha
.loop:
    mov AL, [SI]      ; No segment on [SI] means implicit DS:[SI]
    cmp AL, 0
    je .end
    call printChar
    inc SI
    jmp .loop
.end:
    popa
    ret

printChar:
    pusha
    mov AH, 0x0E
    mov BH, 0
    mov BL, 0x0F
    int 0x10
    popa
    ret

; Place the Data after our code
loadMsg db "Loading",0

times 510 - ($ - $$) db 0   ; padding with 0 at the end
dw 0xAA55                   ; PC boot signature
Run Code Online (Sandbox Code Playgroud)

  • @rcgldr:我很清楚它会禁用它直到下一条指令结束(在真正的8088/8086硬件上)它应该禁用任何段寄存器(不仅仅是SS)的中断.这改变了80286 +,它只用于_SS_.我特意提到有缺陷的8088是有原因的.我的代码针对可能存在的各种硬件.自80年代中期以来,我一直在真正的8088硬件上进行汇编语言开发,这也是一个众所周知的bug.PC杂志于1987年刊登了一则故事:https://books.google.com/books?id = 1L7PVOhfUIoC&p = PA492&lpg = PA492 #v = onepage&q&f = false. (2认同)