免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 3742 | 回复: 2
打印 上一主题 下一主题

init386 分析 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2007-10-14 18:06 |只看该作者 |倒序浏览
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
   内存映像概况(...代表的空间是可用的)

论坛徽章:
0
2 [报告]
发表于 2007-10-14 22:54 |只看该作者
劲...

论坛徽章:
0
3 [报告]
发表于 2007-10-14 23:26 |只看该作者
好文,支持是必须的
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP