跟踪sys_execve的执行过程
郑德伦 原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000一、配置新的MenuOS环境在终端进入LinuxKernel目录,输入rm –rf menugit clone https://github.com/mengning/menu.gitmv test_exec.c tes
原创作品转载请注明出处 《Linux内核分析》MOOC课程
http://mooc.study.163.com/course/USTC-1000029000
一、 配置新的MenuOS环境
在终端进入LinuxKernel目录,输入
rm –rf menu
git clone https://github.com/mengning/menu.git
mv test_exec.c test.c
make 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_strcut
bprm->argc = count(argv, MAX_ARG_STRINGS);//计算参数个数,直到为NULL
retval = prepare_binprm(bprm);//把要加载文件的前128 读入bprm->buf
retval = copy_strings_kernel(1, &bprm->filename, bprm);//copy第一个参数filename
bprm->exec = bprm->p;//参数的起始地址
retval = copy_strings(bprm->envc, envp, bprm);//copy环境变量
retval = copy_strings(bprm->argc, argv, bprm);//copy可执行文件所带参数:argv[0]:hello argv[1]:hello
retval = 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系统调用的执行过程,具体的流程图如下:
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)