ORG 指令
当您在汇编程序的顶部指定一个 ORG 指令(如 ORG 0x0000)并使用 BITS 16 时,您将通知 NASM 在将标签解析为代码时和数据,将生成的绝对偏移量将基于在 ORG 中指定的起始偏移量(16 位代码将被限制为偏移量为 WORD/ 2 个字节)。
如果您在开头有ORG 0x0000 并在代码开头放置标签start:,则start 的绝对偏移量将为0x0000。如果您使用ORG 0x7C00,那么标签start 的绝对偏移量将为0x7c00。这将适用于任何数据标签和代码标签。
我们可以简化您的示例,以查看在处理数据变量和硬编码字符时生成的代码中发生了什么。尽管此代码执行的操作与您的代码不完全相同,但它足以显示哪些有效,哪些无效。
使用 ORG 0x0000 的示例:
BITS 16
ORG 0x0000
start:
push cs
pop ds ; DS=CS
push 0xb800
pop es ; ES = 0xB800 (video memory)
mov ah, 0x0E ; AH = Attribute (yellow on black)
mov al, byte [msg]
mov [es:0x00], ax ; This should print letter 'P'
mov al, byte [msg+1]
mov [es:0x02], ax ; This should print letter 'A'
mov al, 'O'
mov [es:0x04], ax ; This should print letter 'O'
mov al, '!'
mov [es:0x06], ax ; This should print letter '!'
cli
hlt
msg: db "PA"
; Bootsector padding
times 510-($-$$) db 0
dw 0xAA55
如果您要在 VirtualBox 上运行它,前 2 个字符将是垃圾,而 O! 应该正确显示。我将在这个答案的其余部分使用这个例子。
VirtualBox / CS:IP / Segment:Offset Pairs
在 Virtual Box 的情况下,在加载物理地址 0x00007c00 的引导扇区后,它将有效地执行 FAR JMP 到 0x0000:0x7c00 的等效。 FAR JMP(或等效项)不仅会跳转到给定地址,还会将 CS 和 IP 设置为指定的值。 FAR JMP 到 0x0000:0x7c00 将设置 CS = 0x0000 和 IP = 0x7c00。
如果您不熟悉 16 位段:偏移对背后的计算以及它们如何映射到物理地址,那么 document 是理解该概念的一个相当好的起点。从 16 位段中获取物理内存地址的一般公式:偏移量对是 (segment<<4)+offset = 20-bit physical address。
由于 VirtualBox 使用 0x0000:0x7c00 的 CS:IP ,它将在 (0x0000
您的引导加载程序出了什么问题?
假设我们正在使用 VirtualBox 并且上一节中的上述信息被认为是正确的,那么在进入我们的引导加载程序时 CS = 0x0000 和 IP = 0x7c00。如果我们采用示例代码(使用ORG 0x0000)我在此答案的第一部分中编写并查看反汇编信息(我将使用 objdump 输出),我们会看到:
objdump -Mintel -mi8086 -D -b binary --adjust-vma=0x0000 boot.bin
00000000 <.data>:
0: 0e push cs
1: 1f pop ds
2: 68 00 b8 push 0xb800
5: 07 pop es
6: b4 0e mov ah,0xe
8: a0 24 00 mov al,ds:0x24
b: 26 a3 00 00 mov es:0x0,ax
f: a0 25 00 mov al,ds:0x25
12: 26 a3 02 00 mov es:0x2,ax
16: b0 4f mov al,0x4f
18: 26 a3 04 00 mov es:0x4,ax
1c: b0 21 mov al,0x21
1e: 26 a3 06 00 mov es:0x6,ax
22: fa cli
23: f4 hlt
24: 50 push ax ; Letter 'P'
25: 41 inc cx ; Letter 'A'
...
1fe: 55 push bp
1ff: aa stos BYTE PTR es:[di],al
由于在组装成二进制文件时会丢失 ORG 信息,因此我使用--adjust-vma=0x0000 以便第一列值(内存地址)从 0x0000 开始。我想这样做是因为我在原始汇编代码中使用了ORG 0x0000。我还在代码中添加了一些 cmets 以显示我们的数据部分在哪里(以及字母 P 和 A 放在代码后面的位置)。
如果你在 VirtualBox 中运行这个程序,前 2 个字符会显示为乱码。那为什么呢?首先回想一下,VirtualBox 通过将 CS 设置为 0x0000 并将 IP 设置为 0x7c00 来访问我们的代码。然后这段代码将 CS 复制到 DS:
0: 0e push cs
1: 1f pop ds
由于 CS 为零,因此 DS 为零。现在让我们看看这一行:
8: a0 24 00 mov al,ds:0x24
ds:0x24 实际上是我们数据部分中 msg 变量的编码地址。偏移量 0x24 处的字节包含值 P(0x25 包含 A)。您可能会看到哪里出了问题。我们的 DS = 0x0000 所以mov al,ds:0x24 与mov al,0x0000:0x24 完全相同。此语法无效,但我将 DS 替换为 0x0000 以说明问题。 0x0000:0x24 是我们的代码在执行时会尝试读取我们的信 P 的地方。可是等等!即物理地址 (0x0000
有几种方法可以解决这个问题。最简单(也是首选的方法)是将正确的段实际放入 DS,而不是依赖于我们的程序运行时可能是什么 CS。由于我们将 ORG 设置为 0x0000,我们需要有一个 Data Segment(DS) = 0x07c0 。段:偏移量对 0x07c0:0x0000 = 物理地址 0x07c00 。这就是我们的引导加载程序的地址所在。所以我们要做的就是通过替换来修改代码:
push cs
pop ds ; DS=CS
与:
push 0x07c0
pop ds ; DS=0x07c0
当在 VirtualBox 中运行时,此更改应提供正确的输出。现在让我们看看为什么。这段代码没有改变:
8: a0 24 00 mov al,ds:0x24
现在当执行 DS=0x07c0。这就像说mov al,0x07c0:0x24。 0x07c0:0x24,这将转换为 (0x07c0msg 变量。
故事的寓意? ORG 无论你使用什么,当我们启动程序时,DS 寄存器中都应该有一个适用的值。我们应该明确设置它,而不是依赖于 DS 中的内容em>CS.
为什么要打印立即数?
使用原始代码,前 2 个字符打印出乱码,但后两个字符没有。正如上一节所讨论的,前 2 个字符无法打印是有原因的,但最后 2 个字符会打印吗?
让我们更仔细地检查第三个字符O的反汇编:
16: b0 4f mov al,0x4f ; 0x4f = 'O'
由于我们使用了立即数(常量)并将其移入寄存器 AL,因此字符本身被编码为指令的一部分。它不依赖于通过 DS 寄存器进行的内存访问。因此,最后 2 个字符正确显示。
Ross Ridge 的建议及其在 VirtualBox 中的工作原理
Ross Ridge 建议我们使用ORG 0x7c00,您发现它确实有效。为什么会这样?这种解决方案是否理想?
使用我的第一个示例并将ORG 0x0000 修改为ORG 0x7c00,然后组装它。 objdump 会提供这个反汇编:
objdump -Mintel -mi8086 -D -b binary --adjust-vma=0x7c00 boot.bin
boot.bin: file format binary
Disassembly of section .data:
00007c00 <.data>:
7c00: 0e push cs
7c01: 1f pop ds
7c02: 68 00 b8 push 0xb800
7c05: 07 pop es
7c06: b4 0e mov ah,0xe
7c08: a0 24 7c mov al,ds:0x7c24
7c0b: 26 a3 00 00 mov es:0x0,ax
7c0f: a0 25 7c mov al,ds:0x7c25
7c12: 26 a3 02 00 mov es:0x2,ax
7c16: b0 4f mov al,0x4f
7c18: 26 a3 04 00 mov es:0x4,ax
7c1c: b0 21 mov al,0x21
7c1e: 26 a3 06 00 mov es:0x6,ax
7c22: fa cli
7c23: f4 hlt
7c24: 50 push ax ; Letter 'P'
7c25: 41 inc cx ; Letter 'A'
...
7dfe: 55 push bp
7dff: aa stos BYTE PTR es:[di],al
VirtualBox 在跳转到我们的引导加载程序时将 CS 设置为 0x0000。然后我们的原始代码将 CS 复制到 DS,因此 DS = 0x0000。现在观察ORG 0x7c00 指令对我们生成的代码做了什么:
7c08: a0 24 7c mov al,ds:0x7c24
注意我们现在是如何使用 0x7c24 的偏移量的!这就像mov al,0x0000:0x7c24 是物理地址 (0x0000msg 字符串的正确位置。所以它起作用了。
使用ORG 0x7c00 是个坏主意吗?不,没关系。但我们有一个微妙的问题需要解决。如果另一个 Virtual PC 环境或真实硬件没有使用 0x0000:0x7c00 的 CS:IP 到我们的引导加载程序 FAR JMP 会发生什么?这个有可能。有许多带有 BIOS 的物理 PC 实际上相当于跳到0x07c0:0x0000。正如我们已经看到的,这也是物理地址0x07c00。在那种环境下,当我们的代码运行 CS = 0x07c0。如果我们使用将 CS 复制到 DS 的原始代码,DS 现在也有 0x07c0。现在观察在这种情况下这段代码会发生什么:
7c08: a0 24 7c mov al,ds:0x7c24
DS=0x07c0 在这种情况下。当程序实际运行时,我们现在有类似mov al,0x07c0:0x7c24 的东西。呃,看起来很糟糕。这将转换为物理地址是什么? (0x07c0msg 字符串被加载的地方!
那么我们如何解决这个问题呢?修改 Ross Ridge 的建议,并听取我之前给出的关于将 DS 明确设置为我们真正想要的段的建议(不要假设 CS 是正确的然后盲目复制到 DS)如果我们使用ORG 0x7c00,我们应该在引导加载程序启动时将 0x0000 放入 DS。所以我们可以修改这段代码:
ORG 0x7c00
start:
push cs
pop ds ; DS=CS
到:
ORG 0x7c00
start:
xor ax, ax ; ax=0x0000
mov ds, ax ; DS=0x0000
这里我们不依赖 CS 中的不可信值。我们只需将 DS 设置为对我们使用的 ORG 有意义的段值。您可以像以前一样推送 0x0000 并将其弹出到 DS 中。我更习惯于将寄存器清零并将其移至 DS。
通过采用这种方法,无论 CS 中的什么值可能已用于到达我们的引导加载程序,代码仍然会为我们的数据引用适当的内存位置。
不要假设 1st Stage 由 BIOS 调用 CS:IP=0x0000:0x7c00
在我在之前的 StackOverflow 答案中写的 General Bootloader Tips 中,提示 #1 非常重要:
- 当 BIOS 跳转到您的代码时,您不能依赖具有有效或预期值的 CS、DS、ES、SS、SP 寄存器。当您的引导加载程序启动时,它们应该被适当地设置。您只能保证您的引导加载程序将从物理地址 0x07c00 加载和运行,并且引导驱动器号已加载到 DL 寄存器中。
BIOS 可以使用 jmp 0x07c0:0x0000 对我们的代码进行 FAR JMP(或等效),并且一些仿真器和真实硬件会这样做。其他人像 VirtualBox 一样使用jmp 0x0000:0x7c00。
我们应该通过将 DS 明确设置为我们需要的值来解决此问题,并将其设置为对我们在 ORG 指令中使用的值有意义的值。
总结
不要假设 CS 是我们期望的值,也不要盲目地将 CS 复制到 DS 。明确设置 DS。
如果我们如前所述将 DS 适当地设置为 0x07c0,则您的代码可以修复为使用您最初拥有的 ORG 0x0000。这可能看起来像:
ORG 0
BITS 16
push word 0xB800 ; Address of text screen video memory in real mode for colored monitors
push 0x07c0
pop ds ; DS=0x07c0 since we use ORG 0x0000
pop es
我们也可以像这样使用ORG 0x7c00:
ORG 0x7c00
BITS 16
push word 0xB800 ; Address of text screen video memory in real mode for colored monitors
push 0x0000
pop ds ; DS=0x0000 since we use ORG 0x7c00
pop es