问题描述
我在编写在 QEMU 中作为引导加载程序运行的 x86 实模式汇编程序时遇到问题.我正在尝试通过 BIOS 中断 0x10 打印文本.我的代码是:
I have a problem while writing an x86 real mode assembly program that runs as a bootloader in QEMU. I'm trying to print text through BIOS interrupt 0x10. My code is:
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
我使用 [ORG 0x7c00]
作为原点.我测试了 printChar 标签并用 AL 中的一些字母调用它,它工作正常.当我尝试将内存地址加载到这样的消息时:
I'm using [ORG 0x7c00]
as an origin point. I tested the printChar label and calling it with some letter in AL and it works fine. When I try to load a memory address to a message like this:
loadMsg db "Loading",0
mov SI, loadMessage
call print
我在 QEMU 模拟器上得到像U"这样的垃圾作为输出.昨天,我写了一个和这个非常相似的代码,完全没有问题.是什么导致了我的问题,如何解决?
I get garbage like 'U' as output on QEMU emulator. Yesterday, I wrote a code really similar to this one and have no problem at all. What is causing my problem and how can it be fixed?
推荐答案
我最近在一篇文章中写了一些一般引导加载程序提示Stackoverflow 答案可能对您有用.可能提示 #1 适用于您的问题:
I recently wrote some General Bootloader Tips in a Stackoverflow answer that may be of some use to you. Likely Tip #1 applies to your problem:
当 BIOS 跳转到您的代码时,您不能依赖具有有效或预期值的 CS、DS、ES、SS、SP 寄存器.它们应该在引导加载程序启动时进行适当设置.您只能保证您的引导加载程序将从物理地址 0x00007c00 加载并运行,并且引导驱动器编号已加载到 DL 寄存器中.
基于 printChar 工作的事实,并且写出整个字符串并不表明 DS:SI 没有指向内存中的正确位置你的字符串驻留.造成这种情况的常见原因是,当 BIOS 跳转到引导加载程序时,开发人员错误地假设 CS 和/或 DS 寄存器设置正确.它必须手动设置.在原点为 0x7c00 的情况下,DS 需要设置为 0.在 16 位实模式中,物理内存地址从 segment:offset pair 使用公式(segment<<.在您的情况下,您使用的偏移量为 0x7C00.DS 中的值为 0 将产生 (0<
Based on the fact that printChar works, and that writing an entire string out doesn't suggests that DS:SI is not pointing at the proper location in memory where your string resides. The usual cause of this is that developers incorrectly assume the CS and/or DS register is set appropriately when the BIOS jumps to the bootloader. It has to be manually set. In the case of an origin point of 0x7c00, DS needs to be set to 0. In 16-bit real mode physical memory addresses are computed from segment:offset pairs using the formula (segment<<4)+offset
. In your case you are using an offset of 0x7C00. A value in DS of 0 would yield the correct physical address of (0<<4)+0x7c00 = 0x07c00 .
您可以在程序开始时将 DS 设置为 0,例如:
You can set DS to 0 at the start of your program with something like:
xor ax, ax ; set AX to zero
mov ds, ax ; DS = 0
对于 QEMU,BIOS 跳转到 0x07c0:0x0000 .这也表示相同的物理内存位置 (0x07c0ORG 0x7c00
时):
In the case of QEMU, the BIOS jumps to 0x07c0:0x0000 . This also represents the same physical memory location (0x07c0<<4)+0 = 0x07c00 . Such a jump will set CS=0x07c0 (not CS=0). Since there are many segment:offset pairs that map to the same physical memory location, you need to set DS appropriately. You can't rely on CS being the value you expect. So in QEMU, code like this wouldn't even set DS correctly (when using ORG 0x7c00
):
mov ax, cs
mov ds, ax ; Make DS=CS
这可能适用于某些模拟器,例如 DOSBOX 和某些物理硬件,但不是全部.此代码适用的环境是 BIOS 跳转到 0x0000:0x7c00 时.在这种情况下,CS 在到达您的引导加载程序代码时将为零,并且将 CS 复制到 DS 将起作用.不要假设 CS 在所有环境中都为零,这是我要提出的主要观点.始终将段寄存器显式设置为您想要的.
This may work on some emulators like DOSBOX and some physical hardware, but not all. The environments where this code would work is when the BIOS jumps to 0x0000:0x7c00 . In that case CS would be zero when it reaches your bootloader code, and copying CS to DS would work. Don't assume CS will be zero in all environments is the main point I am making. Always set the segment registers to what you want explicitly.
应该工作的代码示例是:
An example of code that should work is:
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
这篇关于BIOS int 10h 在 QEMU 上打印垃圾的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!