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

2024-05-06

一般问题

我一直在开发一个简单的引导加载程序,并在某些环境中偶然发现了一个问题,在这些环境中,此类指令不起作用:

mov si, call_tbl      ; SI=Call table pointer
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 http://x86.renejeschke.de/html/file_module_x86_id_26.html到绝对内存偏移量。我发现如果我使用类似的,我会遇到问题JMP http://x86.renejeschke.de/html/file_module_x86_id_147.html表。相对的调用和跳转似乎不受影响。像这样的代码可以工作:

call print_char 

我采纳了 Stackoverflow 上讨论编写引导加载程序注意事项的发帖者提出的建议。特别是我看到了这个堆栈溢出 https://stackoverflow.com/questions/32701854/boot-loader-doesnt-jump-to-kernel-code/32705076#32705076回答与一般引导加载程序提示。第一个提示是:

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

听取了所有建议,我没有依赖CS,我设置了一个堆栈,并设置DS以适合于ORG(原点偏移)我用过。我创建了一个最小完整可验证示例来演示该问题。我用这个构建了NASM,但这似乎不是一个特定的问题NASM.


最小的例子

需要测试的代码如下:

[ORG 0x7c00]
[Bits 16]

section .text
main:
    xor ax, ax
    mov ds, ax            ; DS=0x0000 since OFFSET=0x7c00
    cli                   ; Turn off interrupts for potentially buggy 8088
    mov ss, ax
    mov sp, 0x7c00        ; SS:SP = Stack just below 0x7c00
    sti                   ; Turn interrupts back on

    mov si, call_tbl      ; SI=Call table pointer
    mov al, [char_arr]    ; First char to print 'B' (beginning)
    call print_char       ; Call print_char directly (relative jump)

    mov al, [char_arr+1]  ; Character to print 'M' (middle)
    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

    mov al, [char_arr+2]  ; Third char to print 'E' (end)
    call print_char       ; Call print_char directly (relative jump)

end:
    cli
.endloop:
    hlt                   ; Halt processor
    jmp .endloop

print_char:
    mov ah, 0x0e    ; Write CHAR/Attrib as TTY
    mov bx, 0x00    ; Page 0
    int 0x10
    retn

; Near call address table with one entry
call_tbl: dw print_char

; Simple array of characters
char_arr: db 'BME'

; Bootsector padding
times 510-($-$$) db 0
dw 0xAA55

我建立了两个ISO图像和用于测试目的的 1.44MB 软盘图像。我使用的是 Debian Jessie 环境,但大多数 Linux 发行版都是类似的:

nasm -f bin boot.asm -o boot.bin
dd if=/dev/zero of=floppy.img bs=1024 count=1440
dd if=boot.bin of=floppy.img conv=notrunc

mkdir iso    
cp floppy.img iso/
genisoimage -quiet -V 'MYBOOT' -input-charset iso8859-1 -o myos.iso -b floppy.img -hide floppy.img iso

我最终得到了一个名为floppy.img and an ISO图像称为myos.iso.


期望与实际结果

在大多数情况下,此代码可以工作,但在许多环境中却不能。当它工作时,它只是在显示屏上打印:

BMMME

我打印出来B使用典型的CALL有了相对偏移,它似乎工作得很好。在某些环境中,当我运行代码时,我刚刚得到:

B

然后它似乎就停止做任何事情。似乎打印出B正常,但随后发生了意想不到的事情。

似乎有效的环境:

  • QEMU使用软盘和 ISO 启动
  • 虚拟盒子使用软盘和 ISO 启动
  • VMWare 9使用软盘和 ISO 启动
  • DosBox用软盘启动
  • 正式包装Bochs(2.6) 在 Debian Jessie 上使用软盘映像
  • Bochs 2.6.6(从源代码控制构建)在 Debian Jessie 上使用软盘映像和ISO image
  • AST Premmia SMP P90 系统来自 90 年代中期,使用软盘和ISO

未按预期工作的环境:

  • 正式包装Bochs(2.6) 在 Debian Jessie 上使用ISO image
  • 基于 486DX 的系统,带有 90 年代初使用软盘映像的 AMI BIOS。 CD 无法在此系统上启动,因此无法测试 CD。

我觉得有趣的是Bochs(版本 2.6)在使用 Debian Jessie 时无法按预期工作ISO。当我从具有相同版本的软盘启动时,它按预期工作。

在所有情况下ISO软盘映像似乎已加载并开始运行ALL至少能够打印出来的情况B在显示屏上。


我的问题

  • 当失败时,为什么它只打印出一个B仅此而已?
  • 为什么有些环境有效而另一些环境却失败?
  • 这是我的代码或硬件/BIOS 中的错误吗?
  • 如何修复它,以便我仍然可以使用近间接跳转和调用表来实现绝对内存偏移?我知道我可以完全避免这些指令,这似乎可以解决我的问题,但我希望能够了解如何以及是否可以在引导加载程序中正确使用它们。

问题

你的问题的答案就隐藏在你的问题中,只是不明显。你引用了我的一般引导加载程序提示 https://stackoverflow.com/a/32705076/3857942:

  1. 当 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是一个特定值已被错误编码。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

近调用/跳转表并不总是在引导加载程序中工作 的相关文章

