前几天折腾了一下kernel 并debug了引导和初始化部分,有一些心得,更正了一直以来的很多错误认识。
这里还是拿linux 0.00 的代码来实验,制作好引导盘后启动 bochs (已重新编译加了 –enable-debugger –enable-disasm 参数)
下图是IBM PC内存的分布状况:
(注意:
0xA0000
is the pointer address to the Graphical Mode and 0xB8000 is the pointer address to the Color Text Mode and 0xB0000 is the pointer to the Monochrome text Mode. )
尽管上面很多区域在bios初始化后填充了内容但其实是可以将kernel 载入
除640KB – 1MB 以及4G高位内存附近的任何存储空间中。因为BIOS提供的参数数据和中断向量kernel并不使用,可以被直接覆盖。
下面总结一下整个过程:
1.
加电后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中去查。这个其实就是段描述符(至少在保护模式下是,这里也暂时这样称呼吧)
dh的最高8位和最低8位以及dl的高16位组成了32位段基址:0xffff0000 再加上ip:0xfff0 那么物理地址就是0xfffffff0 正好是4GB空间的最后16Byte。
即执行的第一条指令也在这里:
(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。
再反汇编看一下:
x /10 0xf000:0xfff0
[bochs]:
0x000ffff0 : 0x00e05bea 0x2f3730f0 0x302f3630 0xe4fc0035
0×00100000 : 0×00000000 0×00000000 0×00000000 0×00000000
0×00100010 : 0×00000000 0×00000000
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
n
Next at t=2
(0) [0x000fe05d] f000:e05d (unk. ctxt): out 0xd, al ; e60d
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
Next at t=483362698
(0) [0x00007c49] 07c0:0049 (unk. ctxt): lmsw ax ; 0f01f0
Next at t=483362699
(0) [0x00007c4c] 07c0:0000004c (unk. ctxt): jmp far 0008:0000 ; ea00000800
中国在能够输出自己的价值观之前不会成为真正的强国