除錯kernel時對bios加電後過程的一些心得
加電後cpu 的 狀態是:
eax:0×00000000, ebx:0×00000000, ecx:0×00000000, edx:0×00000543
ebp:0×00000000, esp:0×00000000, esi:0×00000000, edi:0×00000000
eip:0x0000fff0, eflags:0×00000002, inhibit_mask:0
cs:s=0xf000, dl=0x0000ffff, dh=0xff009bff, valid=1
ss:s=0×0000, dl=0x0000ffff, dh=0×00009300, valid=1
ds:s=0×0000, dl=0x0000ffff, dh=0×00009300, valid=1
es:s=0×0000, dl=0x0000ffff, dh=0×00009300, valid=1
fs:s=0×0000, dl=0x0000ffff, dh=0×00009300, valid=1
gs:s=0×0000, dl=0x0000ffff, dh=0×00009300, valid=1
ldtr:s=0×0000, dl=0×00000000, dh=0×00000000, valid=0
tr:s=0×0000, dl=0×00000000, dh=0×00000000, valid=0
gdtr:base=0×00000000, limit=0xffff
idtr:base=0×00000000, limit=0xffff
dr0:0×00000000, dr1:0×00000000, dr2:0×00000000
dr3:0×00000000, dr6:0xffff0ff0, dr7:0×00000400
cr0:0×00000010, cr1:0×00000000, cr2:0×00000000
cr3:0×00000000, cr4:0×00000000
通過cs:ip可以發現第一條指令是在0xf000:0xfff0 執行的. 如果只讀過大學那些涉及皮毛的書你肯定會認為其實體地址就是0xffff0 (0xf000<<1+0xfff0)。但這樣的認識僅僅是針對i8086 ,真正的x86 架構即i80386 之後是這樣的:cpu剛剛設定cs:0xf000的時候其實並不是像課本上說的那樣在真實模式下。此時的實體地址需要根據cs中的隱藏暫存器dl=0x0000ffff, dh=0xff009bff中去查。
即執行的第一條指令也在這裡:
(0) [0xfffffff0] f000:fff0 (unk. ctxt): jmp far f000:e05b ; ea5be000f0
跳轉到真正BIOS程式的入口地址
這裡做很精妙,因為intel考慮到相容性問題設定了cpu加電後cs:ip=0xf000:0xfff0: 如果是i8086那麼該跳轉指令將在16位cpu可定址最大空間1MB(基本記憶體)的最後16Byte,如果是i80386則將在32位cpu可定址最大空間4GB的最後16Byte。
再反彙編看一下:
<bochs:3> x /10 0xf000:0xfff0
[bochs]:
0x000ffff0 <bogus+ 0>: 0x00e05bea 0x2f3730f0 0x302f3630 0xe4fc0035
0×00100000 <bogus+ 16>: 0×00000000 0×00000000 0×00000000 0×00000000
0×00100010 <bogus+ 32>: 0×00000000 0×00000000
<bochs:4> u /10
000ffff0: ( ): jmp far f000:e05b ; ea5be000f0
000ffff5: ( ): xor byte ptr ds:[bx], dh ; 3037
000ffff7: ( ): das ; 2f
000ffff8: ( ): xor byte ptr ds:0x302f, dh ; 30362f30
000ffffc: ( ): xor ax, 0xfc00 ; 3500fc
000fffff: ( ): in al, 0×0 ; e400
00100001: ( ): add byte ptr ds:[bx+si], al ; 0000
00100003: ( ): add byte ptr ds:[bx+si], al ; 0000
00100005: ( ): add byte ptr ds:[bx+si], al ; 0000
00100007: ( ): add byte ptr ds:[bx+si], al ; 0000
這裡應為CR0的PE標誌復位所以會顯示不正確的實體地址(000ffff0,00100007等)
2.
但剛才那種即非真實模式也非保護模式的特殊定址方式不會持續很久,因為intel規定當這時cs再次載入值時cpu將完全進入真實模式。也就是說剛才jmp
far f000:e05b 導致cs載入0xf000(注意這個是長跳轉哦) 這就使cpu進入真實模式,而且實體地址即0xf000<1+0xe05b=0xfe05b (真實模式段長64KB)
反彙編看一下程式碼:
(0) [0x000fe05b] f000:e05b (unk. ctxt): xor ax, ax ; 31c0
<bochs:3> n
Next at t=2
(0) [0x000fe05d] f000:e05d (unk. ctxt): out 0xd, al ; e60d
<bochs:4>
Next at t=3
(0) [0x000fe05f] f000:e05f (unk. ctxt): out 0xda, al ; e6da
現在的程式碼都是在1MB空間裡執行的。
3.
執行完BIOS程式碼後(現在的BIOS 都相當大和複雜)預設從fd0(IBM PC傳承的傳統)將第一扇區的內容copy到實體地址0x7c00。 但有趣的是這裡0x7c00處是一個長跳轉指令,而且呈現的虛擬地址是0×0:0x7c00:
(0) [0x00007c00] 0000:7c00 (unk. ctxt): jmpfar
07c0:0005 ; ea0500c007
單步執行後:
(0) [0x00007c05] 07c0:0005 (unk. ctxt): mov ax, cs ; 8cc8
可以看到重新載入了cs暫存器,但實體地址還是緊接著跳轉指令。從dump_cpu中也可以看到:
eax:0x0fffaa55, ebx:0×00000000, ecx:0×00110001, edx:0×00000000
ebp:0×00000000, esp:0x0000fffe, esi:0×00007362, edi:0x0000ffde
eip:0×00000005, eflags:0×00000082, inhibit_mask:0
cs:s=0x07c0, dl=0x7c00ffff, dh=0x00009b00, valid=1
ss:s=0×0000, dl=0x0000ffff, dh=0×00009300, valid=7
ds:s=0×0000, dl=0x0000ffff, dh=0×00009300, valid=1
es:s=0×0000, dl=0x0000ffff, dh=0×00009300, valid=1
fs:s=0×0000, dl=0x0000ffff, dh=0×00009300, valid=1
gs:s=0×0000, dl=0x0000ffff, dh=0×00009300, valid=1
ldtr:s=0×0000, dl=0×00000000, dh=0×00000000, valid=0
tr:s=0×0000, dl=0×00000000, dh=0×00000000, valid=0
gdtr:base=0×00000000, limit=0xffff
idtr:base=0×00000000, limit=0xffff
dr0:0×00000000, dr1:0×00000000, dr2:0×00000000
dr3:0×00000000, dr6:0xffff0ff0, dr7:0×00000400
cr0:0×00000010, cr1:0×00000000, cr2:0×00000000
cr3:0×00000000, cr4:0×00000000
不過這樣做的目的還真的不太清楚了
4.
進入保護模式後bochs的偏移地址會變更為32位 :
(0) [0x00007c46] 07c0:0046 (unk. ctxt): mov ax, 0×1 ; b80100
<bochs:1407>
Next at t=483362698
(0) [0x00007c49] 07c0:0049 (unk. ctxt): lmsw ax ; 0f01f0
<bochs:1408>
Next at t=483362699
(0) [0x00007c4c] 07c0:0000004c (unk. ctxt): jmp far 0008:0000 ; ea00000800