随机推荐

  • Amazon S3 上传错误 SSL 证书问题

    我正在尝试在本地主机上测试 Laravel Amazon S3 但不断收到相同的错误 WrappedHttpHandler php 第 192 行中的 S3Exception 执行时出错 列表对象 上 https s3 us west 2
  • LOAD DATA LOCAL INFILE 给出错误:此 MySQL 版本不允许使用命令

    我有一个调用 MySQL 的 PHP 脚本LOAD DATA INFILE从 CSV 文件加载数据 但是 在生产服务器上 我最终遇到了以下错误 用户访问被拒绝 使用密码 是 作为快速解决方法 我将命令更改为LOAD DATA LOCAL I
  • Maven 依赖项更新报告需要数小时才能完成

    我有任务运行 Jenkins 工作女巫会报告新版本的库 我认为这些可以满足我的需要 org codehaus mojo versions maven plugin 2 5 plugin updates report org codehaus
  • 自动更改 github 文件

    我制作了一个带有白名单的应用程序 withelist 位于 github 存储库上 只有一个文件 即 withelist 每次下载我的应用程序的用户想要被允许使用该应用程序时 都必须向我发送一个消息插入白名单 现在这个过程真的很慢 我想加快
  • MYSQL插入GB大小的巨大SQL文件

    我正在尝试创建 Wikipedia DB 副本 大约 50GB 但在处理最大的 SQL 文件时遇到问题 我使用 linux split 实用程序将 GB 大小的文件拆分为 300 MB 的块 例如 split d l 50 enwiki 2
  • 函数内的静态变量如何工作?

    在下面的代码中 int count static int n 5 n n 1 return n 变量n仅在第一次调用该函数时实例化一次 应该有一个标志或其他东西 所以它只初始化变量一次 我试图查看 gcc 生成的汇编代码 但没有任何线索 编
  • Amazon VPC NACL 默认规则评估顺序

    据我了解 NACL 网络访问控制列表 就是子网防火墙 我试图了解创建 NACL 时的默认值 规则 100 默认情况下允许来自所有 IP 的所有端口 否则 一切都被否定 那么 底线是 是全部允许还是全部拒绝 我知道根据 AWS 最佳实践 默认
  • 无法从表存储中获取记录

    我有一个表存储表 我想从中获取一些数据 插入和更新查询工作正常 但当我尝试选择一些记录时遇到问题 下面是我到目前为止所做的代码 class TransactionEntity TableEntity public String s get
  • Django 管理 GenericForeignKey 小部件

    我正在创建一个 Django 应用程序 其中所有模型都可以按照用户设置的顺序相互关联 我正在使用 GenericForeignKeys 设置所有这些 更重要的是 我需要能够支持这些类型的关系 管理的多个集合 因此 一个对象可以拥有多个相关对
  • 使用 NSMutableDictionary 与 NSMutableArray 造成的性能损失>

    我正在考虑使用 NSMutableDictionary 代替我当前的 NSMutableArray 这主要是出于 KVC KVO 的原因 该集合将在我的绘图方法的内循环中经历严重的变化 如果我继续进行此替换 性能是否会受到重大影响 干杯 道
  • @NumberFormat 注释不起作用

    试图在 JSP 中显示货币符号 但我没有看到它 我做了研究 但我只是不知道我还应该添加什么才能让它发挥作用 这就是我所拥有的
  • FindAndUpdate 如何检查文档是否真的更新

    想象一下以下模型 var Office id 1 name My Office branches adddress Some street that avenue isPrincipal true adddress Another addr
  • pandas 使用查询功能检查列是否为空

    我有 pandas 数据框 我想在它的查询函数上执行 isnull 或 not isnull 条件 如下所示 In 67 df data pd DataFrame a 1 20 None 40 50 In 68 df data Out 68
  • 如何更改 Kindle Fire 上 /mnt/SDcard 文件夹的读/写权限?

    我正在尝试在 Android 中开发 Amazon In app 为此 我从该网站下载示例代码https developer amazon com sdk in app purchasing sample code button click
  • PHP如何使用“ XML 中的实体与 DOMdocument

    我正在修改由其他库生成的 XML 文件的内容 我正在使用 PHP 5 3 10 进行一些 DOM 修改并重新插入替换节点 我正在使用的 XML 数据有 quot 在进行操作之前的元素 我想保留这些元素http www w3 org TR R
  • Unix cURL POST 使用文件中的内容到特定变量

    我已经搜索过这个答案 但没有找到任何有效或完全符合我的问题的答案 使用 Unix cURL 我需要将键 值对发布到服务器 密钥将是 MACs 换行符分隔的 MAC 地址文件的内容将是此 POST 的 VALUE 我试过了 curl d fi
  • 重写 .php 扩展名

    请问我该如何重写 有人可以重写这个网址吗 to http localhost display news cat 14 2 http localhost display news cat 14 2 谢谢 您可以使用 htaccess 文件来做
  • GitHub Actions 中的嵌套模板(从另一个 yaml 文件调用一个 yaml 文件)

    GitHub Action 是否支持嵌套模板 例如 以下是 Azure Pipeline yaml 的示例 其中调用另一个 yaml 文件 job BuildFunctions steps each func in parameters f
  • 在 Project Rider 中启用 c# 7 编译

    如果我将 LangVersion 设置为 7 则会出现以下错误 Error CS1617 Invalid option 7 for langversion must be ISO 1 ISO 2 Default or an integer
  • 近调用/跳转表并不总是在引导加载程序中工作

    一般问题 我一直在开发一个简单的引导加载程序 并在某些环境中偶然发现了一个问题 在这些环境中 此类指令不起作用 mov si call tbl SI Call table pointer call call tbl Call print c