You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

静态ELF的可执行映射段为何未在/proc/<pid>/pagemap中标记为文件映射?

问题描述

我写了一个GDB工具,能通过/proc/<pid>/pagemap查看虚拟地址的PFN(页帧号),还能通过/proc/kpagecount和/proc/kpageflags检查该PFN的标志位。

我编写并编译了一个小型64位ELF文件,它包含2个PT_LOAD程序头:

> readelf -l ./bin/a.out
Elf file type is EXEC (Executable file)
Entry point 0x401000
There are 2 program headers, starting at offset 64

Program Headers:
  Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
  LOAD           0x000000 0x0000000000400000 0x0000000000400000 0x0000b0 0x0000b0 R   0x1000
  LOAD           0x001000 0x0000000000401000 0x0000000000401000 0x00005c 0x00005c R E 0x1000

 Section to Segment mapping:
  Segment Sections...
   00
   01     .text

在GDB中查看内存映射:

(gdb)  info proc mappings
process 8495
Mapped address spaces:

          Start Addr           End Addr       Size     Offset  Perms  objfile
            0x400000           0x401000     0x1000        0x0  r--p   <path-to-bin>
            0x401000           0x402000     0x1000     0x1000  r-xp   <path-to-bin>
      0x7ffff7ff9000     0x7ffff7ffd000     0x4000        0x0  r--p   [vvar]
      0x7ffff7ffd000     0x7ffff7fff000     0x2000        0x0  r-xp   [vdso]
      0x7ffffffdd000     0x7ffffffff000    0x22000        0x0  rw-p   [stack]
  0xffffffffff600000 0xffffffffff601000     0x1000        0x0  --xp   [vsyscall]

检查0x400000的PFN时,/proc/<pid>/pagemap和/proc/kpageflags返回的标志如下:

(gdb)  pfn 0x400000
BigEndian? 0
VADDR: 0x400000, PageSize: 4096, EntrySize: 8
Reading /proc/8495/pagemap at 0x2000
[0]0xba [1]0x5 [2]0x22 [3]0x0 [4]0x0 [5]0x0 [6]0x80 [7]0xa1
file-mapped: true, exclusively-mapped: true, soft-dirty: true, swapped: false, uffd-wp write-protected: false
Result: 0xa1800000002205ba
PFN:    0x2205ba

(gef)  kpage 0x2205ba
PFN: 0x2205ba
/proc/kpagecount: 1
Flags:
  REFERENCED
  UPTODATE
  LRU
  MMAP

该页被标记为文件映射,符合预期,因为它是可执行文件的ELF文件头。但对包含.text节的段执行相同操作时,结果却不同:

(gdb)  pfn 0x401000
BigEndian? 0
VADDR: 0x401000, PageSize: 4096, EntrySize: 8
Reading /proc/8495/pagemap at 0x2008
[0]0xbe [1]0xb0 [2]0x27 [3]0x0 [4]0x0 [5]0x0 [6]0x80 [7]0x81
file-mapped: false, exclusively-mapped: true, soft-dirty: true, swapped: false, uffd-wp write-protected: false
Result: 0x818000000027b0be
PFN:    0x27b0be

(gdb)  kpage 0x27b0be
PFN: 0x27b0be
/proc/kpagecount: 1
Flags:
  REFERENCED
  UPTODATE
  DIRTY
  LRU
  MMAP
  ANON
  SWAPBACKED

我原本预期作为ELF文件一部分的已映射.text段会像ELF文件头一样被标记为文件映射,为何实际并非如此?


问题分析

这是因为GDB调试时设置的断点修改了.text段所在的页,触发了Linux的写时复制(Copy-On-Write)机制,导致原本的文件映射页被替换成了匿名页。

具体过程:

  • 当你在GDB中调试程序时,哪怕是默认的启动断点,GDB都会将目标地址的指令替换成int3(0xCC),这相当于对.text段所在的只读页发起了写操作。
  • 由于原页面是从可执行文件映射来的只读页,内核会为该页创建一个新的匿名副本,把修改后的内容写入这个副本,同时更新进程的页表指向这个新的匿名页。
  • 这个新的匿名页不再与原文件关联,因此/proc/kpageflags会标记它为ANON(匿名)、DIRTY(已修改),不再保留原文件映射的属性。

而ELF文件头所在的0x400000页没有被修改,所以仍然保持文件映射的属性。如果去掉GDB的所有断点,重新运行程序(不触发任何对.text页的修改),你会看到.text段的页依然是文件映射类型。

内容的提问来源于stack exchange,提问作者Tramadol

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 14:14:56