Chinaunix

标题: [FreeBSD]x86地址映射实例 [打印本页]

作者: qiuhanty    时间: 2007-08-15 15:04
标题: [FreeBSD]x86地址映射实例
x86地址映射实例
qiuhan
2007.8.15

今天我们通过qemu来探讨freeBSD下x86地址映射。
用户地址空间的映射:
我们以调试auditd为例
# qgdb auditd
(gdb) b main
Breakpoint 1 at 0x804b594: file /kerndebug/umbrella/usr.sbin/auditd/../../contrib/audit_supt/auditd/auditd.c, line 1140.
(gdb) target remote :1234
Remote debugging using :1234
0xc08daf1c in .rtld_start () from /libexec/ld-elf.so.1
(gdb) c
Continuing.
Breakpoint 1, main (argc=-1077940752, argv=0x0)
    at /kerndebug/umbrella/usr.sbin/auditd/../../contrib/audit_supt/auditd/auditd.c:1138
1138    {
(gdb) cpu_dump
ldtr:s=0x0050, bs=0xc0a73ae0, lm=0x00000087, flag=0x0000e200
tr:s=0x0048, bs=0xc0a73dc0, lm=0x00000067, flag=0xc00089a7
gdtr:base=0xc0a73a40, limit=0x97
idtr:base=0xc0a73f20, limit=0x7ff
dr0:0x00000000, dr1:0x00000000, dr2:0x00000000
dr3:0x00000000, dr6:0x00000000, dr7:0x00000000
cr0:0xe005003b, cr1:0x00000000, cr2:0x281d9028
cr3:0x079b6000, cr4:0x00000690
(gdb) p $eip
$1 = (void (*)()) 0x804b578 <main>
(gdb) x/x $eip
0x804b578 <main>:       0x83e58955

这里,0x804b578是一个虚拟地址,经过段地址映射出的线性地址不变;我们主要来看线性地址到物理地址的转换。
该地址的前10位左移2位得到0x80,加上作为页目录(Page-directory)基址的cr3,得到对应的页目录项地址
0x079b6080(physical),查看该地址内容:
(gdb) xp/x 0x079b6080
0x79b6080:      0x04180067
这里得到的是页表(Page-table)的基址0x04180000.再取0x804b578的中间10位左移2位得到0x12c,相加得到
对应的页表项地址0x0418012c,查看该地址内容:
(gdb) xp/x 0x0418012c
0x418012c:      0x04115425
这里得到的是页的基址0x04115000,加上0x804b578的最后12位,得到最终的物理地址0x04115578
(gdb) xp/x 0x04115578
0x4115578:      0x83e58955
哈哈,内容一样吧!

内核地址空间的映射:
用户程序(通过系统调用或中断)进入内核空间时,是不需要切换cr3的,很神奇吧!我们来看为什么。
继续在刚才的环境,我们随便查看一个内核地址0xc0a39628
(gdb) x/x 0xc0a39628
0xc0a39628:     0xc1c910b4
内核地址到物理地址相差一个3G,我们直接减去0xc00000000就可以了
(gdb) xp/x 0xa39628
0xa39628:       0xc1c910b4
但是这个地址MMU是怎么得到的呢?仍然要通过cr3,我们来手动完成这个过程。
0xc0a39628的前10位左移2位得到0xc08(这里有个技巧,直接取前3位(16进制),第3位取4的整数倍即可),加上
cr3得到0x079b6c08,查看该地址内容:
(gdb) xp/x 0x079b6c08
0x79b6c08:      0x008001e3
注意,这里和刚才有所不同。刚才我们没有讨论最后12位的含义,这里必须说一下.当倒数第8位(Page size)的值
为1(即16进制的倒数第2位的值大于8 )时,以为着页面大小为4M,这时就从先前的两级映射退化为一级映射,页的基址
就是0x00800000,而地址的后22位(0x239628 )均为偏移,相加就得到物理地址0xa39628.
那么有多少个这样页面大小为4M的映射呢?
(gdb) xp/16x 0x079b6c00
0x79b6c00:      0x01000063      0x004001a3      0x008001e3      0x00c001e3
0x79b6c10:      0x01004023      0x01005063      0x01006003      0x01007063
0x79b6c20:      0x01008063      0x01009023      0x0100a023      0x0100b003
0x79b6c30:      0x0100c003      0x0100d003      0x0100e003      0x0100f003
原来只有3个(0x004001a3, 0x008001e3以及0x00c001e3),它们对应的地址空间为0xc0400000到0xc1000000,
即物理地址的4M--16M,共12M.还记得我们在《loader分析》中说到,kernel加载的基址就是0xc0400000,这不
是一个巧合。
为什么要用这样页面大小为4M的映射呢?
Intel <System Programming Guide> 3.7.3解释到,把操作系统或者可执行的内核放在大的页面中可以减少
TLB(Translation Lookaside Buffer, 用于缓存页目录项和页表项) misses, 从而可以提高系统整体性能。
而且,4M和4K的页项使用不同的TLB.
为什么用户程序进入内核空间时,不需要切换cr3?
应为所有的可以赋给cr3的基址在0xc00偏移上的内容几乎都是一致的。包括专门标志内核空间的IdlePTD(其值一般
为0x101e000).IdlePTD是cr3的第一次,在内核初始化化过程中一直是该值,直到启动第一个用户程序。在cpu_switch
会通过比较当前cr3是否等于该值来判断是否需要切换cr3.

为了更清楚地址映射过程,我们来看一下qemu中的一段代码:
    if (!(env->cr[0] & CR0_PG_MASK)) {//页表映射没有使能
            pte = addr;
            page_size = 4096;
        } else {
            /* page directory entry */
            //页目录项地址,a20_mask的值一般均为0xffffffff
            pde_addr = ((env->cr[3] & ~0xfff) + ((addr >> 20) & ~3)) & env->a20_mask;
            pde = ldl_phys(pde_addr);
            if (loglevel & CPU_LOG_QIUHAN) {
                fprintf(logfile, "pde_addr=0x%08x, pde=0x%08x\n", pde_addr, pde);
            }
            if (!(pde & PG_PRESENT_MASK))//页表不存在
                return -1;
            //页面大小是否为4M
            if ((pde & PG_PSE_MASK) && (env->cr[4] & CR4_PSE_MASK)) {
                pte = pde & ~0x003ff000; /* align to 4MB */
                page_size = 4096 * 1024;
            } else {
                /* page directory entry */
                pte_addr = ((pde & ~0xfff) + ((addr >> 10) & 0xffc)) & env->a20_mask;
                pte = ldl_phys(pte_addr);
                if (!(pte & PG_PRESENT_MASK))//页不存在
                    return -1;
                page_size = 4096;
            }
        }
        pte = pte & env->a20_mask;
    }

    page_offset = (addr & TARGET_PAGE_MASK) & (page_size - 1);
    paddr = (pte & TARGET_PAGE_MASK) + page_offset;
    if (loglevel & CPU_LOG_QIUHAN) {
        fprintf(logfile, "pte_addr=0x%08x, pte=0x%08x, page_offset=0x%08x, paddr=0x%08x\n",
                pte_addr, pte, page_offset, paddr);
    }
    return paddr;

