# Page Table - Virtual address to physical address
> Author: 堇姬Naup
## 前言
把之前寫到一半的完善好了,丟上來


## qemu monitor
https://qemu-project.gitlab.io/qemu/system/monitor.html
可以用他來讀寫物理記憶體或是做很多事情
把 Qemu 加上 `-monitor unix:/tmp/qemu-monitor.sock,server,nowait`
後 load 這支
https://github.com/Naupjjin/slub-gdbext/tree/main
就可以通過 socket,來去操作 qemu monitor
`qemu_monitor 'monitor_command_string'`
```py
import socket
class QemuMonitorCommand(GenericCommand):
"""
Send a command to QEMU monitor via UNIX socket.
-monitor unix:/tmp/qemu-monitor.sock,server,nowait
by. naup
"""
_cmdline_ = "qemu_monitor"
_syntax_ = "qemu_monitor 'monitor_command_string'"
def __init__(self):
super(QemuMonitorCommand, self).__init__(self._cmdline_, gdb.COMMAND_OBSCURE)
self.monitor_path = "/tmp/qemu-monitor.sock"
def do_invoke(self, argv):
if not argv:
err("Usage: qemu_monitor 'command'")
return
cmd = " ".join(argv) + "\n"
try:
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
sock.connect(self.monitor_path)
sock.sendall(cmd.encode())
sock.settimeout(0.2)
response = b""
while True:
try:
data = sock.recv(4096)
if not data:
break
response += data
except socket.timeout:
break
print(response.decode(errors="ignore").strip())
except Exception as e:
err(f"Failed to connect to QEMU monitor: {e}")
QemuMonitorCommand()
```
實際用起來長這樣

