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
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
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.
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.
Why is there a WBINVD
before jmp
?
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.
Edit for more info:
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
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
If it finds the code at 0xffff0
is a far jump, then it will go into an infinite loop.
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.