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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 17:57:16