使用寄存器与值调用 JMP 的不同行为

Different behavior calling JMP with register vs value

我正在尝试执行到地址 0x7C00 的绝对跳转,作为业余爱好过程的一部分 OS。我在 GAS 中使用 intel 语法并在 QEMU 中进行测试。我尝试了两种方法:

jmp 0x00007c00

mov eax, 0x00007C00
jmp eax

第二种方法似乎按我的预期工作并跳转到 0x7C00,但第一种方法导致 QEMU 崩溃并指出它是 "trying to execute code outside RAM or ROM at 0x40007c00"。有谁知道为什么它跳转到不同的地址并且高位字节被设置为 0x4000?

编辑:

我在拆解的时候分别收到了以下信息:

  3c:   e9 fc 7b 00 00          jmp    7c3d <int32_end+0x7ad4>

  3c:   b8 00 7c 00 00          mov    [=13=]x7c00,%eax
  41:   ff e0                   jmp    *%eax

所以他们的编译方式不同,尽管我仍然对第二个到底在做什么感到困惑,它看起来像是跳转到 0x7c3d

答案竟然是评论中的一系列见解:

首先是反汇编代码,看到 jmp 汇编成一个 near jmp rel32。事实证明,所有 x86 直接近跳转都是相对的。 felixcloutier.com/x86/jmp 显示此 jmp 使用的 E9 操作码的编码。要编码正确的 rel32 偏移量以到达给定的绝对目标地址,汇编器 + 链接器需要知道跳转指令 运行 来自的地址。

源代码中的

jmp 0x00007c00 为它提供了 0x00007c00 的绝对跳转目标,但汇编程序将通过相对跳转到达它。它与 jmp .+0x7c00 或直接指定 rel32 位移不同。如果指令本身在一个文件中写入两次,然后将其组装+链接到 ELF 可执行文件(例如 gcc -static -nostdlib foo.s && objdump -drwC -Mintel a.out),则可以很容易地看到这一点。这里的两条 jmp 指令具有不同的编码(不同的 rel32)和相同的绝对目标。此外,在观察最终的精灵时,您可以看到汇编程序以相对跳转到达 0x7C000x3ff06723,因为代码地址从 0xC0100000 开始)。

我遇到的一个问题是,当我的代码应该从 0xC0100000 开始时,地址和跳转似乎太小了。我没有意识到 .o 文件中的地址是相对于文件开头的,因此指令位于 0x3c 处,这是它在文件中的偏移量。如果我反汇编链接后生成的最终 elf 文件,所有地址都会添加 0xC0100000

另外值得注意的是,为了方便起见,反汇编程序正在计算绝对地址 7c3d,这是基于它在该指令末尾显示的地址。实际相对位移是little-endianfc 7b 00 00 = 0x00007bfc。由于我正在反汇编一个 .o 文件,链接器还没有填充真正的位移。这可以通过使用 objdump -drwC -Mintel.

来避免

为了使执行跳转的代码与位置无关,最好的方法是坚持使用第二种方法中的 mov-immediate + jmp eax