读懂这段代码,就比较清楚了。下面是刚才寻址时打印出的调试信息,能看懂吧:
addr=0x0804b578, cr3=0x079b6000, a20_mask=0xffffffff
pde_addr=0x079b6080, pde=0x04180067
pte_addr=0x0418012c, pte=0x04115425, page_offset=0x00000000, paddr=0x04115000
paddr=0x04115578

addr=0xc0a39628, cr3=0x079b6000, a20_mask=0xffffffff
pde_addr=0x079b6c08, pde=0x008001e3
pte_addr=0xbfbf8b98, pte=0x008001e3, page_offset=0x00239000, paddr=0x00a39000
paddr=0x00a39628
这就是qemu的好处:如果你对硬件不熟悉,可以通过阅读它的代码或者打印调试信息来理解。我正是先读了这段代码才
能写下此文的。

这样我们就对freeBSD下x86地址映射有了一个比较清楚的认识,至于这种映射是如何建立起来的,我们下次再说吧。

[ 本帖最后由 qiuhanty 于 2007-8-15 15:05 编辑 ]
作者: 弥敦路九号    时间: 2007-08-15 16:35
不错不错,IA-32 Intel® Architecture Software Developer’s Manual Volume 3: System Programming Guide + Linux1.0 kernel source 也是很好的学习材料。
作者: 雨丝风片    时间: 2007-08-16 08:59
是个分析虚拟与物理关系的好方法!
作者: qiuhanty    时间: 2007-08-16 10:47
哈哈!俺终于也有一篇精华了!!多谢风雨版主!
有时间再来写写单机单虚拟机(vmware)调试内核的方法。
作者: 雨丝风片    时间: 2007-08-16 11:07
原帖由 qiuhanty 于 2007-8-16 10:47 发表
哈哈!俺终于也有一篇精华了!!多谢风雨版主!
有时间再来写写单机单虚拟机(vmware)调试内核的方法。


