原创作品转载请注明出处 《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系统调用的执行过程,具体的流程图如下:
这里写图片描述

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