免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1139 | 回复: 0
打印 上一主题 下一主题

execve系统调用分析 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2007-02-15 12:29 |只看该作者 |倒序浏览
Linux提供了execl、execlp、execle、execv、execvp和execve等六个用以执行一个可执行文件的函数(统称为exec函数,其间的差异在于对命令行参数和环境变量参数的传递方式不同)。这些函数的第一个参数都是要被执行的程序的路径,第二个参数则向程序传递了命令行参数,第三个参数则向程序传递环境变量。以上函数的本质都是调用在arch/i386/kernel/process.c文件中实现的系统调用sys_execve来执行一个可执行文件,该函数代码如下:

asmlinkage int sys_execve(struct pt_regs regs)
{
    int error;
    char * filename;
    // 将可执行文件的名称装入到一个新分配的页面中
    filename = getname((char __user *) regs.ebx);
    error = PTR_ERR(filename);
    if (IS_ERR(filename))
        goto out;
    // 执行可执行文件
    error = do_execve(filename,
            (char __user * __user *) regs.ecx,
            (char __user * __user *) regs.edx,
            &regs);
    if (error == 0) {
        task_lock(current);
        current->ptrace &= ~PT_DTRACE;
        task_unlock(current);
        /* Make sure we don't return using sysenter.. */
        set_thread_flag(TIF_IRET);
    }
    putname(filename);
out:
    return error;
}
该系统调用所需要的参数pt_regs在include/asm-i386/ptrace.h文件中定义:
struct pt_regs {
    long ebx;
    long ecx;
    long edx;
    long esi;
    long edi;
    long ebp;
    long eax;
    int xds;
    int xes;
    long orig_eax;
    long eip;
    int xcs;
    long eflags;
    long esp;
    int xss;
};
该参数描述了在执行该系统调用时,用户态下的CPU寄存器在核心态的栈中的保存情况。通过这个参数,sys_execve可以获得保存在用户空间的以下信息:可执行文件路径的指针(regs.ebx中)、命令行参数的指针(regs.ecx中)和环境变量的指针(regs.edx中)。
真正执行程序的功能则是在fs/exec.c文件中的do_execve函数中实现的:
int do_execve(char * filename, char __user *__user *argv,
        char __user *__user *envp,     struct pt_regs * regs)
{
    struct linux_binprm *bprm;        // 保存和要执行的文件相关的数据
    struct file *file;
    int retval;
    int i;
    retval = -ENOMEM;
    bprm = kzalloc(sizeof(*bprm), GFP_KERNEL);
    if (!bprm)
        goto out_ret;
    // 打开要执行的文件,并检查其有效性(这里的检查并不完备)
    file = open_exec(filename);
    retval = PTR_ERR(file);
    if (IS_ERR(file))
        goto out_kfree;
    // 在多处理器系统中才执行,用以分配负载最低的CPU来执行新程序
    // 该函数在include/linux/sched.h文件中被定义如下:
    // #ifdef CONFIG_SMP
    // extern void sched_exec(void);
    // #else
    // #define sched_exec() {}
    // #endif
    sched_exec();
    // 填充linux_binprm结构
    bprm->p = PAGE_SIZE*MAX_ARG_PAGES-sizeof(void *);
    bprm->file = file;
    bprm->filename = filename;
    bprm->interp = filename;
    bprm->mm = mm_alloc();
    retval = -ENOMEM;
    if (!bprm->mm)
        goto out_file;
    // 检查当前进程是否在使用LDT,如果是则给新进程分配一个LDT
    retval = init_new_context(current, bprm->mm);
    if (retval  0)
        goto out_mm;
    // 继续填充linux_binprm结构
    bprm->argc = count(argv, bprm->p / sizeof(void *));
    if ((retval = bprm->argc)  0)
        goto out_mm;
    bprm->envc = count(envp, bprm->p / sizeof(void *));
    if ((retval = bprm->envc)  0)
        goto out_mm;
    retval = security_bprm_alloc(bprm);
    if (retval)
        goto out;
    // 检查文件是否可以被执行,填充linux_binprm结构中的e_uid和e_gid项
    // 使用可执行文件的前128个字节来填充linux_binprm结构中的buf项
    retval = prepare_binprm(bprm);
    if (retval  0)
        goto out;
    // 将文件名、环境变量和命令行参数拷贝到新分配的页面中
    retval = copy_strings_kernel(1, &bprm->filename, bprm);
    if (retval  0)
        goto out;
    bprm->exec = bprm->p;
    retval = copy_strings(bprm->envc, envp, bprm);
    if (retval  0)
        goto out;
    retval = copy_strings(bprm->argc, argv, bprm);
    if (retval  0)
        goto out;
    // 查询能够处理该可执行文件格式的处理函数,并调用相应的load_library方法进行处理
    retval = search_binary_handler(bprm,regs);
    if (retval >= 0) {
        free_arg_pages(bprm);
        // 执行成功
        security_bprm_free(bprm);
        acct_update_integrals(current);
        kfree(bprm);
        return retval;
    }
out:
    // 发生错误,返回inode,并释放资源
    for (i = 0 ; i  MAX_ARG_PAGES ; i++) {
        struct page * page = bprm->page;
        if (page)
            __free_page(page);
    }
    if (bprm->security)
        security_bprm_free(bprm);
out_mm:
    if (bprm->mm)
        mmdrop(bprm->mm);
out_file:
    if (bprm->file) {
        allow_write_access(bprm->file);
        fput(bprm->file);
    }
out_kfree:
    kfree(bprm);
out_ret:
    return retval;
}
该函数用到了一个类型为linux_binprm的结构体来保存要要执行的文件相关的信息,该结构体在include/linux/binfmts.h文件中定义:
struct linux_binprm{
    char buf[BINPRM_BUF_SIZE];    // 保存可执行文件的头128字节
    struct page *page[MAX_ARG_PAGES];
    struct mm_struct *mm;
    unsigned long p;    // 当前内存页最高地址
    int sh_bang;
    struct file * file;    // 要执行的文件
    int e_uid, e_gid;    // 要执行的进程的有效用户ID和有效组ID
    kernel_cap_t cap_inheritable, cap_permitted, cap_effective;
    void *security;
    int argc, envc;    // 命令行参数和环境变量数目
    char * filename;    // 要执行的文件的名称
    char * interp;        // 要执行的文件的真实名称,通常和filename相同
    unsigned interp_flags;
    unsigned interp_data;
    unsigned long loader, exec;
};
在该函数的最后,又调用了fs/exec.c文件中定义的search_binary_handler函数来查询能够处理相应可执行文件格式的处理器,并调用相应的load_library方法以启动进程。这里,用到了一个在include/linux/binfmts.h文件中定义的linux_binfmt结构体来保存处理相应格式的可执行文件的函数指针如下:
struct linux_binfmt {
    struct linux_binfmt * next;
    struct module *module;
    // 加载一个新的进程
    int (*load_binary)(struct linux_binprm *, struct pt_regs * regs);
    // 动态加载共享库
    int (*load_shlib)(struct file *);
    // 将当前进程的上下文保存在一个名为core的文件中
    int (*core_dump)(long signr, struct pt_regs * regs, struct file * file);
    unsigned long min_coredump;
};
Linux内核允许用户通过调用在include/linux/binfmt.h文件中定义的register_binfmt和unregister_binfmt函数来添加和删除linux_binfmt结构体链表中的元素,以支持用户特定的可执行文件类型。
在调用特定的load_binary函数加载一定格式的可执行文件后,程序将返回到sys_execve函数中继续执行。该函数在完成最后几步的清理工作后,将会结束处理并返回到用户态中,最后,系统将会将CPU分配给新加载的程序。


本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u/27624/showart_247673.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP