Skip to content

VFS Exec 与 Binfmt

1. 模块架构

1.1 功能概述

Exec 是 Linux 执行新程序的核心机制。Binfmt (Binary Format) 是可执行文件格式的注册框架,支持多种可执行格式如 ELF、script 等。

1.2 关键源文件

文件作用
fs/exec.c程序加载核心
fs/binfmt_script.c脚本解释器
fs/binfmt_elf.cELF 格式
fs/binfmt_aout.ca.out 格式
include/linux/binfmts.hbinfmt 接口

2. 核心数据结构

2.1 struct linux_binprm

c
// include/linux/binfmts.h:100
struct linux_binprm {
    struct file *file;                 // 可执行文件
    const char *filename;              // 文件名
    const char *interp;                // 解释器
    struct page **page;                // 参数页面
    unsigned long p;                   // 栈指针
    unsigned long shdr0;               // section header 0
    unsigned long header0;             // ELF header
    unsigned long interp_flags;
    unsigned long vma_pages;           // VMA 页面数
    unsigned long min_copied;          // 复制的最小长度
    unsigned long cred_prepared:1;     // 凭证已准备
    unsigned long executable_stack:1;   // 可执行栈
    unsigned long entry:2;
    struct mm_struct *mm;              // 内存描述符
    struct cred *cred;                 // 凭证
    int argc, envc;                   // 参数数量
    const char *const *argv;           // 参数向量
    const char *const *envp;           // 环境变量
    struct rlimit saved_rlim[ RLIM_NLIMITS ];
    unsigned long rlim_stack_max;
    // ...
};

2.2 struct linux_binfmt

c
// include/linux/binfmts.h:80
struct linux_binfmt {
    struct list_head lh;
    struct module *module;
    int (*load_binary)(struct linux_binprm *);
    int (*load_shlib)(struct file *);
    int (*core_dump)(struct coredump_params *cprm);
    unsigned long min_coredump;
    int hasvdso;
};

3. Exec 流程

3.1 sys_execve()

c
// fs/exec.c:2000
int do_execve(struct user_arg_ptr *argv, struct user_arg_ptr *envp)
{
    return do_execveat(AT_FDCWD, "/proc/self/exe", argv, envp, 0);
}

3.2 do_execveat()

c
// fs/exec.c:1900
int do_execveat(int dfd, const char *filename,
                struct user_arg_ptr argv,
                struct user_arg_ptr envp,
                int flags)
{
    struct linux_binprm *bprm;

    // 分配 binprm
    bprm = kzalloc(sizeof(*bprm), GFP_KERNEL);
    if (!bprm)
        return -ENOMEM;

    // 打开可执行文件
    ret = open_exec(filename);

    // 准备参数
    ret = prepare_binprm(bprm);

    // 检查权限
    ret = security_bprm_check(bprm);

    // 执行
    ret = exec_binprm(bprm);

    // 释放
    free_bprm(bprm);

    return ret;
}

3.3 exec_binprm()

c
// fs/exec.c:1850
static int exec_binprm(struct linux_binprm *bprm)
{
    struct file *file = bprm->file;
    struct linux_binfmt *fmt;

    // 搜索并调用二进制格式的 load_binary
    list_for_each_entry(fmt, &formats, lh) {
        if (!try_module_get(fmt->module))
            continue;

        // 调用格式特定的加载函数
        bprm->recursion_depth++;
        ret = fmt->load_binary(bprm);
        bprm->recursion_depth--;

        module_put(fmt->module);
        if (ret >= 0)
            return ret;
    }

    return -ENOEXEC;
}

4. ELF 加载

4.1 load_elf_binary()

