做了这么多铺垫,终于可以完整了解一下整体的 80386 的内存管理。

验证 GDT 步骤

终端 1

# 启动 ubuntu 16 虚拟机
# 参考 https://zhoukekestar.github.io/notes/2023/01/18/vm.html
$ qemu-system-i386 \
  -drive "file=./ubuntu16.img.qcow2,format=qcow2" \
  -m 2G \
  -nic user,hostfwd=tcp::10022-:22 \
  -monitor stdio

# 查看虚拟机 GDT
$ (qemu) info registers
...
GDT=     f67c4000 000000ff
...

终端 2

# 新开终端,连接虚拟机
$ ssh zkk@localhost -p10022

# 新建 dt.asm, 内容如下
$ cat dt.asm
section	.text
	global _start
_start:
	SGDT	[gdtcopy]
	SLDT	[ldtcopy]

section	.data
gdtcopy times 16 db 'x'
ldtcopy times 16 db 'x'

# 编译
$ nasm -f elf dt.asm
$ ld dt.o

# 调试
# 参考 https://zhoukekestar.github.io/notes/2023/01/30/debug.html
$ gdb ./a.out
# 开启汇编视图
(gdb) layout asm
# 断点
(gdb) starti
# step into
(gdb) si
│   0x8049000 <_start>      sgdtl  0x804a000
│  >0x8049007 <_start+7>    sldt   0x804a010
# 查看 GDT 内容
# 结果和虚拟机显示的 GDT=     f67c4000 000000ff 中相对应
(gdb) x/12x 0x804a000
0x804a000:      0x400000ff      0x7878f67c      0x78787878      0x78787878

验证 Segment Register

# 显示 CPU 寄存器
(qemu) info registers
...
GS =00e0 f67ce8c0 00000018 00409100 DPL=0 DS   [--A]
...
GDT=     f67c4000 000000ff
...

# 通过段转化,计算 DS 段寄存器的物理地址
# [BASE] + [INDEX] * 8 = 0xf67c4000 + (0xe0 >> 3) * 8 = 0xf67c40e0

# 查看相关的物理内存内容,可以看到相关的段描述已经被预加载至 Hidden Descriptor
# 参考 https://pdos.csail.mit.edu/6.828/2005/readings/i386/s05_01.htm
# 参考 https://techiefood4u.files.wordpress.com/2019/08/cao_unit_ii.pdf 查看 Hidden Part of Segment Selector
#   * Base Address + Limit + Access Information
#   * 0xe8c00018 0xf640917c 转成上述格式就是 f67ce8c0(Base) 00000018(limit) 00409100(Access)
(qemu) x/2x 0xf67c40e0
f67c40e0: 0xe8c00018 0xf640917c

验证 Paging

QEMU 验证

# 接上面的内存内容,可以获得如下信息
(qemu) x/2x 0xf67c40e0
f67c40e0: 0xe8c00018 0xf640917c

# 查看虚拟地址与物理地址的映射关系
# 所以,可以看到转换关系为:0xf67c40e0 => 0x367c40e0
#   注意,因为 tlb 的内容过长,所以在使用 iterm2 终端时
#   需要勾选一下 Preferences - Profiles - Terminal - Unlimited scrollback
(qemu) info tlb
...
00000000f6600000: 0000000036600000 -GPDA---W
00000000f6800000: 0000000036800000 -GPDA---W
...

# 查看物理地址,内容与虚拟地址相同
(qemu) xp/2x 0x367c40e0
00000000367c40e0: 0xe8c00018 0xf640917c

程序验证

# 计算分页逻辑
SEGMENT DESCTRPTOR = [BASE] + [INDEX] * 8 = 0xf67c4000 + (0xe0 >> 3) * 8 = 0xf67c40e0
CR3 = 0x30c65d00
DIR_ENTRY = ((0x30c65d00 >> 12) + (0xf67ce8c0 >> 22)) = 0x30c3e

# (qemu) xp/24x 0x30c1e
# 0000000000030c1e: 0x00000000 0x00000000 0x00000000 0x00000000
# 0000000000030c2e: 0x00000000 0x00000000 0x00000000 0x00000000
# 0000000000030c3e: 0xc6710000 0x6d64818d 0x706f5f61 0x00000073
# 0000000000030c4e: 0x00000000 0x00000000 0x00000000 0x00000000
# 0000000000030c5e: 0x00000000 0x00000000 0x00000000 0x00000000
# 0000000000030c6e: 0x00000000 0x00000000 0x00000000 0x00000000

# 因为 0xc6710000 最后一位是 0,表示不存在,所以随便找个末尾是 1 的数据简单看一下
# 0x706f5f61 >> 12 = 0x706f5
# (qemu) xp/24x 0x706f5
# 00000000000706f5: 0x04b1b0b8 0x82b3bc80 0x79b02121 0x9607dd93
# 0000000000070705: 0x217222c8 0x21b28019 0xb711bbf0 0x3ec10517
# 0000000000070715: 0x20ddc000 0x21b24c0c 0x1e216221 0xb01c2182
# 0000000000070725: 0x63602192 0x1b481b21 0x2132303b 0x80214240
# 0000000000070735: 0x39b0b346 0x1b2162b3 0x67810b0c 0x62336a02
# 0000000000070745: 0xdd801521 0xb4303010 0x060c446a 0x40b44040

获取 cr3 需要内核态权限才能获取

  • /proc/pid/maps , https://www.baeldung.com/linux/proc-id-maps
  • /proc/pid/pagemap
    • 此文件是一个虚拟文件,https://unix.stackexchange.com/questions/457196/why-is-cat-proc-pid-pagemap-not-giving-any-output
    • https://github.com/dwks/pagemap

参考

  • https://os2.unexploitable.systems/l/W3L1.pdf