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.
jmp
instruction being re-fetched? – Maillol0xffff0
could be very little related to the real boot strap code. Execution starts withcs:ip = 0xf000:0xfff0
but thecs
nevertheless uses0xffff0000
as a base, resulting in a physical address of0xfffffff0
. The PCH aliases the legacy ROM hole to this upper range until the BIOS itself initialises it properly. Usually the code in the lower range is the same as the one in the upper range but you should look near the 4GiB to be sure. Finally, remember that legacy BIOS introduced the warm boot, so you may be at0xffff0
not necessarily only after a boot. – Graciagracie0xffff0000
thing, but with the OP's update this looks very much like what you'd expect. (Saving EAX into MM0 had me suspicious, but then I realized it wasn't XMM0, so it doesn't need the SSE-enable bits in control registers to be set for it to work.) Unless there's similar code elsewhere? – Maillol0xffff0000
thing, it's the same, so I believe this is the boot code. I'm now more confused by the self-checking thing in the code, as you can see from my update. Do you have any thought about the self-check? I really can't believe it's a silly attempt to prevent code modification. – ArgileWBINVD
into a far jump so the second time around the boot processor follows that while the others will wait in theHLT
until the BSP initializes them. – KylixJMP
into aWBINVD
". – Wittman