为什么BIOS入口点以WBINVD指令开头? [英] Why does the BIOS entry point start with a WBINVD instruction?
问题描述
我正在研究计算机中的BIOS代码(x86_64 Linux,IvyBridge).我使用以下过程转储BIOS代码:
I'm investigating the BIOS code in my machine (x86_64 Linux, IvyBridge). I use the following procedure to dump the BIOS code:
$ sudo cat /proc/iomem | grep ROM
000f0000-000fffff : System ROM
$ sudo dd if=/dev/mem of=bios.dump bs=1M count=1
然后我使用radare2
读取并反汇编二进制转储:
Then I use radare2
to read and disassemble the binary dump:
$ 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
我知道x86处理器初始化总是从16位8086环境开始,并且要执行的第一条指令在f000:fff0
,即0xffff0
.所以我去那个地方并反汇编代码.
I know x86 processor initialization always starts with a 16-bit 8086 environment, and the first instruction to be executed is at f000:fff0
, i.e. 0xffff0
. So I go to that location and disassemble the code.
令我惊讶的是,第一条指令是 WBINVD
,其功能是使高速缓存,这在处理器开机或重置时似乎无关紧要.我希望第一条指令只是对较低内存地址的jmp
.
To my surprise, the first instruction is WBINVD
, whose functionality is to invalidate the cache, which seems to be irrelevant when the processor is powered on or reset. I would expect the first instruction to be simply a jmp
to a lower memory address.
为什么在jmp
之前有WBINVD
?
我已经搜索了英特尔手册第3卷第9章处理器管理和初始化"的相关部分,但未提及WBINVD
.我还搜索了一些在线资源,但没有找到任何解释.
I've already searched the relevant portion of the Intel manuals, Volume 3 Chapter 9 Processor Management and Initialization, but it doesn't mention anything about WBINVD
. I also searched some online resources but didn't find any explanation.
按照0xff51c
指令执行0xff51c
后,该代码更有趣;它正在进行自我检查:
After following the jmp
instruction to 0xff51c
, the code is more interesting; it's doing a self-check:
[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
总而言之,这个BIOS代码是在0xffff0
处读取自身并将字节与0xea
进行比较,而这恰好是一个远跳操作码:
To conclude the weirdness, this BIOS code is reading itself at 0xffff0
and comparing the byte with 0xea
, which is exactly the opcode of a far jump:
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
如果发现0xffff0
处的代码有很大的距离,那么它将陷入无限循环.
If it finds the code at 0xffff0
is a far jump, then it will go into an infinite loop.
更准确地说,AP(应用处理器)将按hlt
指令无限循环,而BSP(引导处理器)将循环回到起始0xffff0
.由于0xffff0
处的代码不会更改,因此可以断定BSP总是会发现该字节为0xea
且永远不会跳出循环.
More precisely, the APs (Application Processors) will loop infinitely at the hlt
instruction, while the BSP (Boot Strap Processor) will loop back to the beginning 0xffff0
. Since the code at 0xffff0
won't be changed, we can conclude the BSP will always find the byte being 0xea
and will never go out of the loop.
那么这种自我检查的目的是什么?我简直无法相信这是天真地防止修改.
So what's the purpose of this self-checking? I can hardly believe it's a naive attempt to prevent modification.
推荐答案
尽管很难推理,但请记住,即使es
设置为
Albeit hard to reason about, remember that the load mov al, byte es:[0xfff0]
is not reading from the the BIOS first instruction, even though es
is set to 0xf000
.
从0xfffffff0
中读取第一条指令,PCH在复位时也可能会将0xf0000-0xfffff
别名为0xffff0000-0xffffffff
,因此,启动BSP时,它将执行您转储的代码.
IIRC,除非明确唤醒,否则AP不会启动.
The first instruction is read from 0xfffffff0
, the PCH will also probably alias 0xf0000-0xfffff
to 0xffff0000-0xffffffff
at reset, so when the BSP is booted it will execute the code you dumped.
IIRC, the APs don't boot unless explicitly waken up.
然后BSP将继续初始化硬件(从转储中判断).
在某些时候,它将设置0xf0000-0xfffff
的属性映射,以引导对内存的读取和写入(或只是写入然后读取).
最终结果是,当处理器(硬件线程)启动时,它将执行闪存中的代码,直到执行远距离跳转为止.
此时,cs
基数已根据实模式规则正确计算(非常类似于非实模式),并且指令将从0xf0000-0xfffff
(即从RAM)获取.
所有这一切,而cs
段值实际上并没有改变.
The BSP will then will proceed with initialising the HW (judging from the dump).
At some point it will set the attribute map for the 0xf0000-0xfffff
to steer reads and writes (or just writes and then reads) to memory.
The end result is that when a processor (an HW thread) boots it will execute the code from the flash until it perform a far jump.
At the point the cs
base is correctly computed as per real-mode rules (pretty much like the unreal mode) and the instruction will be fetched from the 0xf0000-0xfffff
(i.e. from the RAM).
All of this while the cs
segment value didn't actually change.
BSP将在某个时候启动其多处理器初始化例程,在该例程中,它向所有人(包括他自己)广播INIT-SIPI-SIPI,这将导致AP进入睡眠状态,并且BSP会出现ljmp 0xf000:0xfff0
.
这里的技巧是,跳转目标0xf000:0xfff0
与wbinvd
指令的总线地址不同.
那里可能还有其他东西,可能是另一个初始化例程.
The BSP at some point will start its multiprocessor initialisation routine, where it broadcasts to everyone (including himself) an INIT-SIPI-SIPI that will result in a sleep for the APs and a ljmp 0xf000:0xfff0
for the BSP.
The trick here is that the target of the jump, 0xf000:0xfff0
, is not the same bus address of the wbinvd
instruction.
There could be something else there, probably another initialisation routine.
在初始化结束时,BIOS可以简单地重置0xf0000-0xfffff
的属性以落入闪存(因此可以进行软件重置),从而防止(无意间)转储中间代码.
At the end of the initialisation the BIOS could simply reset the attributes of the 0xf0000-0xfffff
to fall through to the flash (so a software reset is possible), preventing (not intentionally) a dump of the intermediary code.
这不是很有效,但是BIOS通常不是代码的杰作.
This is not very efficient, but BIOSes are not usually masterpieces of code.
我没有足够的信息来确定正在发生的事情,我的意思是,ljmp 0xf000:0xfff0
和mov al, byte es:[0xfff0]
不必从它们所在的同一区域读取.
考虑到这一点,所有赌注都关闭了.
只有适当的逆向工程才能证明一切.
I don't have enough element to be sure what's going on, my point is that the ljmp 0xf000:0xfff0
and the mov al, byte es:[0xfff0]
doesn't have to read from the same region they reside in.
With this in mind, all bets are off.
Only a proper reverse engineering will tell.
关于wbinvd
,我在评论中建议它可能与热启动功能有关,而Peter Cordes建议它可能特别与cache-as-RAM有关.
这是有道理的,但我想永远都不确定.
这也可能是对货物狂热的一种情况,在这种情况下,程序员基于传言认为该指令是必要的.
Regarding the wbinvd
, I suggested in the comment it could be related to the warm boot facility and Peter Cordes suggested that it may specifically have to do with cache-as-RAM.
It makes sense, I guess will never be sure though.
It could as well be a case of cargo cult, where a programmer deemed the instruction necessary based rumors.
这篇关于为什么BIOS入口点以WBINVD指令开头?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!