assembly - 为什么我的引导加载程序不能正确地从内存中加载一个字节?

标签 assembly x86 bootloader low-level real-mode

我有以下 x86 程序:

mov ah, 0x0e          ; Set up call to BIOS routine to print character

mov al, [character]   ; Stick the byte at label "character"
int 0x10              ; Display character in al

jmp $                 ; Loop forever

character:
db 0x41               ; Put the byte "A" at this position

times 510-($-$$) db 0 ; Pad with zeros and end with the magic number for a bootloader
db 0x55
db 0xaa

我以两种不同的方式运行它:

  • 在qemu中
  • 使用 dd 将其写入 U 盘并在旧的 64 位笔记本电脑上启动

我使用以下命令来运行这段代码:

$ nasm -f bin -o boot.bin main.s
$ qemu-system-x86_64 boot.bin  # to test
$ dd if=boot.bin of=/dev/sda # to put it on a USB stick

上面写的代码在任何一种情况下都不起作用。在硬件上它显示一个闪烁的光标,在 qemu 上它打印一个西里尔字母,而不是“A”。所以我将第二行(非空行)改为

mov al, [0x7c00 + character]

将偏移量 0x7c00 添加到标签中,因为根据某些来源,x86 将您的引导加载程序放在内存中的 0x7c00 处。这在 qemu 中按预期工作,但继续在硬件上给我一个闪烁的光标。请注意,这与将 [org 0x7c00] 放在顶部具有相同的效果,我的意思是通过使用上面的行或通过添加 org 指令生成的二进制文件是相同(我比较了他们的 md5)。

为了确保我的硬件没有一些奇怪的字符集,其中 0x41 不是“A”,我试过了

mov al, 0x41

这在 qemu 和硬件上都有效。

我怎样才能正确引用存储在“character”中的数据,以便我的笔记本电脑找到应该存在的值?请注意,因为这是一个引导加载程序,所以 CPU(如果我理解正确的话)处于 16 位实模式。

最佳答案

x86 具有几个包含内存偏移量的段寄存器。在实模式(和其他模式?)中,这些寄存器隐式添加到您创建的任何内存引用中。使用哪个段寄存器取决于上下文(换句话说,地址在哪种指令中使用)。在我们的例子中,当我们尝试使用

从内存中获取数据时
mov al, [character]

处理器将隐式地将 ds(对于“数据段”)寄存器的内容(乘以 16)添加到内存偏移量 character。请注意,这发生在运行时,而不是编译时,因此如果您反汇编它,您将不会在二进制文件中看到它。

解决方案是将汇编程序顶部的ds置零。但是,请注意,您实际上不能只说 mov ds, 0,因为 x86 不支持将常量写入段寄存器 - 您必须像在

中那样通过另一个寄存器
mov ax, 0
mov ds, ax

为了完整起见,这是完整的更新代码,可在我的笔记本电脑和 QEMU 上运行。与问题中的代码的差异在下面进行了评论。

mov ax, 0  ; Zero out the data segment register
mov ds, ax ;

mov ah, 0x0e

mov al, [0x7c00 + character] ; Add 0x7c00 to the offset
                             ; As mentioned in the question, putting ORG 0x7C00 at the top of the file
                             ; also works (and is better, but this is clearer for demonstration purposes)
                             ; and in fact produces an identical binary to this explicit addition.
int 0x10

jmp $

character:
db 0x41

times 510-($-$$) db 0
db 0x55
db 0xaa

显然,这里发生的是 ds 寄存器在 QEMU 上默认为零,但在我的硬件上不是。由专业人员编写的真正的引导加载程序总是会明确地将此类事情置零,而不是假设 BIOS 在加载其代码之前将寄存器置于任何特定状态。

如果您一直在阅读 "Writing a Simple Operating System - from Scratch" by Nick Blundell和我一样,他实际上稍后会在第 3.6.1 节(“使用段扩展内存访问”)中讨论这些东西。不幸的是,在那之前我卡在了这几页上,没有提前阅读。

关于assembly - 为什么我的引导加载程序不能正确地从内存中加载一个字节?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63087852/

相关文章:

assembly - 以二进制实现将 8 位数除以 3 (11) 的硬件

Linux kernel header.S源码,为什么清零BSS时需要_end+3?

c++ - x86 内存排序测试显示英特尔手册中规定不应该进行重新排序?

assembly - 无法弄清楚程序集 x86 中的 printf 函数以及使用操作数

assembly - 第二阶段 bootLoader 未在 bochs、LINUX(ubuntu 16.04)、Brokenthorn osdev 系列中加载

x86 - (NASM) (80x86) 引导加载程序需要 xor ax, ax

embedded - 为什么 ROM 的闪存扇区起始地址较小?

linux - 用户空间中 x86-64 Linux 上 CS 和 SS 寄存器的含义?

c - 恢复用户堆栈/寄存器

c - .c 文件中的内联 PPC 汇编代码出现 "Error: unsupported relocation against <register>"错误