- 论坛徽章:
- 0
|
qiuhan
2007.8.20
今天我们继续以内存映像为主线来分析init386.
代码为于:
sys/i386/i386/machdep.c
传给init386的参数为第一个可用页面基址:
(gdb) p/x first
$1 = 0x1025000
首先为thread0设置堆栈和pcb指针:
(gdb) p thread0.td_pcb
$4 = (struct pcb *) 0xc1020d90
(gdb) p/x sizeof(struct pcb)
$6 = 0x270
0xc1020d90 + 0x270 = 0xC1021000
接着设置preload_metadata, preload_bootstrap_relocate把区域中的地址信息加上3G的偏移, 这样我们在后面就可用通过preload_metadata找到loader时加载的模块或者二进制文件(详见《loader分析》).
设置kern_envp,以查找环境变量。 紧接着的init_param1就根据该值初始化一些基本的环境变量。
设置gdt,ldt,pc, idt, tr, 这里不详说。
vm86_initialize
初始化vm86 区域内存映射, 为在保护模式下执行vm86函数做准备。紧接着的getmemsize就是使用vm86 函数来获取内存大小。
* +--------+ vm86phystk 0x01021000
* | page 0 |
* | | +--------+
* | | | stack |
* | | |--------| <--------- pcb_ebp 0xc1021fa8
* | | |vm86frame| 0x58
* +--------+ +--------+ <--------- vm86paddr 0x01022000
* | | |Page Tbl| 1M + 64K = 272 entries = 1088bytes
* | | +--------+ vm86pcb 0x01022440
* | | | PCB | size: ~240 bytes
* | page 1 | |PCB Ext | size: ~140 bytes (includes TSS)
* | | +--------+
* | | |int map |
* | | +--------+
* +--------+ | |
* | page 2 | | I/O |
* +--------+ | bitmap |
* | page 3 | | |
* | | +--------+
* +--------+ 0x01025000
vm86 区域内存映射图
真正映射到Page Tbl上的空间为:0-4k(rwu), basemem-640k, 640k-1M(rwu)
vm86paddr以上的内存由vm86_layout描述:
struct vm86_layout {
pt_entry_t vml_pgtbl[PGTABLE_SIZE]; //272
struct pcb vml_pcb; //offset =0x440
struct pcb_ext vml_ext; //offset =0x6b0
char vml_intmap[INTMAP_SIZE]; //32 offset =0x73c
char vml_iomap[IOMAP_SIZE]; //8192 offset =0x75c
char vml_iomap_trailer; //offset = 0x275c
};
(gdb) p &((struct vm86_layout *)0x0)->vml_pcb
$29 = (struct pcb *) 0x440
* pcb_esi = new PTD entry 0 //0x1022007
* pcb_ebp = pointer to frame on vm86 stack //0xc1021fa8
* pcb_esp = stack frame pointer at time of switch
* pcb_ebx = va of vm86 page table
* pcb_eip = argument pointer to initial call
(gdb) p/x *(struct pcb *)vm86pcb
$21 = {pcb_cr3 = 0x0, pcb_edi = 0x0, pcb_esi = 0x1022007,
pcb_ebp = 0xc1021fa8, pcb_esp = 0xc1020b90, pcb_ebx = 0xc1022000,
pcb_eip = 0xc1020ccc, pcb_dr0 = 0x0, pcb_dr1 = 0x0, pcb_dr2 = 0x0,
...
invltlb()
重新读入cr3以使tlb更新。
getmemsize
一个很有意思的函数。参数为第一个可用页面,即0x1025000,输出为:
(gdb) p/x phys_avail
$14 = {0x1000, 0x9f000, 0x100000, 0x400000, 0x1025000, 0x7fe8000, 0x0, 0x0, 0x0, 0x0}
通过vm86_intcall(0x12, &vmf)计算basemem(得到639), 然后把basemem到640k之间的空间映射到vm86paddr; 通过INT 15:E820得到内存map
(gdb) p/x physmap
$45 = {0x0, 0x9fc00, 0x100000, 0x7ff0000, 0x0 <repeats 12 times>}
然后向内存反复写'0','1'来判断内存是否正常。
这里有2个地方比较有意思:
1 vm86_intcall的调用
修改IdlePTD的一个映射为vm86paddr,然后重新加载cr3,以按照vm86 Page Tbl 的方式来映射0-0xffffc(1M空间,线性映射,即物理地址和虚拟地址相同)
(gdb) xp/4x 0x0101e000
0x101e000: 0x01022027 0x01001023 0x01002023 0x01003003
伪造了一个vm86frame,其中eip为0xa00, eflags中包含VM位, 然后jmp doreti,伪装成从内核返回的样子[FIXME]。
eflags 0x86 [ PF SF ]
eflags 0xa0202 [ IF VM VIF ]
2 检测内存只检测每个页面的前4个字节,以点代面.而且每次都是向0xc1402000 这个地址写入数据,然后读出并判断。为什么呢? 答案是改变pte。 0xc1402000 这个地址对于的pte是0x01005008,而0x01005008 这个物理地址对应的虚拟地址是0xbff05008, 每次都是通过修改0xbff05008 的值来映射到不同的页面。
++++++++++++++++++++++++++++++++++++++++++++++++
addr=0xc1402000, cr3=0x0101e000, a20_mask=0xffffffff
pde_addr=0x0101ec14, pde=0x01005063
pte_addr=0x01005008, pte=0x0000201b, page_offset=0x00000000
paddr=0x00002000
++++++++++++++++++++++++++++++++++++++++++++++++
addr=0xbff05008, cr3=0x0101e000, a20_mask=0xffffffff
pde_addr=0x0101ebfc, pde=0x0101e063
pte_addr=0x0101ec14, pte=0x01005063, page_offset=0x00000000
paddr=0x01005008
怎么从0xc1402000 得到它对应的pte的虚拟地址呢?
内核是通过vtopte来实现的。
vtopte(va) = PTmap + (va>>12)<<2
(gdb) p PTmap
$34 = 0xbfc00000
把va=0xc1402000 带入算算是不是等于0xbff05008?
还记得create_pagetables中建立的最后一个pde是什么吗?
(gdb) x/4x 0x0101ebf8
0x101ebf8: 0x00000000 0x0101e003 0x01000003 0x00400083
没错,就是0x0101ebfc, 它把页表映射到自身。不好理解吧,我们结合例子来看一下:
对0xbff05008做映射,第一级映射是从pde到pte,pde_addr=0x0101ebfc, pde=0x0101e063,
可以发现页表的基址就是0x0101e000, 和cr3的值相同,也就是说它欺骗mmu做了一次无用功; 第二级
映射从pte到物理地址,pte_addr=0x0101ec14, pte=0x01005063,此时的映射和正常情况下的
第一级映射一样,得到地址就是pte的物理地址0x01005008。
#define NPDEPTD 0x400
#define NPGPTD 1
#define KVA_PAGES 256
/* number of page tables/pde's */
#define NKPDE (KVA_PAGES) 256
#define KPTDI (NPDEPTD-NKPDE) =0x300
#define PTDPTDI (KPTDI-NPGPTD) =0x2ff
(gdb) p/x phys_avail
$14 = {0x1000, 0x9f000, 0x100000, 0x400000, 0x1025000, 0x7fe8000, 0x0, 0x0, 0x0, 0x0}
这个结构在vm_page_startup(通过vm_mem_init调用)用来初始化内存模块,后面我们会说到,
这里简单说明一下:
第0, 1和 2, 3...分别代表可用空间的起始地址和结束地址,例如:
0x1000 - 0x9f000 是一段可用内存,而0x9f000 - 0x100000则不是可用的。
第0个页面(0x1000以下)含有bios代码, 不可用;
0x9f000(636k)是basemem(0x9fc00, 639k)页面对齐的结果, 从basemem到ISA_HOLE_START(640k)可能含有 bios代码或数据, 不可用;
ISA_HOLE_START 到 0x100000含有ISA,不可用;
0x400000 - 0x1025000, 存放着kernel,不可用;
0x7fe8000 - 0x7ff0000, message buff(dmesg?[FIXME]), 不可用.
MSGBUF_SIZE 8p 在物理内存的尾端为message buffer预留了8p
|----------------| 0x7ff0000(128M)
| message buff |
|----------------| 0x7fe8000
| ... |
|----------------| 0x1025000
| kenel acpi... |
|----------------| 0x400000
| ... |
|----------------| 0x100000
| isa |
|----------------| 0x9f000
| ... |
|----------------| 0x1000
| bios |
|----------------| 0x0
内存映像概况(...代表的空间是可用的) |
|