问题
你的问题的答案就隐藏在你的问题中,只是不明显。你引用了我的一般引导加载程序提示 https://stackoverflow.com/a/32705076/3857942:
- 当 BIOS 跳转到您的代码时,您将无法依赖CS,DS,ES,SS,SP寄存器具有有效或预期值。当引导加载程序启动时,应正确设置它们。您只能保证您的引导加载程序将从物理地址 0x00007c00 加载并运行,并且引导驱动器号加载到DL登记。
您的代码设置正确DS,并设置自己的堆栈(SS, and SP)。你没有盲目复制CS to DS,但你所做的是依靠CS是预期值 (0x0000)。在我解释我的意思之前,我想提请您注意最近的一个堆栈溢出答案 https://stackoverflow.com/a/34425506/3857942我给出了如何ORG指令(或任何链接器指定的原点)与 BIOS 使用的段:偏移对一起工作,跳转到物理地址 0x07c00。
答案详细说明了如何CS被复制到DS引用内存地址(例如变量)时可能会导致问题。我在总结中指出:
不要假设 CS 是我们期望的值,也不要盲目地将 CS 复制到 DS 。明确设置 DS。
关键是不要假设 CS 是我们期望的值。所以你的下一个问题可能是 - 我似乎没有使用CS我是吗?答案是肯定的。通常当您使用典型的CALL or JMP指令看起来像这样:
call print_char
jmp somewhereelse
在 16 位代码中,这两个都是相对跳转。这意味着您在内存中向前或向后跳转,但作为相对于紧随该指令之后的指令的偏移量JMP or CALL。您的代码放置在段中的位置并不重要,因为它是与您当前所在位置的正/负位移。目前的价值是多少CS实际上与相对跳跃无关,因此它们应该按预期工作。
您的指令示例似乎并不总是正确工作,包括:
call [call_tbl] ; Call print_char using near indirect absolute call
; via memory operand
call [ds:call_tbl] ; Call print_char using near indirect absolute call
; via memory operand w/segment override
call near [si] ; Call print_char using near indirect absolute call
; via register
所有这些都有一个共同点。地址是CALLed or JMPed are ABSOLUTE,不是相对的。标签的偏移量将受到以下因素的影响ORG(代码的原点)。如果我们查看您的代码的反汇编,我们将看到以下内容:
objdump -mi8086 -Mintel -D -b binary boot.bin --adjust-vma 0x7c00
boot.bin: file format binary
Disassembly of section .data:
00007c00 <.data>:
7c00: 31 c0 xor ax,ax
7c02: 8e d8 mov ds,ax
7c04: fa cli
7c05: 8e d0 mov ss,ax
7c07: bc 00 7c mov sp,0x7c00
7c0a: fb sti
7c0b: be 34 7c mov si,0x7c34
7c0e: a0 36 7c mov al,ds:0x7c36
7c11: e8 18 00 call 0x7c2c ; Relative call works
7c14: a0 37 7c mov al,ds:0x7c37
7c17: ff 16 34 7c call WORD PTR ds:0x7c34 ; Near/Indirect/Absolute call
7c1b: 3e ff 16 34 7c call WORD PTR ds:0x7c34 ; Near/Indirect/Absolute call
7c20: ff 14 call WORD PTR [si] ; Near/Indirect/Absolute call
7c22: a0 38 7c mov al,ds:0x7c38
7c25: e8 04 00 call 0x7c2c ; Relative call works
7c28: fa cli
7c29: f4 hlt
7c2a: eb fd jmp 0x7c29
7c2c: b4 0e mov ah,0xe ; Beginning of print_char
7c2e: bb 00 00 mov bx,0x0 ; function
7c31: cd 10 int 0x10
7c33: c3 ret
7c34: 2c 7c sub al,0x7c ; 0x7c2c offset of print_char
; Only entry in call_tbl
7c36: 42 inc dx ; 0x42 = ASCII 'B'
7c37: 4d dec bp ; 0x4D = ASCII 'M'
7c38: 45 inc bp ; 0x45 = ASCII 'E'
...
7dfd: 00 55 aa add BYTE PTR [di-0x56],dl
我手动添加了一些注释CALL陈述是,包括有效的相对陈述和可能无效的近似/间接/绝对陈述。我还确定了在哪里print_char
函数是,以及它在的位置call_tbl
.
从代码后面的数据区域我们确实看到call_tbl
位于 0x7c34,它包含 0x7c2c 的 2 字节绝对偏移量。这都是正确的,但是当您使用绝对 2 字节偏移量时,它被假定为当前的偏移量。CS。如果你读过这篇文章堆栈溢出答案 https://stackoverflow.com/a/34425506/3857942(我之前提到过)关于错误时会发生什么DS并且 offset 用于引用变量,您现在可能意识到这可能适用于JMPs CALL使用绝对偏移量的 s 涉及NEAR2 字节绝对值。
作为一个例子,让我们以这个并不总是有效的调用为例:
call [call_tbl]
call_tbl
从 DS:[call_tbl] 加载。我们正确设置DS当我们启动引导加载程序时,它会返回到 0x0000,因此这确实可以从内存地址 0x0000:0x7c34 正确检索值 0x7c2c。然后处理器将设置 IP=0x7c2c 但它假设它是相对于当前设置的CS。因为我们不能假设CS是预期值,处理器可能会 CALL 或 JMP 到错误的位置。这一切都取决于什么CS:IPBIOS 用于跳转到我们的引导加载程序(可能会有所不同)。
在这种情况下BIOS相当于FAR JMP到我们位于 0x0000:0x7c00 的引导加载程序,CS将被设置为 0x0000 并且IP到 0x7c00。当我们遇到call [call_tbl]
它将解决一个CALL到 CS:IP=0x0000:0x7c2c 。这是物理地址 (0x0000print_char函数在内存中的物理起始位置。
某些 BIOS 的作用相当于FAR JMP到我们位于 0x07c0:0x0000 的引导加载程序,CS将被设置为 0x07c0 并且IP到 0x0000。这也映射到物理地址 (0x07c0call [call_tbl]它将解决一个CALL到 CS:IP=0x07c0:0x7c2c 。这是物理地址(0x07c0print_char函数位于物理地址 0x07c2c,而不是 0x0f82c。
Having CS设置不正确会导致问题JMP and CALL进行近端/绝对寻址的指令。以及使用段覆盖的任何内存操作数CS:
。使用的示例CS:
实模式中断处理程序中的重写可以在这里找到堆栈溢出答案 https://stackoverflow.com/a/34500963/3857942
Solution
既然已经表明我们不能依赖CS当BIOS跳转到我们可以设置的代码时设置CS我们自己。设置CS我们可以做一个FAR JMP我们自己的代码将设置CS:IP对我们正在使用的 ORG(代码和数据的原点)有意义的值。如果我们使用 ORG 0x7c00,则此类跳转的示例如下:
jmp 0x0000:$+5
$+5
表示使用比当前程序计数器高 5 的偏移量。远跳转有 5 个字节长,因此这会影响跳转到跳转后的指令。它也可以这样编码:
jmp 0x0000:farjmp
farjmp:
当这些指令中的任何一个完成时CS将被设置为 0x0000 并且IP将被设置为下一条指令的偏移量。他们对我们来说最重要的是CS将是 0x0000。当与 0x7c00 的 ORG 配对时,它将正确解析绝对地址,以便它们在 CPU 上物理运行时正常工作。 0x0000:0x7c00=(0x0000
当然如果我们使用 ORG 0x0000 那么我们需要设置CS到 0x07c0。这是因为(0x07c0
jmp 0x07c0:$+5
CS将被设置为 0x07c0 并且IP将被设置为下一条指令的偏移量。
这一切的最终结果是我们正在设置CS到我们想要的段,而不是依赖于我们无法保证 BIOS 何时完成跳转到我们的代码的值。
不同环境的问题
正如我们所看到的CS可能很重要。大多数 BIOS 无论是在模拟器、虚拟机还是真实硬件中都会执行相当于远跳到 0x0000:0x7c00 的操作,并且在这些环境中您的引导加载程序将可以工作。一些环境,例如较旧的 AMI Bioses 和Bochs2.6 当从CD正在启动我们的引导加载程序CS:IP= 0x07c0:0x0000。正如在那些接近/绝对的环境中所讨论的CALLs and JMPs 将从错误的内存位置继续执行,并导致我们的引导加载程序无法正常运行。
那么呢Bochs适用于软盘映像而不适用于ISO图像?这是早期版本的一个特点Bochs。当从软盘启动时,虚拟 BIOS 跳转到 0x0000:0x7c00;当从 ISO 映像启动时,虚拟 BIOS 会跳转到 0x07c0:0x0000。这解释了为什么它的工作方式不同。这种奇怪的行为显然是由于对特别提到段 0x07c0 的 El Torito 规范之一的字面解释而产生的。较新版本的Boch的虚拟 BIOS 已修改为对两者使用 0x0000:0x7c00。
这是否意味着某些 BIOS 有 Bug?
这个问题的答案是主观的。在 IBM PC-DOS 的第一个版本(2.1 之前)中,引导加载程序假设 BIOS 跳转到 0x0000:0x7c00,但这并没有明确定义。 80年代的一些BIOS制造商开始使用0x07c0:0x0000并破坏了一些早期版本DOS。当发现这一点时,引导加载程序被修改为行为良好,不会对使用哪个段:偏移量对来到达物理地址 0x07c00 做出任何假设。当时人们可能认为这是一个错误,但这是基于 20 位段:偏移对引入的歧义。
自 80 年代中期以来,我认为任何新的引导加载程序都假设CS是一个特定值已被错误编码。