c
// fs/binfmt_elf.c:600
static int load_elf_binary(struct linux_binprm *bprm)
{
    struct file *file = bprm->file;
    struct elf_phdr *elf_ppnt, *elf_phdata;
    struct elf_hdr *elf_ex = (struct elf_hdr *)bprm->buf;
    unsigned long elf_bss, elf_brk;
    int retval;

    // 检查 ELF magic
    if (memcmp(elf_ex->e_ident, ELFMAG, SELFMAG) != 0)
        return -ENOEXEC;

    // 读取程序头
    elf_phdata = load_phdrs(file, elf_ex);
    if (IS_ERR(elf_phdata))
        return PTR_ERR(elf_phdata);

    // 查找解释器
    for (elf_ppnt = elf_phdata; elf_ppnt++ < ...; ) {
        if (elf_ppnt->p_type == PT_INTERP) {
            // 加载解释器
            load_elf_interp(...);
        }
    }

    // 设置 brk
    elf_brk = ELF_PAGESTART(elf_bss + ELF_MIN_ALIGN - 1);

    // 创建 VMA
    for (elf_ppnt = elf_phdata; elf_ppnt++ < ...; ) {
        if (elf_ppnt->p_type != PT_LOAD)
            continue;

        // 映射段
        elf_map(file, elf_ppnt, ...);
    }

    // 设置入口点
    current->start_code = elf_entry;

    // 准备栈
    retval = setup_arg_pages(bprm, ...);

    // 复制环境和参数
    retval = copy_strings(bprm->envc, bprm->envp, bprm);
    retval = copy_strings(bprm->argc, bprm->argv, bprm);

    // 清除当前地址空间
    flush_old_exec(bprm);

    // 设置新的地址空间
    install_exec_creds(bprm);

    // 启动新程序
    start_thread(regs, elf_entry, bprm->p);

    return 0;
}

4.2 elf_map()

c
// fs/binfmt_elf.c:400
static unsigned long elf_map(struct file *file, struct elf_phdr *elf_ppnt,
                            int executable, unsigned long *interp_map_addr)
{
    unsigned long page, offset;
    unsigned long map_addr;
    unsigned long prot = PROT_READ | PROT_EXEC;

    // 计算偏移和地址
    offset = elf_ppnt->p_offset;
    map_addr = *interp_map_addr;

    // mmap 映射
    map_addr = do_mmap(file, map_addr, elf_ppnt->p_filesz, prot,
                       MAP_FIXED | MAP_PRIVATE, offset);

    return map_addr;
}

5. 脚本解释器

5.1 load_script()

c
// fs/binfmt_script.c:100
static int load_script(struct linux_binprm *bprm)
{
    const char *i_arg, *i_name;
    char *cp;
    struct file *file;
    int retval;

    // 检查 #! 标志
    if ((bprm->buf[0] != '#') || (bprm->buf[1] != '!'))
        return -ENOEXEC;

    // 解析解释器路径和参数
    bprm->buf[255] = '\0';
    cp = strchr(bprm->buf, '\n');

    // 提取解释器
    i_name = cp + 1;
    for (cp = i_name; *cp && (*cp == ' '); cp++)
        ;

    // 查找参数
    i_arg = strchr(cp, ' ');
    if (i_arg) {
        *i_arg = '\0';
        i_arg++;
    }

    // 打开解释器
    file = open_exec(i_name);
    if (IS_ERR(file))
        return PTR_ERR(file);

    // 替换 bprm 中的文件名
    bprm->file = file;
    bprm->filename = i_name;

    // 递归调用 exec_binprm
    return search_binary_handler(bprm);
}

6. 参数页面管理

6.1 prepare_binprm()

c
// fs/exec.c:200
int prepare_binprm(struct linux_binprm *bprm)
{
    struct file *file = bprm->file;
    umode_t mode;
    struct inode *inode = file_inode(file);
    int retval;

    // 检查权限
    mode = inode->i_mode;
    if (IS_SUID(mode)) {
        bprm->per_clear |= PER_CLEAR_ON_SETID;
        bprm->cred->euid = inode->i_uid;
    }

    if (IS_SGID(mode)) {
        bprm->per_clear |= PER_CLEAR_ON_SETID;
        bprm->cred->egid = inode->i_gid;
    }

    // 读取文件头
    memset(bprm->buf, 0, sizeof(bprm->buf));
    retval = kernel_read(file, 0, bprm->buf, sizeof(bprm->buf));

    return retval;
}

6.2 setup_arg_pages()

c
// fs/exec.c:400
int setup_arg_pages(struct linux_binprm *bprm, unsigned long page_size)
{
    unsigned long stack_base;
    struct vm_area_struct *vma;
    struct mm_struct *mm = current->mm;

    // 计算栈基址
    stack_base = STACK_TOP;
    stack_base -= (page_size - 1);
    stack_base &= ~(page_size - 1);

    // 映射参数页面
    for (i = 0; i < MAX_ARG_PAGES; i++) {
        struct page *page = bprm->page[i];
        if (page) {
            vma = vm_area_alloc(mm);
            vm_map_range(vma, stack_base, page, PAGE_SIZE);
            stack_base += PAGE_SIZE;
        }
    }

    // 设置栈指针
    bprm->p = stack_base;

    return 0;
}

7. 凭证管理

7.1 install_exec_creds()

c
// fs/exec.c:500
void install_exec_creds(struct linux_binprm *bprm)
{
    // 安装新的凭证
    commit_creds(bprm->cred);

    // 清除 setuid 标志
    current->pdeath_signal = 0;

    // 设置执行域
    call_int_hook(bprm, BINPRM_HOOK);
}

7.2 compute_creds()

c
// fs/exec.c:300
void compute_creds(struct linux_binprm *bprm)
{
    const struct cred *old = current_cred();

    // 复制凭证
    bprm->cred = prepare_creds();
    if (!bprm->cred)
        return;

    // 应用 SUID/SGID
    if (bprm->file->f_path.mnt->mnt_flags & MNT_NOSUID)
        return;

    // 检查 inode 的 suid/sgid
    if (mode & S_ISUID) {
        bprm->cred->euid = inode->i_uid;
        bprm->per_clear |= PER_CLEAR_ON_SETID;
    }

    if (mode & S_ISGID) {
        bprm->cred->egid = inode->i_gid;
        bprm->per_clear |= PER_CLEAR_ON_SETID;
    }
}

8. 线程启动

8.1 start_thread()

c
// fs/binfmt_elf.c:200
void start_thread(struct pt_regs *regs, unsigned long entry,
                  unsigned long stack)
{
    // 设置指令指针
    regs->ip = entry;
    // 设置栈指针
    regs->sp = stack;
    // 清除标志
    regs->flags = 0;
    // 设置 CS/SS
    regs->cs = __USER_CS;
    regs->ss = __USER_DS;
}

9. Core Dump

9.1 do_coredump()

c
// fs/coredump.c:200
int do_coredump(long signr, struct pt_regs *regs)
{
    struct linux_binfmt *fmt;
    struct core_state *core_state;
    struct core_dump_params cprm = {
        .regs = regs,
        .signr = signr,
        .limit = rlimit(RLIMIT_CORE),
    };

    // 检查 limit
    if (cprm.limit == 0)
        return 0;

    // 获取二进制格式
    fmt = current->binfmt;
    if (!fmt || !fmt->core_dump)
        return 0;

    // 创建 core 文件
    cprm.file = filp_open("core", O_CREAT | O_RDWR, 0600);

    // 调用格式特定的 core_dump
    fmt->core_dump(&cprm);

    return 0;
}

10. Binfmt 注册

10.1 register_binfmt()

c
// fs/exec.c:1800
int register_binfmt(struct linux_binfmt *fmt)
{
    if (!fmt)
        return -EINVAL;
    fmt->module = NULL;
    list_add(&fmt->lh, &formats);
    return 0;
}

10.2 unregister_binfmt()

c
// fs/exec.c:1820
void unregister_binfmt(struct linux_binfmt *fmt)
{
    list_del(&fmt->lh);
}

11. Exec 执行流程图

用户: execve("/bin/ls", ["ls", "-l"], envp)

内核路径:
1. do_execveat()
   |
2. open_exec()
   |  打开可执行文件
   |
3. prepare_binprm()
   |  检查权限,读取文件头
   |
4. search_binary_handler()
   |  遍历注册的 binfmt
   |
5. ELF 格式:
   load_elf_binary()
   |
   +---> 检查 magic
   +---> 读取 program headers
   +---> 映射 segments (elf_map)
   +---> 设置入口点
   +---> setup_arg_pages()
   +---> flush_old_exec()
   +---> start_thread()

6. 脚本格式:
   load_script()
   |
   +---> 检查 #!
   +---> 提取解释器路径
   +---> open_exec(解释器)
   +---> search_binary_handler()
         (递归)

12. 常用 binfmt 格式

格式文件说明
ELFbinfmt_elf.cExecutable and Linkable Format
Scriptbinfmt_script.c#! 解释器脚本
a.outbinfmt_aout.c早期 Unix 格式
flatbinfmt_flat.c嵌入式格式
FDPICbinfmt_elf_fdpic.cFDPIC ELF

基于 VitePress 构建