本文主要是介绍[FreeBSD]x86地址映射实例,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
http://www.chinaunix.net 作者:qiuhanty
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地址映射有了一个比较清楚的认识,至于这种映射是如何建立起来的,我们下次再说吧。
这篇关于[FreeBSD]x86地址映射实例的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!