## 原理
Virtual address 對應的就是 page table offset
通過他來查表可以查出 physical address
VA 實際上只會用到 48 bits,其餘是 Sign Extension
9 bits PML4I (Page-Map Level-4 Index)
9 bits PDPI (Page Directory Pointer Index)
9 bits PDI (Page Directory Index)
9 bits PTI (Page Table Index)
12 bits Physical Page Offset
`0xffffffff82b6a3a0`
Unused (bits 63–48) : 0xffff (65535)
PML4 Index : 0x1ff (511)
PDPT Index : 0x1fe (510)
PD Index : 0x15 (21)
PT Index : 0x16a (362)
Offset : 0x3a0 (928)
參考該docs的2.5(p.3069)
[Intel® 64 and IA-32 Architectures Software Developer’s Manual Combined Volumes: 1, 2A, 2B, 2C, 2D, 3A, 3B, 3C, 3D, and 4](https://www.intel.com.tw/content/www/tw/zh/content-details/782158/intel-64-and-ia-32-architectures-software-developer-s-manual-combined-volumes-1-2a-2b-2c-2d-3a-3b-3c-3d-and-4.html)

CR3 register contains the base address of the page-directorypointer table. With 4-level paging and 5-level paging, the CR3 register contains the base address of the PML4 table and PML5 table
0 PWT(Page-level Write-Through)
1 PCD(Page-level Cache Disable)
11:2 保留
63:12 Page table base address(PML4 physical address)
可以從 cr3 來找到 Page table 最上層
只要把最低 12 bits mask 掉,就是 page table physical address,把他轉為 virtual address 來看比較方便


每一個 Table 為 1 Page (4 KB)
每一個 Entry 為 8 Bytes
因此每一個 Table 含有 512 個 Entries (分別就對應 9 bits, 2^9 = 512)
高四個 page table 找到想找的 page 後
通過最後低位 12 bits
找到該 page 具體位置
就是個先映射到實際 page,在從該 page 找實際位置的轉換方式
cr3 找到第一個頁表的位置後,就可以通過 VA 算出的第一個值,找 offset,以此類推
`512**4*0x1000-1 = 0xffffffffffff` 就是 48 bits
## gdb (2MB page)
目標將
```gdb
gef> p &slab_caches
$3 = (struct list_head *) 0xffffffff82b6a3a0 <slab_caches>
gef> v2p 0xffffffff82b6a3a0
Virt: 0xffffffff82b6a3a0 -> Phys: 0x2b6a3a0
```
手動轉成 physical address
這邊不知道他是 2MB 還是 4KB,不過前三張找法是一樣的
Unused (bits 63–48) : 0xffff (65535)
PML4 Index : 0x1ff (511)
PDPT Index : 0x1fe (510)
PD Index : 0x15 (21)
### cr3 to PML4

先找 cr3

`0x0000000004866000 & ~0xfff = 0x0000000004866000`
這樣就找到 PML4 base (PA) = 0x0000000004866000
VA = 0xffff888004866000

### PML4 to PDP
PML4,Linux Kernel 的 PGD (Page Global Directory)
PML4 entry 長這樣,整張 PML4 頁表共有 512 個

高 9 bits 轉 offset,找到對應 PML4E
`0xffff888004866000 + 511 * 8 = 0xffff888004866ff8`

根據 PML4E 去掉低 12 bits,跟高 1 bits
`0x0000000002a33067 & ~0xfff = 0x2a33000`

這樣就找到 PDP base (PA) = 0x2a33000
VA = 0xffff888002a33000
### PDP 2 PD
PDP, Linux Kernel 的 PUD (Page Upper Directory)
接下來從 PDP 找出 PD base
PDPE 長這樣

算 offset 找到
`0xffff888002a33000 + 510 * 8 = 0xffff888002a33ff0`

根據 PDPE 去掉低 12 bits,跟高 1 bits
`0x0000000002a34063 & ~0xfff = 0x2a34000`

這樣就找到 PD base (PA) = 0x2a34000
VA = 0xffff888002a34000
### PD find any
PD,Linux Kernel 的 PMD (Page Middle Directory)
PDE 長這樣



算 offset 找到
`0xffff888002a34000 + 0x15 * 8 = 0xffff888002a340a8`

找到的 PDE 長這樣
`0x8000000002a000e3`
`0b1000000000000000000000000000000000000010101000000000000011100011`
這邊跟先前的不太一樣
可以關注到這邊的 PDE 有兩種形式
在 2 MB / 4 KB Page Size 的情況結構不同
會根據 PS 來標示是哪種
1 的話是 2 MB
4KB 找到的是 PT base
在 2MB 狀況下沒有 PT,是直接找到該 physical page frame
所以轉換一下
`0x0000000002a000e3 & ~0xfffff = 0x2a00000`
PS: 這邊附上 PT 的 PTE

### page frame + offset
所以前面的重新計算一下
2MB:
Unused (bits 63–48) : 0xffff (65535)
PML4 Index : 0x1ff (511)
PDPT Index : 0x1fe (510)
PD Index : 0x15 (21)
Offset (2MB page) : 0x16a3a0 (1483680)
physical page frame = 0x2a00000
加上 offset `0x16a3a0`
`0x2a00000 + 0x16a3a0 = 0x2b6a3a0`
這樣就找到 physical adress 啦

## gdb (4KB page)
這次把 kaslr 打開來,並搭配 monitor
要找 0xffffffff8538adc0
```gdb
gef> x/6xg 0xffffffff8538adc0
0xffffffff8538adc0: 0x89482775c38948ff 0x8948000011e2e8ef
0xffffffff8538add0: 0x8948fffff7bae8ef 0x8548000012c2e8ef
0xffffffff8538ade0: 0x5d5bfb230f0374db 0x0fc031ccccccccc3
gef> v2p 0xffffffff8538adc0
Virt: 0xffffffff8538adc0 -> Phys: 0x1478adc0
```

### cr3 to PML4

先找 cr3

`0x2068000 & ~0xfff = 0x2068000`
這樣就找到 PML4 base (PA): 0x2068000
### PML4 to PDP
PML4,Linux Kernel 的 PGD (Page Global Directory)
PML4 entry 長這樣,整張 PML4 頁表共有 512 個

高 9 bits 轉 offset,找到對應 PML4E
`0x2068000 + 511 * 8 = 0x2068ff8`

根據 PML4E 去掉低 12 bits,跟高 1 bits
`0x0000000015033067 & ~0xfff = 0x15033000`
這樣就找到 PDP base (PA) = 0x15033000
### PDP 2 PD
PDP, Linux Kernel 的 PUD (Page Upper Directory)
接下來從 PDP 找出 PD base
PDPE 長這樣

算 offset 找到
`0x15033000 + 510 * 8 = 0x15033ff0`

根據 PDPE 去掉低 12 bits,跟高 1 bits
`0x0000000015034063 & ~0xfff = 0x15034000`
這樣就找到 PD base (PA) = 0x15034000
### PD find PT
PD,Linux Kernel 的 PMD (Page Middle Directory)
PDE 長這樣



算 offset 找到
`0x15034000 + 0x29 * 8 = 0x15034148`

0x000000000240e063 = 0b10010000001110000001100011
第 7 個是 0,是 4 KB,有 PT
`0x000000000240e063 & ~0xfff = 0x240e000`
這樣就找到 PT base (PA) = 0x240e000
### PT to physical page frame
PT
PTE 長這樣

`0x240e000 + 0x18a * 8 = 0x240ec50`

`0x000000001478a021 & ~0xfff = 0x1478a000`
這樣就找到那張 page 了
page base 是 0x1478a000
### page frame + offset
`0x1478a000 + 0xdc0 = 0x1478adc0`
長的一模一樣,找到了

## after all
以上就是如何轉換