你的问题的答案埋没在你的问题中,它并不明显.你引用了我的一般Bootloader提示:
当BIOS跳转到您的代码时,您不能依赖具有有效或预期值的CS,DS,ES,SS,SP寄存器.应在引导加载程序启动时正确设置它们.您只能保证将从物理地址0x00007c00加载并运行引导加载程序,并将引导驱动器号加载到DL寄存器中.
您的代码正确设置了DS,并设置了自己的堆栈(SS和SP).您没有盲目地将CS复制到DS,但您所做的是依赖于CS是期望值(0x0000).在我解释我的意思之前,我想提请你注意最近的Stackoverflow回答,我给出了关于ORG指令(或任何链接器指定的原点)如何与段:偏移对一起使用的答案. BIOS跳转到物理地址0x07c00.
答案详细说明了如何在引用内存地址(例如变量)时将CS复制到DS会导致问题.在我所说的摘要中:
不要假设CS是我们期望的值,并且不要盲目地将CS复制到DS.明确设置DS.
关键是不要假设CS是我们期望的值.所以你的下一个问题可能是 - 我似乎没有使用CS我?答案是肯定的.通常,当您使用典型的CALL或JMP指令时,它看起来像这样:
call print_char jmp somewhereelse
在16位代码中,这两者都是相对跳跃.这意味着您在内存中向前或向后跳转,但在JMP或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
所有这些都有一个共同点.CALL ed或JMP ed 的地址是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并且它包含一个2字节的绝对偏移量0x7c2c.这一切都是正确的,但是当你使用绝对的2字节偏移时,它被认为是在当前的CS中.如果你已经读过这个Stackoverflow的答案(我前面提到过),当使用错误的DS和偏移来引用变量时会发生什么,你现在可能会意识到这可能适用于使用涉及NEAR 2-的绝对偏移的JMP的CALL字节绝对值.
举个例子,让我们接受这个并不总是有效的电话:
call [call_tbl]
call_tbl
从DS加载:[call_tbl].当我们启动引导加载程序时,我们正确地将DS设置为0x0000,这样就可以从内存地址0x0000:0x7c34中正确检索值0x7c2c.然后处理器将设置IP = 0x7c2c但它假设它相对于当前设置的CS.由于我们不能假设CS是预期值,处理器可能会将CALL或JMP发送到错误的位置.这一切都取决于CS:用于跳转到引导加载程序的BIOS(可能会有所不同).
如果BIOS在0x0000:0x7c00处与引导加载程序相当于FAR JMP,则CS将设置为0x0000,IP将设置为0x7c00.当我们遇到call [call_tbl]
它时,它将解析为CALL到CS:IP = 0x0000:0x7c2c.这是物理地址(0x0000 << 4)+ 0x7c2c = 0x07c2c,实际上是print_char
函数从物理上开始的内存函数.
有些BIOS 在0x07c0:0x0000处对我们的引导加载程序执行相当于FAR JMP的操作,CS将设置为0x07c0,IP将设置为0x0000.这也映射到物理地址(0x07c0 << 4)+ 0 = 0x07c00.当我们遇到call [call_tbl]
它时,它将解析为CALL到CS:IP = 0x07c0:0x7c2c.这是物理地址(0x07c0 << 4)+ 0x7c2e = 0x0f82c.这显然是错误的,因为该print_char
函数位于物理地址0x07c2c,而不是0x0f82c.
有CS设置错误将导致问题JMP和CALL是做近/绝对寻址指令.以及使用段覆盖的任何内存操作数CS:
.CS:
在此Stackoverflow答案中可以找到在实模式中断处理程序中使用覆盖的示例
既然已经表明,我们不能依靠CS时,BIOS跳转到我们的代码,我们可以设置设置CS自己.要设置CS,我们可以对我们自己的代码执行FAR JMP,这将把CS:IP设置为对我们正在使用的ORG(代码和数据的原点)有意义的值.如果我们使用ORG 0x7c00,这种跳转的一个例子:
jmp 0x0000:$+5
$+5
说使用比当前程序计数器高5的偏移量.远jmp是5个字节长所以这有影响我们在jmp之后远程跳转到指令.它也可以用这种方式编码:
jmp 0x0000:farjmp farjmp:
当这些指令中的任何一个完成时,CS将被设置为0x0000,IP将被设置为下一条指令的偏移量.对我们来说关键是CS将是0x0000.当与ORG为0x7c00配对时,它将正确解析绝对地址,以便它们在CPU上物理运行时能够正常工作.0x0000:0x7c00 =(0x0000 << 4)+ 0x7c00 =物理地址0x07c00.
当然,如果我们使用ORG 0x0000,那么我们需要将CS设置为0x07c0.这是因为(0x07c0 << 4)+ 0x0000 = 0x07c00.所以我们可以这样编写远jmp代码:
jmp 0x07c0:$+5
CS将被设置为0x07c0,IP将被设置为下一条指令的偏移量.
所有这一切的最终结果是我们将CS设置为我们想要的段,而不是依赖于当BIOS完成跳转到我们的代码时我们无法保证的值.
正如我们所见,CS很重要.无论是在仿真器,虚拟机还是真实硬件中,大多数BIOS都会相当于远程跳转到0x0000:0x7c00,并且在这些环境中你的引导加载程序可以工作.当从CD启动时,某些环境(如较旧的AMI Bioses和Bochs 2.6)正在启动我们的引导加载程序CS:IP = 0x07c0:0x0000.正如在那些环境中所讨论的那样,/ CALL和JMP将继续从错误的内存位置执行并导致我们的引导程序无法正常运行.
那么Bochs为软盘图像工作而不是ISO图像呢?这是早期版本的Bochs的特点.从软盘启动时,虚拟BIOS跳转到0x0000:0x7c00,当从ISO映像启动时,使用0x07c0:0x0000.这解释了它的工作原理.这种奇怪的行为显然是因为对El Torito规范之一的字面解释,特别提到了段0x07c0.较新版本的Boch虚拟BIOS被修改为使用0x0000:0x7c00.
这个问题的答案是主观的.在IBM的PC-DOS的第一个版本(2.1之前)中,引导加载程序假设BIOS跳转到0x0000:0x7c00,但这没有明确定义.80年代的一些BIOS制造商开始使用0x07c0:0x0000并破坏了早期版本的DOS.当发现这一点时,引导加载程序被修改为表现良好,不对任何段:偏移对用于达到物理地址0x07c00做出任何假设.当时人们可能认为这是一个错误,但是基于20位段引入的歧义:偏移对.
自80年代中期以来,我认为任何假定CS是特定值的新引导程序都被编码为错误.