内核里面的内存管理部分,讲虚拟层面上的逻辑结构很容易,但虚拟和物理的对应却不容易讲清楚,
确实需要一些独特的方法来探察,你这篇文章在方法上的指导意义很大,希望能够继续把映射部分
分析、讲解得更透彻一些!
作者: yezizs    时间: 2007-08-17 17:02
不错不错,是个好老师!
作者: kindler    时间: 2007-08-21 08:47
标题: 回复 #1 qiuhanty 的帖子
想不到你也跑来这里混~~
作者: mik    时间: 2007-10-03 21:53
学习了,请问LZ,gdb 里有这个 cpu_dump 命令吗?
作者: qiuhanty    时间: 2007-10-14 17:21
原帖由 mik 于 2007-10-3 21:53 发表
学习了,请问LZ,gdb 里有这个 cpu_dump 命令吗?


没有,是我自己加的(还有查看物理地址的xp),呵呵

[ 本帖最后由 qiuhanty 于 2007-10-14 17:23 编辑 ]
作者: 雨丝风片    时间: 2007-10-16 12:51
原帖由 qiuhanty 于 2007-10-14 17:21 发表


没有,是我自己加的(还有查看物理地址的xp),呵呵


可以用qemu的monitor的xp和info registers代替一下。
作者: qiuhanty    时间: 2007-10-17 13:50
原帖由 雨丝风片 于 2007-10-16 12:51 发表


可以用qemu的monitor的xp和info registers代替一下。


果然如此!看来是我多此一举,又向风雨版主学到一招!
作者: hteh    时间: 2008-05-28 13:06
请问楼主info all-registers里有cr0……3寄存器吗?我的怎么没有显示呢?我用的是vmware+FC6。谢谢!
作者: qiuhanty    时间: 2008-07-28 21:25
原帖由 hteh 于 2008-5-28 13:06 发表
请问楼主info all-registers里有cr0……3寄存器吗?我的怎么没有显示呢?我用的是vmware+FC6。谢谢!


应该有吧!我现在手边没有环境,你可以Ctrl-Alt-2组合键后进入 qemu 控制台,输入help看看。
作者: 雨丝风片    时间: 2008-07-29 08:47
原帖由 qiuhanty 于 2008-7-28 21:25 发表


应该有吧!我现在手边没有环境,你可以Ctrl-Alt-2组合键后进入 qemu 控制台,输入help看看。


它用的是vmware,而且还不是qgdb。。。。
作者: mz198424    时间: 2008-07-29 23:37
标题: 回复 #1 qiuhanty 的帖子
顶!!!




欢迎光临 Chinaunix (http://bbs.chinaunix.net/) Powered by Discuz! X3.2