本文主要是介绍理解x86_64 Paging(Page Map Level 4),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
原文
一个方便编译内核的小工具
page
在 x86_64 上,页面是 0x1000 字节的内存片,按 0x1000 字节对齐。
这就是为什么,如果查看 /proc/<pid>/maps
,会发现所有地址范围的地址开始和结束都将以 0x000 结尾,因为 x86_64 上内存映射的最小大小是页面大小(0x1000字节)并且页面需要“页面对齐”(最后 12 位必须为零)。
尽管许多虚拟页面可能引用相同的物理页面,但“虚拟页面”将被 MMU 解析为单个“物理页面”(也称为“页面框架”)。
虚拟地址中有什么
正如人们可能猜到的那样,PML4 有四级分页结构,这些分页结构称为“页表”。页表是一个页大小的内存区域,包含 512 个 8 字节页表条目。页表的每个条目将引用下一级页表或虚拟地址解析到的最终物理地址。
页表中用于地址转换的条目基于内存访问的虚拟地址。每层有 512 个条目,这意味着每层都使用 9 位虚拟地址来索引相应的页表。
假设我们有一个这样的地址:
0x7ffe1c9c9000
该地址的最后 12 位表示物理页内的偏移量:
0x7ffe1c9c9000 & 0xfff = 0x0
这意味着一旦我们确定了该虚拟地址解析到的页面的物理地址,我们将在结果中添加零以获得最终的物理地址。
最后 12 位(同样也是最终页内的偏移量)之后,虚拟地址由页表中的索引组成。如前所述,每个级别的分页使用 9 位虚拟地址,因此分页结构的最低级别(页表)由地址的接下来 9 位进行索引(通过使用 & 0x1ff 进行位掩码)移动后的值)。对于以下级别,我们只需每次右移另外九位,并再次屏蔽掉较低的九位作为我们的索引。对上面的地址执行此操作可以得到以下指示:
Level 1, Page Table (PT):
Index = (0x7ffe1c9c9000 >> 12) & 0x1ff = 0x1c9Level 2, Page Middle Directory (PMD):
Index = (0x7ffe1c9c9000 >> 21) & 0x1ff = 0x0e4Level 3, Page Upper Directory (PUD):
Index = (0x7ffe1c9c9000 >> 30) & 0x1ff = 0x1f8Level 4, Page Global Directory (PGD):
Index = (0x7ffe1c9c9000 >> 39) & 0x1ff = 0x0ff
页表基址寄存器
现在我们知道如何索引页表并模糊地了解它们包含的内容,它们实际上在哪里???
CPU 的每个线程都有一个名为 cr3 的页表基址寄存器。
cr3 保存分页结构最高层的物理地址,也称为页面全局目录(PGD)。
从gdb中,调试内核时,可以像这样读取 cr3 的内容:
gef➤ p/x $cr3
$1 = 0x10d664000
cr3 寄存器除了 PGD 地址之外还可以保存一些附加信息,具体取决于所使用的处理器功能,因此从 cr3 获取 PGD 物理地址的更通用方法寄存器的作用是屏蔽掉其内容的低 12 位,如下所示:
gef➤ p/x $cr3 & ~0xfff
$2 = 0x10d664000
页表条目 page table entries
让我们看看从 gdb 中的 cr3 获得的物理地址是什么。
QEMU 监视器向 gdb 公开的 monitor xp/...
命令让我们打印出虚拟机的物理内存,执行 monitor xp/512gx ...
将打印 PGD 的全部内容(所有 512 个条目) cr3 引用:
gef➤ monitor xp/512gx 0x10d664000
...
000000010d664f50: 0x0000000123fca067 0x0000000123fc9067
000000010d664f60: 0x0000000123fc8067 0x0000000123fc7067
000000010d664f70: 0x0000000123fc6067 0x0000000123fc5067
000000010d664f80: 0x0000000123fc4067 0x0000000123fc3067
000000010d664f90: 0x0000000123fc2067 0x000000000b550067
000000010d664fa0: 0x000000000b550067 0x000000000b550067
000000010d664fb0: 0x000000000b550067 0x0000000123fc1067
000000010d664fc0: 0x0000000000000000 0x0000000000000000
000000010d664fd0: 0x0000000000000000 0x0000000000000000
000000010d664fe0: 0x0000000123eab067 0x0000000000000000
000000010d664ff0: 0x000000000b54c067 0x0000000008c33067
这会产生大量输出,其中大部分为零,所以我在这里只包含输出的尾部。
此输出可能对您来说还没有多大意义,但我们可以观察数据中的一些模式,例如,许多 8 字节条目以 0x67 结尾。
解码 PGD 条目
从上面的 PGD 输出中,我们以 0x000000010d664f50
处值为 0x0000000123fca067
的 PGD 条目为例,了解如何解码条目。
让我们用该条目值的二进制表示来完成此操作:
gef➤ p/t 0x0000000123fca067
$6 = 100100011111111001010000001100111
这是一个小图,显示条目中的每一位代表什么:
~ PGD Entry ~ Present ──────┐Read/Write ──────┐|User/Supervisor ──────┐||Page Write Through ──────┐|||Page Cache Disabled ──────┐ ||||Accessed ──────┐| ||||Ignored ──────┐|| ||||Reserved ──────┐||| ||||
┌─ NX ┌─ Reserved Ignored ──┬──┐ |||| ||||
|┌───────────┐ |┌──────────────────────────────────────────────┐ | | |||| ||||
|| Ignored | || PUD Physical Address | | | |||| ||||
|| | || | | | |||| ||||
0000 0000 0000 0000 0000 0000 0000 0001 0010 0011 1111 1100 1010 0000 0110 011156 48 40 32 24 16 8 0
以下是每个标签的含义:
- NX(不可执行)——如果设置了该位,则该 PGD 条目的后代内存映射将无法执行
- Reserved – 这些值必须为零
- PUD Physical Address - 与该 PGD 条目关联的 PUD 的物理地址
- Accessed – 如果此条目或其后代引用了任何页面,则该位将由 MMU 置位,并可由操作系统清除
- Page Cache Disabled (PCD)-页面缓存禁用 (PCD) 该 PGD 条目的后代页面不应进入 CPU 的缓存层次结构,有时也称为“不可缓存”(UC) 位
- Page Write Through (WT) – 页直写 (WT) – 对此 PGD 条目的后代页的写入应立即写入 RAM,而不是在最终更新 RAM 之前缓冲写入 CPU 高速缓存
- User/Supervisor – 如果该位未设置,则除非处于管理员模式,否则无法访问该 PGD 的后代页面
- Read/Write – 如果该位未设置,则无法写入该 PGD 的后代页
- Present – 如果该位未设置,则处理器将不会使用该条目进行地址转换,并且其他位均不适用
我们真正关心的位是Present
位,代表分页结构下一级的物理地址、PUD 物理地址位和权限位:NX、用户/管理员和读/写。
- Present 位非常重要,因为如果没有设置它,条目的其余部分将被忽略
- PUD 物理地址通过告诉我们下一级分页结构的物理地址所在位置,让我们能够继续分页
- 权限位全部应用于 PGD 条目的后代页面,并确定如何访问这些页面
其余部分对于我们的目的来说并不那么重要:
- Accessed位:如果该条目用于转换内存访问,则设置访问位,这对于页面遍历并不重要
- Page Cache Disabled and Page Write Through:页面缓存禁用和页面写入不用于正常内存映射,并且不会影响页面转换或权限,因此让我们忽略它们
因此,解码此条目,我们了解到:
PUD 存在:
gef➤ p/x 0x0000000123fca067 & 0b0001
$18 = 0x1
PUD 及以下的映射可能是可写的:
gef➤ p/x 0x0000000123fca067 & 0b0010
$19 = 0x2
用户可以访问 PUD 及以下内容中的映射:
gef➤ p/x 0x0000000123fca067 & 0b0100
$20 = 0x4
PUD 的物理地址(位 (51:12] )是 0x123fca000 :
gef➤ p/x 0x0000000123fca067 & ~((1ull<<12)-1) & ((1ull<<51)-1)
$21 = 0x123fca000
PUD 中及以下的映射可能是可执行的:
gef➤ p/x 0x0000000123fca067 & (1ull<<63)
$22 = 0x0
解码所有级别的条目
现在我们已经了解了如何解码 PGD 条目,解码其余级别并没有太大不同,至少在常见情况下是这样。
对于所有这些图,“X”表示该位可以是 0 或 1,否则,如果将某个位设置为特定值,则该值要么是架构所需要的,要么是图中所示的特定编码所需要的。
PGD
~ PGD Entry ~ Present ──────┐Read/Write ──────┐|User/Supervisor ──────┐||Page Write Through ──────┐|||Page Cache Disabled ──────┐ ||||Accessed ──────┐| ||||Ignored ──────┐|| ||||Reserved ──────┐||| ||||
┌─ NX ┌─ Reserved Ignored ──┬──┐ |||| ||||
|┌───────────┐ |┌──────────────────────────────────────────────┐ | | |||| ||||
|| Ignored | || PUD Physical Address | | | |||| ||||
|| | || | | | |||| ||||
XXXX XXXX XXXX 0XXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX 0XXX XXXX56 48 40 32 24 16 8 0
这个我们已经看到了,我在上一节中详细描述了它,但这里没有填写特定的 PGD 条目。
PUD
~ PUD Entry, Page Size unset ~ Present ──────┐Read/Write ──────┐|User/Supervisor ──────┐||Page Write Through ──────┐|||Page Cache Disabled ──────┐ ||||Accessed ──────┐| ||||Ignored ──────┐|| ||||Page Size ──────┐||| ||||
┌─ NX ┌─ Reserved Ignored ──┬──┐ |||| ||||
|┌───────────┐ |┌──────────────────────────────────────────────┐ | | |||| ||||
|| Ignored | || PMD Physical Address | | | |||| ||||
|| | || | | | |||| ||||
XXXX XXXX XXXX 0XXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX 0XXX XXXX56 48 40 32 24 16 8 0
正如您所看到的,上图的 PUD 与 PGD 非常相似,唯一的区别是引入了页面大小
位。
设置的页面大小位极大地改变了我们解释 PUD 条目的方式。
对于此图,我们假设它未设置,这是最常见的情况。
PMD
~ PMD Entry, Page Size unset ~ Present ──────┐Read/Write ──────┐|User/Supervisor ──────┐||Page Write Through ──────┐|||Page Cache Disabled ──────┐ ||||Accessed ──────┐| ||||Ignored ──────┐|| ||||Page Size ──────┐||| ||||
┌─ NX ┌─ Reserved Ignored ──┬──┐ |||| ||||
|┌───────────┐ |┌──────────────────────────────────────────────┐ | | |||| ||||
|| Ignored | || PT Physical Address | | | |||| ||||
|| | || | | | |||| ||||
XXXX XXXX XXXX 0XXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX 0XXX XXXX56 48 40 32 24 16 8 0
同样,PMD 图与上图非常相似,并且与 PUD 条目一样,我们现在忽略页面大小位。
PT
~ PT Entry ~ Present ──────┐Read/Write ──────┐|User/Supervisor ──────┐||Page Write Through ──────┐|||Page Cache Disabled ──────┐ ||||Accessed ──────┐| ||||
┌─── NX Dirty ──────┐|| ||||
|┌───┬─ Memory Protection Key Page Attribute Table ──────┐||| ||||
|| |┌──────┬─── Ignored Global ─────┐ |||| ||||
|| || | ┌─── Reserved Ignored ───┬─┐| |||| ||||
|| || | |┌──────────────────────────────────────────────┐ | || |||| ||||
|| || | || 4KB Page Physical Address | | || |||| ||||
|| || | || | | || |||| ||||
XXXX XXXX XXXX 0XXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX56 48 40 32 24 16 8 0
在页表条目中,事情变得更加有趣,有一些以前级别中没有的新字段/属性。
这些新字段/属性是:
- Memory Protection Key (MPK or PK): 内存保护密钥(MPK 或 PK),这是一个 x86_64 扩展,允许将 4 位密钥分配给页面,该页面可用于配置具有该密钥的所有页面的内存权限
- Global:全局,这与 TLB(转换后备缓冲区,用于虚拟到物理地址转换的 MMU 缓存)如何缓存页面的转换有关,设置该位意味着在上下文切换时该页面不会从 TLB 中刷新,这通常在内核页面上启用,以减少 TLB 未命中
- Page Attribute Table (PAT): 页面属性表(PAT),如果设置,MMU 在确定页面的“内存类型”是否为“内存类型”时应参考页面属性表 MSR。该页面是否是“不可缓存”、“直写”或其他几种内存类型之一
- Dirty:该位与访问位类似,如果该页被写入,则由 MMU 置位,并且必须由操作系统重置
这些实际上都不会影响地址转换本身,但内存保护密钥的配置可能意味着该条目引用的页面的预期内存访问权限可能比该条目本身编码的权限更严格。
与前面的级别不同,由于这是最后一个级别,因此该条目保存与我们正在转换的虚拟地址关联的页面的最终物理地址。一旦您应用位掩码来获取物理地址字节并添加原始虚拟地址的最后 12 位(页面内的偏移量),您就拥有了物理地址!
希望这看起来没那么糟糕,页面遍历的一般情况只需几个步骤:
- 通过移位地址和应用位掩码将虚拟地址转换为索引和页面偏移量
- 读取 cr3 得到PGD的物理地址
- 对于每个级别,直到最后一个
- 使用从虚拟地址计算出的索引来了解要使用页表中的哪个条目
- 对条目应用位掩码以获得下一级的物理地址
- 在最后一层,再次从虚拟地址中找到索引对应的条目
- 应用位掩码来获取与虚拟地址关联的页面的物理地址
- 添加页面内从虚拟地址到页面物理地址的偏移量
- 完毕
大页
如前所述,前面的 PUD 和 PMD 图表适用于未设置页面大小位时的常见情况。
那么,什么时候设置呢?
当它被设置时,它有效地告诉MMU,打包它,我们就完成了,不要继续页面遍历,当前条目保存我们正在寻找的页面的物理地址。
但除此之外,页面大小位设置的条目中页面的物理地址不是普通的 4KB(0x1000 字节)页面,它是一个“大页面”,有两个变体:1GB 大页和 2MB 大页。
当 PUD 条目设置了页面大小位时,它指的是 1GB 大页面,而当 PMD 设置了页面大小位时,它指的是 2MB 大页面。
但 1GB 和 2MB 的数字从何而来呢?
每个页表级别最多可容纳 512 个条目,这意味着一个 PT 最多可以引用 512 个页面和 512 * 4KB = 2MB
。因此,PMD 级别的大页面实际上意味着该条目引用与完整 PT 大小相同的页面。
将其扩展到 PUD 级别,我们只需再次乘以 512 即可获得具有完整 PT 的完整 PMD 的大小: 512 * 512 * 4KB = 1GB
。
Huge Page PUD
~ PUD Entry, Page Size set ~ Present ─────┐Read/Write ─────┐|User/Supervisor ─────┐||Page Write Through ─────┐|||Page Cache Disabled ─────┐ ||||Accessed ─────┐| ||||Dirty ─────┐|| ||||
┌─── NX Page Size ─────┐||| ||||
|┌───┬─── Memory Protection Key Global ─────┐ |||| ||||
|| |┌──────┬─── Ignored Ignored ───┬─┐| |||| ||||
|| || | ┌─── Reserved Page Attribute Table ───┐ | || |||| ||||
|| || | |┌────────────────────────┐┌───────────────────┐| | || |||| ||||
|| || | || 1GB Page Physical Addr || Reserved || | || |||| ||||
|| || | || || || | || |||| ||||
XXXX XXXX XXXX 0XXX XXXX XXXX XXXX XXXX XX00 0000 0000 0000 000X XXXX 1XXX XXXX56 48 40 32 24 16 8 0
当设置页面大小位时,请注意 PUD 条目看起来更像 PT 条目而不是普通的 PUD 条目,这是有道理的,因为它也引用页面而不是页表。
不过,与 PT 条目有一些区别:
- 页大小位是 PT 上页属性表 (PAT) 位的位置,因此 PAT 位被重新定位到位 12。
- 1GB 大页的物理地址需要在物理内存中具有 1GB 对齐,这就是为什么存在新的保留位以及为什么位 12 能够重新用作 PAT 位的原因。
总的来说,这里并没有太多新内容,处理大页面时唯一的其他区别是,需要对地址应用不同的位掩码来获取页面物理地址的位,同时计算 1GB 对齐意味着页内虚拟地址的物理地址我们需要使用基于 1GB 对齐而不是 4KB 对齐的掩码。
Huge Page PMD
~ PMD Entry, Page Size set ~ Present ─────┐Read/Write ─────┐|User/Supervisor ─────┐||Page Write Through ─────┐|||Page Cache Disabled ─────┐ ||||Accessed ─────┐| ||||Dirty ─────┐|| ||||
┌─── NX Page Size ─────┐||| ||||
|┌───┬─── Memory Protection Key Global ─────┐ |||| ||||
|| |┌──────┬─── Ignored Ignored ───┬─┐| |||| ||||
|| || | ┌─── Reserved Page Attribute Table ─────┐ | || |||| ||||
|| || | |┌───────────────────────────────────┐┌────────┐| | || |||| ||||
|| || | || 2MB Page Physical Address ||Reserved|| | || |||| ||||
|| || | || || || | || |||| ||||
XXXX XXXX XXXX 0XXX XXXX XXXX XXXX XXXX XXXX XXXX XXX0 0000 000X XXXX 1XXX XXXX56 48 40 32 24 16 8 0
这与设置了页面大小位的 PUD 条目非常相似,唯一改变的是,由于该级别 2MB 页面的对齐较小,因此设置的保留位较少。
2MB 对齐意味着大页内的偏移量应使用基于 2MB 对齐的掩码来计算。
手动执行页表遍历
所以最后一节有很多图表,在本节中让我们看看如何在 gdb 中实际手动执行页面遍历。
连接启动的 vm 和 gdb 后,我首先将选择一个地址来进行页面遍历,作为示例,我将在内核中运行时使用当前堆栈指针:
gef➤ p/x $rsp
$42 = 0xffffffff88c07da8
现在我们有了要遍历的地址,我们还可以从 cr3 获取 PGD 的物理地址:
gef➤ p/x $cr3 & ~0xfff
$43 = 0x10d664000
我将使用这个小 python 函数从虚拟地址中提取页表偏移量:
def get_virt_indicies(addr):pageshift = 12addr = addr >> pageshiftpt, pmd, pud, pgd = (((addr >> (i*9)) & 0x1ff) for i in range(4))return pgd, pud, pmd, pt
输出如下
In [2]: get_virt_indicies(0xffffffff88c07da8)
Out[2]: (511, 510, 70, 7)
PGD
我们根据虚拟地址获得的 PGD 索引为 511,将 511 乘以 8 将让我们获得 PGD 中的字节偏移量,虚拟地址的 PGD 条目起始位置为:
gef➤ p/x 511*8
$44 = 0xff8
将该偏移量添加到 PGD 的物理地址即可得到 PGD 条目的物理地址:
gef➤ p/x 0x10d664000+0xff8
$45 = 0x10d664ff8
读取该地址的物理内存可以获取 PGD 条目本身:
gef➤ monitor xp/gx 0x10d664ff8
000000010d664ff8: 0x0000000008c33067
看起来条目的最后三位(当前、用户和可写)已设置,最高位 (NX) 未设置,这意味着到目前为止与此虚拟地址关联的页面的权限没有任何限制。
屏蔽位 [12, 51) 给出了 PUD 的物理地址:
gef➤ p/x 0x0000000008c33067 & ~((1<<12)-1) & ((1<<51) - 1)
$46 = 0x8c33000
PUD
我们根据虚拟地址获得的 PUD 索引为 510,将 510 乘以 8 将让我们获得 PUD 中的字节偏移量,该偏移量是虚拟地址的 PUD 条目的起始位置:
gef➤ p/x 510*8
$47 = 0xff0
将该偏移量添加到 PUD 的物理地址即可得到 PUD 条目的物理地址:
gef➤ p/x 0x8c33000+0xff0
$48 = 0x8c33ff0
读取该地址的物理内存可以获取 PUD 条目本身:
gef➤ monitor xp/gx 0x8c33ff0
0000000008c33ff0: 0x0000000008c34063
在这个级别,我们需要开始关注大小位(位 7),因为如果它是 1GB 页面,我们将在此停止页面遍历。
gef➤ p/x 0x0000000008c34063 & (1<<7)
$49 = 0x0
似乎该条目未设置,因此我们将继续页面行走。
另请注意,PUD 条目以 0x3 结尾,而不是像上一级别那样以 0x7 结尾,底部两位(存在、可写)仍然设置,但第三位(用户位)现在未设置。这意味着用户模式访问属于该 PUD 条目的页面将由于访问权限检查失败而导致页面错误。
NX 位仍未设置,因此属于该 PUD 的页面仍然可执行。
屏蔽位 [12, 51) 给出了 PMD 的物理地址:
gef➤ p/x 0x0000000008c34063 & ~((1ull<<12)-1) & ((1ull<<51)-1)
$50 = 0x8c34000
PMD
我们根据虚拟地址获得的 PMD 索引为 70,将 70 乘以 8 将让我们获得虚拟地址的 PMD 条目开始处的 PMD 字节偏移量:
gef➤ p/x 70*8
$51 = 0x230
将该偏移量添加到 PMD 的物理地址即可得到 PMD 条目的物理地址:
gef➤ p/x 0x8c34000+0x230
$52 = 0x8c34230
读取该地址的物理内存可以获取 PMD 条目本身:
gef➤ monitor xp/gx 0x8c34230
0000000008c34230: 0x8000000008c001e3
同样,在这个级别我们需要注意大小位,因为如果它是 2MB 页面,我们将在这里停止页面遍历。
gef➤ p/x 0x8000000008c001e3 & (1<<7)
$53 = 0x80
看起来我们的虚拟地址指的是一个2MB的大页面!因此该 PMD 条目中的物理地址就是该大页的物理地址。
另外,查看权限位,看起来该页面仍然存在并且可写,并且用户位仍然未设置,因此该页面只能从管理员模式(ring-0)访问。
与之前的级别不同,最高位 NX 位被设置:
gef➤ p/x 0x8000000008c001e3 & (1ull<<63)
$54 = 0x8000000000000000
所以这个Huge Page并不是可执行内存。
在位 [21:51) 上应用位掩码可以得到大页的物理地址:
gef➤ p/x 0x8000000008c001e3 & ~((1ull<<21)-1) & ((1ull<<51)-1)
$56 = 0x8c00000
现在我们需要对基于 2MB 页面对齐的虚拟地址应用掩码,以获取大页的偏移量。
2MB 相当于 1<<21 ,因此应用 (1ull<<21)-1 位掩码将为我们提供偏移量:
gef➤ p/x 0xffffffff88c07da8 & ((1ull<<21)-1)
$57 = 0x7da8
现在将此偏移量添加到 2MB 大页的基地址中将得到与我们开始的虚拟地址关联的物理地址:
gef➤ p/x 0x8c00000 + 0x7da8
$58 = 0x8c07da8
看起来虚拟地址: 0xffffffff88c07da8 的物理地址为: 0x8c07da8 !
检查
有几种方法可以测试我们的页面行走是否正确,一个简单的检查是将内存转储到虚拟地址和物理地址并进行比较,如果它们看起来相同,我们可能是对的:
Physical
gef➤ monitor xp/10gx 0x8c07da8
0000000008c07da8: 0xffffffff810effb6 0xffffffff88c07dc0
0000000008c07db8: 0xffffffff810f3685 0xffffffff88c07de0
0000000008c07dc8: 0xffffffff8737dce3 0xffffffff88c3ea80
0000000008c07dd8: 0xdffffc0000000000 0xffffffff88c07e98
0000000008c07de8: 0xffffffff8138ab1e 0x0000000000000000
Virtual
gef➤ x/10gx 0xffffffff88c07da8
0xffffffff88c07da8: 0xffffffff810effb6 0xffffffff88c07dc0
0xffffffff88c07db8: 0xffffffff810f3685 0xffffffff88c07de0
0xffffffff88c07dc8: 0xffffffff8737dce3 0xffffffff88c3ea80
0xffffffff88c07dd8: 0xdffffc0000000000 0xffffffff88c07e98
0xffffffff88c07de8: 0xffffffff8138ab1e 0x0000000000000000
另一种检查方法是使用 QEMU 监视器向 gdb 公开的 monitor gva2gpa (guest virtual address to guest physical address)命令:
gef➤ monitor gva2gpa 0xffffffff88c07da8
gpa: 0x8c07da8
假设 QEMU 正确地进行地址转换(可能是一个合理的假设),那么看起来我们已经双重确认了我们的页面遍历是成功的!
这篇关于理解x86_64 Paging(Page Map Level 4)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!