execve系统调用分析 | |
| |
来源: ChinaUnix博客 日期: 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, ®s); 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分配给新加载的程序。 |
execve系统调用分析
最新推荐文章于 2023-06-27 15:37:33 发布