静态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
相关产品推荐
相关产品推荐

