Linux用户态API:无需解析/proc/self/maps获取PIE程序地址空间大小
解决方案
1. 修正dl_iterate_phdr的遍历逻辑
你可能只读取了单个程序头的信息,而非累加同一共享对象下所有PT_LOAD段的大小。动态加载器加载的每个共享对象(主程序或依赖库)包含多个程序头,其中PT_LOAD类型的段是实际映射到内存的部分,需累加这些段的p_memsz(内存占用大小)或p_filesz(文件占用大小)才能得到完整的空间大小。
C示例代码
#include <link.h> #include <stdio.h> static int callback(struct dl_phdr_info *info, size_t size, void *data) { unsigned long total_memsz = 0; unsigned long total_filesz = 0; printf("Shared object: %s\n", info->dlpi_name); printf("Base address: 0x%lx\n", info->dlpi_addr); for (int i = 0; i < info->dlpi_phnum; i++) { const ElfW(Phdr) *phdr = &info->dlpi_phdr[i]; if (phdr->p_type == PT_LOAD) { total_memsz += phdr->p_memsz; total_filesz += phdr->p_filesz; printf(" PT_LOAD %d: memsz=0x%lx, filesz=0x%lx\n", i, phdr->p_memsz, phdr->p_filesz); } } printf("Total memsz: 0x%lx, total filesz: 0x%lx\n\n", total_memsz, total_filesz); return 0; } int main() { dl_iterate_phdr(callback, NULL); return 0; }
Rust绑定示例(依赖libc crate)
use libc::{c_void, dl_iterate_phdr, ElfW, dl_phdr_info}; use std::ffi::CStr; unsafe extern "C" fn callback(info: *mut dl_phdr_info, _size: usize, _data: *mut c_void) -> i32 { let info = &*info; let name = if !info.dlpi_name.is_null() { CStr::from_ptr(info.dlpi_name).to_str().unwrap_or("unknown") } else { "(main program)" }; println!("Shared object: {}", name); println!("Base address: 0x{:x}", info.dlpi_addr); let mut total_memsz = 0; let mut total_filesz = 0; for i in 0..info.dlpi_phnum { let phdr = &(*info.dlpi_phdr.offset(i as isize)); if phdr.p_type == libc::PT_LOAD { total_memsz += phdr.p_memsz; total_filesz += phdr.p_filesz; println!(" PT_LOAD {}: memsz=0x{:x}, filesz=0x{:x}", i, phdr.p_memsz, phdr.p_filesz); } } println!("Total memsz: 0x{:x}, total filesz: 0x{:x}\n", total_memsz, total_filesz); 0 } fn main() { unsafe { dl_iterate_phdr(Some(callback), std::ptr::null_mut()); } }
2. 直接解析ELF文件获取完整段信息
如果你的程序采用了按需加载(仅加载运行必需的段),dl_iterate_phdr仅会返回已加载的段数据。若要获取整个可执行文件的完整空间大小,可直接打开文件并解析ELF头。
C示例代码
#include <fcntl.h> #include <sys/mman.h> #include <sys/stat.h> #include <unistd.h> #include <stdio.h> #include <elf.h> void parse_elf(const char *path) { int fd = open(path, O_RDONLY); if (fd == -1) { perror("open"); return; } struct stat st; if (fstat(fd, &st) == -1) { perror("fstat"); close(fd); return; } void *map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); if (map == MAP_FAILED) { perror("mmap"); close(fd); return; } ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)map; ElfW(Phdr) *phdr = (ElfW(Phdr) *)((char *)map + ehdr->e_phoff); unsigned long total_filesz = 0; unsigned long total_memsz = 0; printf("ELF file: %s\n", path); for (int i = 0; i < ehdr->e_phnum; i++) { if (phdr[i].p_type == PT_LOAD) { total_filesz += phdr[i].p_filesz; total_memsz += phdr[i].p_memsz; printf(" PT_LOAD %d: filesz=0x%lx, memsz=0x%lx\n", i, phdr[i].p_filesz, phdr[i].p_memsz); } } printf("Total filesz: 0x%lx, total memsz: 0x%lx\n\n", total_filesz, total_memsz); munmap(map, st.st_size); close(fd); } int main() { parse_elf("/proc/self/exe"); // 解析当前程序 return 0; }
Rust示例代码
use libc::{O_RDONLY, PROT_READ, MAP_PRIVATE, MAP_FAILED}; use std::ffi::CString; use std::fs::File; use std::os::unix::io::AsRawFd; use std::mem; const EI_NIDENT: usize = 16; const PT_LOAD: u32 = 1; #[repr(C)] struct ElfEhdr { e_ident: [u8; EI_NIDENT], e_type: u16, e_machine: u16, e_version: u32, e_entry: u64, e_phoff: u64, e_shoff: u64, e_flags: u32, e_ehsize: u16, e_phentsize: u16, e_phnum: u16, e_shentsize: u16, e_shnum: u16, e_shstrndx: u16, } #[repr(C)] struct ElfPhdr { p_type: u32, p_flags: u32, p_offset: u64, p_vaddr: u64, p_paddr: u64, p_filesz: u64, p_memsz: u64, p_align: u64, } fn parse_elf(path: &str) { let path_c = CString::new(path).unwrap(); let file = File::open(path).unwrap(); let fd = file.as_raw_fd(); let mut st = unsafe { mem::zeroed::<libc::stat>() }; unsafe { libc::fstat(fd, &mut st) }; let map = unsafe { libc::mmap(std::ptr::null_mut(), st.st_size as usize, PROT_READ, MAP_PRIVATE, fd, 0) }; if map == MAP_FAILED { eprintln!("mmap failed"); return; } let ehdr = unsafe { &*(map as *const ElfEhdr) }; let phdr_ptr = (map as usize + ehdr.e_phoff as usize) as *const ElfPhdr; let mut total_filesz = 0u64; let mut total_memsz = 0u64; println!("ELF file: {}", path); for i in 0..ehdr.e_phnum { let phdr = unsafe { &*phdr_ptr.offset(i as isize) }; if phdr.p_type == PT_LOAD { total_filesz += phdr.p_filesz; total_memsz += phdr.p_memsz; println!(" PT_LOAD {}: filesz=0x{:x}, memsz=0x{:x}", i, phdr.p_filesz, phdr.p_memsz); } } println!("Total filesz: 0x{:x}, total memsz: 0x{:x}\n", total_filesz, total_memsz); unsafe { libc::munmap(map, st.st_size as usize) }; } fn main() { parse_elf("/proc/self/exe"); }
问题原因说明
dl_iterate_phdr返回的是动态加载器(ld.so)已加载到内存的段信息:
- 对于1.7G的大型程序,动态加载器会采用按需加载策略,仅加载运行必需的段(如代码段、核心数据段),非必要段(如大体积只读数据、调试信息)仅在被访问时才会加载,因此返回的数值仅对应已加载部分。
- 若你之前只读取了单个程序头的数值,而非累加同一共享对象下的所有
PT_LOAD段,也会得到极小的数值。
内容的提问来源于stack exchange,提问作者Igor B
相关产品推荐
相关产品推荐

