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并且永远不会离开循环.
那么这种自我检查的目的是什么?我简直不敢相信这是一种防止修改的天真尝试.
尽管很难推理,但请记住mov al, byte es:[0xfff0],即使es设置为0xf000.
第一条指令是从 读取的0xfffffff0,PCH 也可能0xf0000-0xfffff会0xffff0000-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:0xfff0和mov al, byte es:[0xfff0]不必从它们所在的同一区域读取。考虑
到这一点,所有赌注都没有了。
只有适当的逆向工程才能说明问题。
关于wbinvd,我在评论中建议它可能与热启动设施有关,而 Peter Cordes 建议它可能特别与缓存即 RAM 有关。
这是有道理的,但我想永远不会确定。
这也可能是一个货物崇拜的例子,程序员认为必要的指令是基于谣言。