当前位置:  开发笔记 > 编程语言 > 正文

近呼叫/跳转表并不总是在引导加载程序中工作

如何解决《近呼叫/跳转表并不总是在引导加载程序中工作》经验,为你挑选了1个好方法。



1> Michael Petc..:

问题

你的问题的答案埋没在你的问题中,它并不明显.你引用了我的一般Bootloader提示:

    当BIOS跳转到您的代码时,您不能依赖具有有效或预期值的CS,DS,ES,SS,SP寄存器.应在引导加载程序启动时正确设置它们.您只能保证将从物理地址0x00007c00加载并运行引导加载程序,并将引导驱动器号加载到DL寄存器中.

您的代码正确设置了DS,并设置了自己的堆栈(SSSP).您没有盲目地将CS复制到DS,但您所做的是依赖于CS是期望值(0x0000).在我解释我的意思之前,我想提请你注意最近的Stackoverflow回答,我给出了关于ORG指令(或任何链接器指定的原点)如何与段:偏移对一起使用的答案. BIOS跳转到物理地址0x07c00.

答案详细说明了如何在引用内存地址(例如变量)时将CS复制到DS会导致问题.在我所说的摘要中:

不要假设CS是我们期望的值,并且不要盲目地将CS复制到DS.明确设置DS.

关键是不要假设CS是我们期望的值.所以你的下一个问题可能是 - 我似乎没有使用CS我?答案是肯定的.通常,当您使用典型的CALLJMP指令时,它看起来像这样:

call print_char
jmp somewhereelse

在16位代码中,这两者都是相对跳跃.这意味着您在内存中向前或向后跳转,但在JMPCALL之后立即作为相对于指令的偏移量.将代码放在段中的位置无关紧要,因为它是您当前所在位置的正/负位移.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-的绝对偏移的JMPCALL字节绝对值.

举个例子,让我们接受这个并不总是有效的电话:

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设置错误将导致问题JMPCALL是做近/绝对寻址指令.以及使用段覆盖的任何内存操作数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.正如在那些环境中所讨论的那样,/ CALLJMP将继续从错误的内存位置执行并导致我们的引导程序无法正常运行.

那么Bochs为软盘图像工作而不是ISO图像呢?这是早期版本的Bochs的特点.从软盘启动时,虚拟BIOS跳转到0x0000:0x7c00,当从ISO映像启动时,使用0x07c0:0x0000.这解释了它的工作原理.这种奇怪的行为显然是因为对El Torito规范之一的字面解释,特别提到了段0x07c0.较新版本的Boch虚拟BIOS被修改为使用0x0000:0x7c00.


这是否意味着某些BIOS有Bug?

这个问题的答案是主观的.在IBM的PC-DOS的第一个版本(2.1之前)中,引导加载程序假设BIOS跳转到0x0000:0x7c00,但这没有明确定义.80年代的一些BIOS制造商开始使用0x07c0:0x0000并破坏了早期版本的DOS.当发现这一点时,引导加载程序被修改为表现良好,不对任何段:偏移对用于达到物理地址0x07c00做出任何假设.当时人们可能认为这是一个错误,但是基于20位段引入的歧义:偏移对.

自80年代中期以来,我认为任何假定CS是特定值的新引导程序都被编码为错误.

推荐阅读
郑谊099_448
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有