为什么BIOS入口点以WBINVD指令开始?

use*_*622 13 boot x86 assembly bios

我正在研究我的机器中的BIOS代码(x86_64 Linux,IvyBridge).我使用以下过程来转储BIOS代码:

$ sudo cat /proc/iomem | grep ROM
  000f0000-000fffff : System ROM
$ sudo dd if=/dev/mem of=bios.dump bs=1M count=1
Run Code Online (Sandbox Code Playgroud)

然后我radare2用来读取和反汇编二进制转储:

$ r2 -b 16 bios.dump 
[0000:0000]> s 0xffff0
[f000:fff0]> pd 3
        :   f000:fff0      0f09           wbinvd
        `=< f000:fff2      e927f5         jmp 0xff51c
            f000:fff5      0000           add byte [bx + si], al
Run Code Online (Sandbox Code Playgroud)

我知道x86处理器初始化始终以16位8086环境开始,并且要执行的第一条指令是at f000:fff0,即0xffff0.所以我去那个地方并反汇编代码.

令我惊讶的是WBINVD,第一条指令的功能是使缓存无效,这在处理器上电或复位时似乎无关紧要.我希望第一条指令只是jmp一个较低的内存地址.

为什么WBINVD以前有jmp

我已经搜索了英特尔手册第3卷第9章处理器管理和初始化的相关部分,但它没有提及任何相关内容WBINVD.我也搜索了一些在线资源,但没有找到任何解释.

编辑以获取更多信息:

遵循jmp指令后0xff51c,代码更有趣; 它正在进行自我检查:

[f000:f51c]> pd
            f000:f51c      dbe3           fninit
            f000:f51e      0f6ec0         movd mm0, eax
            f000:f521      6631c0         xor eax, eax
            f000:f524      8ec0           mov es, ax
            f000:f526      8cc8           mov ax, cs
            f000:f528      8ed8           mov ds, ax
            f000:f52a      b800f0         mov ax, 0xf000
            f000:f52d      8ec0           mov es, ax
            f000:f52f      6726a0f0ff00.  mov al, byte es:[0xfff0]     ; [0xfff0:1]=0
            f000:f536      3cea           cmp al, 0xea
        ,=< f000:f538      750f           jne 0xff549
        |   f000:f53a      b91b00         mov cx, 0x1b
        |   f000:f53d      0f32           rdmsr  ; check BSP (Boot Strap Processor) flag, if set, loop back to 0xffff0; otherwise, infinite hlt
        |   f000:f53f      f6c401         test ah, 1
       ,==< f000:f542      7441           je 0xff585
      ,===< f000:f544      eaf0ff00f0     ljmp 0xf000:0xfff0
      ||`-> f000:f549      b001           mov al, 1
      ||    f000:f54b      e680           out 0x80, al
      ||    f000:f54d      66be8cfdffff   mov esi, 0xfffffd8c          ; 4294966668
      ||    f000:f553      662e0f0114     lgdt cs:[si]
      ||    f000:f558      0f20c0         mov eax, cr0
      ||    f000:f55b      6683c803       or eax, 3
      ||    f000:f55f      0f22c0         mov cr0, eax
      ||    f000:f562      0f20e0         mov eax, cr4
      ||    f000:f565      660d00060000   or eax, 0x600
      ||    f000:f56b      0f22e0         mov cr4, eax
      ||    f000:f56e      b81800         mov ax, 0x18
      ||    f000:f571      8ed8           mov ds, ax
      ||    f000:f573      8ec0           mov es, ax
      ||    f000:f575      8ee0           mov fs, ax
      ||    f000:f577      8ee8           mov gs, ax
      ||    f000:f579      8ed0           mov ss, ax
      ||    f000:f57b      66be92fdffff   mov esi, 0xfffffd92          ; 4294966674
      ||    f000:f581      662eff2c       ljmp cs:[si]
      |`.-> f000:f585      fa             cli
      | :   f000:f586      f4             hlt
      | `=< f000:f587      ebfc           jmp 0xff585
Run Code Online (Sandbox Code Playgroud)

总结一下这个奇怪的问题,这个BIOS代码正在自己读取0xffff0并比较字节0xea,这正是远程跳转的操作码:

            f000:f52a      b800f0         mov ax, 0xf000
            f000:f52d      8ec0           mov es, ax
            f000:f52f      6726a0f0ff00.  mov al, byte es:[0xfff0]     ; [0xfff0:1]=0
            f000:f536      3cea           cmp al, 0xea
Run Code Online (Sandbox Code Playgroud)

如果它发现代码处于0xffff0远处跳转,那么它将进入无限循环.

更准确地说,AP(应用程序处理器)将无限循环hlt指令,而BSP(引导程序处理器)将循环回到开头0xffff0.由于代码0xffff0将不会被更改,我们可以得出结论,BSP将始终找到该字节,0xea并且永远不会离开循环.

那么这种自我检查的目的是什么?我简直不敢相信这是一种防止修改的天真尝试.

Mar*_*oom 7

尽管很难推理,但请记住mov al, byte es:[0xfff0],即使es设置为0xf000.

第一条指令是从 读取的0xfffffff0,PCH 也可能0xf0000-0xfffff0xffff0000-0xffffffff在复位时别名为,因此当 BSP 启动时,它将执行您转储的代码。
IIRC,除非明确唤醒,否则 AP 不会启动。

然后 BSP 将继续初始化 HW(从转储判断)。
在某些时候,它将设置属性映射0xf0000-0xfffff以将读取和写入(或仅写入然后读取)引导到内存。
最终结果是,当处理器(硬件线程)启动时,它将执行闪存中的代码,直到执行远跳转。
在这一点上cs,根据实模式规则正确计算基数(非常类似于虚模式),并且将从0xf0000-0xfffff(即从 RAM)获取指令。
所有这一切,而cs段值实际上并没有改变。

BSP 在某个时刻将启动它的多处理器初始化例程,在那里它向每个人(包括他自己)广播一个 INIT-SIPI-SIPI,这将导致 AP 和ljmp 0xf000:0xfff0BSP进入睡眠状态。
这里的技巧是跳转的目标,0xf000:0xfff0,与wbinvd指令的总线地址不同。
那里可能还有其他东西,可能是另一个初始化例程。

在初始化结束时,BIOS 可以简单地将 的属性0xf0000-0xfffff重置为闪存(因此可以进行软件重置),从而防止(非故意)转储中间代码。

这不是很有效,但 BIOS 通常不是代码的杰作。

我没有足够的元素来确定发生了什么,我的观点是ljmp 0xf000:0xfff0mov al, byte es:[0xfff0]不必从它们所在的同一区域读取。考虑
到这一点,所有赌注都没有了。
只有适当的逆向工程才能说明问题。

关于wbinvd,我在评论中建议它可能与热启动设施有关,而 Peter Cordes 建议它可能特别与缓存即 RAM 有关。
这是有道理的,但我想永远不会确定。
这也可能是一个货物崇拜的例子,程序员认为必要的指令是基于谣言。

  • @MichaelPetch 我终于找到了一个安装了旧版 linux 的旧磁盘,所以我进行了测试。你的猜测是对的!当机器以传统模式启动时,`0xffff0` 代码与`0xfffffff0` 代码完全不同。高内存代码与有问题的代码相同。较低的1M BIOS 一开始就跳得很远,到不同的位置,下面的代码也完全不同。所以这个检查是为了区分传统的 BIOS 和 UEFI 模式。 (4认同)
  • 如果您对这个案例仍然感兴趣,请查看我的回答。我想我找到了一个消息来源,证实了你对这起货物崇拜案件的怀疑。 (2认同)