本文共 3606 字,大约阅读时间需要 12 分钟。
原创作品转载请注明出处 《Linux内核分析》MOOC课程
一、 配置新的MenuOS环境 在终端进入LinuxKernel目录,输入rm –rf menugit clone https://github.com/mengning/menu.gitmv test_exec.c test.cmake rootfs
完成新的MenuOS的配置。在此版本的MenuOS中,加入了exec功能。也就是执行execlp库函数,来创建一个新的进程hello。
二、 追踪sys_execve的执行过程 打开终端,进入LinuxKernel目录输入 qemu –kernel linux-3.18.6/arch/x86/boot/bzImage –initrd rootfs.img –S –s 然后打开另一个终端输入gdb(gdb)file linux-3.18.6/vmlinux(gdb)target remote:1234(gdb)c(gdb)b sys_execve(gdb)b load_elf_binary(gdb)b start_thread(gdb)b do_execve(gdb)c
我们开始跟踪执行过程,在qemu中输入exec,首先进入断点是sys_execve,在在中端处理程序中,调用了do_execve(),其中getname从用户空间获取filename(也就是hello)的路径,到内核中。
进入do_execve函数体内发现,实际工作是完成argv envp赋值,然后调用do_execve_common 我们进入do_execve_common函数体内 我们看下do_execve_common的源代码,do_execve_common完成了一个linux_binprm的结构体 bprm的初始化工作:retval = bprm_mm_init(bprm);初始化了mm_strcutbprm->argc = count(argv, MAX_ARG_STRINGS);//计算参数个数,直到为NULLretval = prepare_binprm(bprm);//把要加载文件的前128 读入bprm->bufretval = copy_strings_kernel(1, &bprm->filename, bprm);//copy第一个参数filenamebprm->exec = bprm->p;//参数的起始地址retval = copy_strings(bprm->envc, envp, bprm);//copy环境变量retval = copy_strings(bprm->argc, argv, bprm);//copy可执行文件所带参数:argv[0]:hello argv[1]:helloretval = exec_binprm(bprm);//执行bprm
int do_execve_common(struct filename *filename, struct user_arg_ptr argv, struct user_arg_ptr envp){ struct linux_binprm *bprm; struct file *file; struct files_struct *displaced; int retval; /**省略中间一部分***/ bprm = kzalloc(sizeof(*bprm), GFP_KERNEL); if (!bprm) goto out_files; retval = prepare_bprm_creds(bprm); if (retval) goto out_free; check_unsafe_exec(bprm); current->in_execve = 1; file = do_open_exec(filename); retval = PTR_ERR(file); if (IS_ERR(file)) goto out_unmark; sched_exec(); bprm->file = file; bprm->filename = bprm->interp = filename->name; retval = bprm_mm_init(bprm); if (retval) goto out_unmark; bprm->argc = count(argv, MAX_ARG_STRINGS); if ((retval = bprm->argc) < 0) goto out; bprm->envc = count(envp, MAX_ARG_STRINGS); if ((retval = bprm->envc) < 0) goto out; 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; retval = exec_binprm(bprm); if (retval < 0) goto out; /***省略中间一部分代码****/ return retval;}
然后我们跟踪到exec_binprm,查看函数代码,这段代码将父进程的pid保存,获取新的pid,然后执行search_binary_hander(bprm),用来遍历format链表,找到合适的处理hello的方式。
static int exec_binprm(struct linux_binprm *bprm){ pid_t old_pid, old_vpid; int ret; /* Need to fetch pid before load_binary changes it */ old_pid = current->pid; rcu_read_lock(); old_vpid = task_pid_nr_ns(current, task_active_pid_ns(current->parent)); rcu_read_unlock(); ret = search_binary_handler(bprm); if (ret >= 0) { audit_bprm(bprm); trace_sched_process_exec(current, old_pid, bprm); ptrace_event(PTRACE_EVENT_EXEC, old_vpid); proc_exec_connector(current); } return ret;}
我们继续continue 调试,达到断点search_bintary_handler
在这里面有个结构体linux_binfmt *fmt;static struct linux_binfmt elf_format = { .module = THIS_MODULE, .load_binary = load_elf_binary, .load_shlib = load_elf_library, .core_dump = elf_core_dump, .min_coredump = ELF_EXEC_PAGESIZE,};
里面的.load_binary字段初始化为load_elf_binary由此可见load_binary应该是一个函数指针。
我们继续跟踪到达load_elf_binary,这个函数完成的二进制文件的装载和启动,其中在函数的末尾有start_thread(regs, elf_entry, bprm->p);是二进制的启动代码,我们继续跟踪到达start_thread start_thread函数完成pt_reg结构的转换,从当前进程转换到新进程,完成进程的切换,从而开始执行execve的进程。 三、 总结 通过上面的调试过程,我们已经比较清楚的了解到一个sys_execve系统调用的执行过程,具体的流程图如下: