[FreeBSD]x86地址映射实例

2024-02-26 17:48

本文主要是介绍[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地址映射实例的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/749654

相关文章

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

C++操作符重载实例(独立函数)

C++操作符重载实例,我们把坐标值CVector的加法进行重载,计算c3=c1+c2时,也就是计算x3=x1+x2,y3=y1+y2,今天我们以独立函数的方式重载操作符+(加号),以下是C++代码: c1802.cpp源代码: D:\YcjWork\CppTour>vim c1802.cpp #include <iostream>using namespace std;/*** 以独立函数

实例:如何统计当前主机的连接状态和连接数

统计当前主机的连接状态和连接数 在 Linux 中,可使用 ss 命令来查看主机的网络连接状态。以下是统计当前主机连接状态和连接主机数量的具体操作。 1. 统计当前主机的连接状态 使用 ss 命令结合 grep、cut、sort 和 uniq 命令来统计当前主机的 TCP 连接状态。 ss -nta | grep -v '^State' | cut -d " " -f 1 | sort |

两个月冲刺软考——访问位与修改位的题型(淘汰哪一页);内聚的类型;关于码制的知识点;地址映射的相关内容

1.访问位与修改位的题型(淘汰哪一页) 访问位:为1时表示在内存期间被访问过,为0时表示未被访问;修改位:为1时表示该页面自从被装入内存后被修改过,为0时表示未修改过。 置换页面时,最先置换访问位和修改位为00的,其次是01(没被访问但被修改过)的,之后是10(被访问了但没被修改过),最后是11。 2.内聚的类型 功能内聚:完成一个单一功能,各个部分协同工作,缺一不可。 顺序内聚:

《x86汇编语言:从实模式到保护模式》视频来了

《x86汇编语言:从实模式到保护模式》视频来了 很多朋友留言,说我的专栏《x86汇编语言:从实模式到保护模式》写得很详细,还有的朋友希望我能写得更细,最好是覆盖全书的所有章节。 毕竟我不是作者,只有作者的解读才是最权威的。 当初我学习这本书的时候,只能靠自己摸索,网上搜不到什么好资源。 如果你正在学这本书或者汇编语言,那你有福气了。 本书作者李忠老师,以此书为蓝本,录制了全套视频。 试

Java Websocket实例【服务端与客户端实现全双工通讯】

Java Websocket实例【服务端与客户端实现全双工通讯】 现很多网站为了实现即时通讯,所用的技术都是轮询(polling)。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发 出HTTP request,然后由服务器返回最新的数据给客服端的浏览器。这种传统的HTTP request 的模式带来很明显的缺点 – 浏 览器需要不断的向服务器发出请求,然而HTTP

NGINX轻松管理10万长连接 --- 基于2GB内存的CentOS 6.5 x86-64

转自:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=190176&id=4234854 一 前言 当管理大量连接时,特别是只有少量活跃连接,NGINX有比较好的CPU和RAM利用率,如今是多终端保持在线的时代,更能让NGINX发挥这个优点。本文做一个简单测试,NGINX在一个普通PC虚拟机上维护100k的HTTP

828华为云征文|华为云Flexus X实例docker部署rancher并构建k8s集群

828华为云征文|华为云Flexus X实例docker部署rancher并构建k8s集群 华为云最近正在举办828 B2B企业节,Flexus X实例的促销力度非常大,特别适合那些对算力性能有高要求的小伙伴。如果你有自建MySQL、Redis、Nginx等服务的需求,一定不要错过这个机会。赶紧去看看吧! 什么是华为云Flexus X实例 华为云Flexus X实例云服务是新一代开箱即用、体

LLVM入门2:如何基于自己的代码生成IR-LLVM IR code generation实例介绍

概述 本节将通过一个简单的例子来介绍如何生成llvm IR,以Kaleidoscope IR中的例子为例,我们基于LLVM接口构建一个简单的编译器,实现简单的语句解析并转化为LLVM IR,生成对应的LLVM IR部分,代码如下,文件名为toy.cpp,先给出代码,后面会详细介绍每一步分代码: #include "llvm/ADT/APFloat.h"#include "llvm/ADT/S

OpenStack离线Train版安装系列—11.5实例使用-Cinder存储服务组件

本系列文章包含从OpenStack离线源制作到完成OpenStack安装的全部过程。 在本系列教程中使用的OpenStack的安装版本为第20个版本Train(简称T版本),2020年5月13日,OpenStack社区发布了第21个版本Ussuri(简称U版本)。 OpenStack部署系列文章 OpenStack Victoria版 安装部署系列教程 OpenStack Ussuri版