分享

Linux执行二进制可执行文件流程

 wythe 2023-05-06 发布于广东

execve族系统调用用于运行一个新的进程,并且替换当前进程的内存映像(代码,数据,栈)为新进程的内存映像。这个系统调用会在指定的进程空间内加载一个新的程序,同时将参数和环境变量传递给新程序。这个调用函数可以用于实现重载进程代码、切换用户权限、启动新的进程等功能。

execvp和execv是execve族的两个变体,它们允许你在新进程中运行可执行程序文件。 execvp使用PATH环境变量执行程序,而execv需要指定完整路径。

execve族的调用可以让用户免于开发自己的shell或者cli程序,用现有的程序交互式地对系统进行操作,甚至可以在shell中启动其他进程。

下面我们来跟踪一下Linux内核是如何实现该族函数的

内核中的定义

SYSCALL_DEFINE3(execve,
		const char __user *, filename,
		const char __user *const __user *, argv,
		const char __user *const __user *, envp)
{
	return do_execve(getname(filename), argv, envp);
}

SYSCALL_DEFINE5(execveat,
		int, fd, const char __user *, filename,
		const char __user *const __user *, argv,
		const char __user *const __user *, envp,
		int, flags)
{
	int lookup_flags = (flags & AT_EMPTY_PATH) ? LOOKUP_EMPTY : 0;

	return do_execveat(fd,
			   getname_flags(filename, lookup_flags, NULL),
			   argv, envp, flags);
}

#ifdef CONFIG_COMPAT
COMPAT_SYSCALL_DEFINE3(execve, const char __user *, filename,
	const compat_uptr_t __user *, argv,
	const compat_uptr_t __user *, envp)
{
	return compat_do_execve(getname(filename), argv, envp);
}

COMPAT_SYSCALL_DEFINE5(execveat, int, fd,
		       const char __user *, filename,
		       const compat_uptr_t __user *, argv,
		       const compat_uptr_t __user *, envp,
		       int,  flags)
{
	int lookup_flags = (flags & AT_EMPTY_PATH) ? LOOKUP_EMPTY : 0;

	return compat_do_execveat(fd,
				  getname_flags(filename, lookup_flags, NULL),
				  argv, envp, flags);
}
#endif

通过不同flag设置不同参数,但是它们最后会调用到一个函数中do_execveat_common

/*
 * sys_execve() executes a new program.
 */
static int do_execveat_common(int fd, struct filename *filename,
			      struct user_arg_ptr argv,
			      struct user_arg_ptr envp,
			      int flags)
{
	char *pathbuf = NULL;
	struct linux_binprm *bprm;
	struct file *file;
	struct files_struct *displaced;
	int retval;

	if (IS_ERR(filename))
		return PTR_ERR(filename);

	/*
	 * We move the actual failure in case of RLIMIT_NPROC excess from
	 * set*uid() to execve() because too many poorly written programs
	 * don't check setuid() return code.  Here we additionally recheck
	 * whether NPROC limit is still exceeded.
	 */
	if ((current->flags & PF_NPROC_EXCEEDED) &&
	    atomic_read(¤t_user()->processes) > rlimit(RLIMIT_NPROC)) {
		retval = -EAGAIN;
		goto out_ret;
	}

	/* We're below the limit (still or again), so we don't want to make
	 * further execve() calls fail. */
	current->flags &= ~PF_NPROC_EXCEEDED;

	retval = unshare_files(&displaced);
	if (retval)
		goto out_ret;

	retval = -ENOMEM;
	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_execat(fd, filename, flags);
	retval = PTR_ERR(file);
	if (IS_ERR(file))
		goto out_unmark;

	sched_exec();

	bprm->file = file;
	if (fd == AT_FDCWD || filename->name[0] == '/') {
		bprm->filename = filename->name;
	} else {
		if (filename->name[0] == '\0')
			pathbuf = kasprintf(GFP_KERNEL, "/dev/fd/%d", fd);
		else
			pathbuf = kasprintf(GFP_KERNEL, "/dev/fd/%d/%s",
					    fd, filename->name);
		if (!pathbuf) {
			retval = -ENOMEM;
			goto out_unmark;
		}
		/*
		 * Record that a name derived from an O_CLOEXEC fd will be
		 * inaccessible after exec. Relies on having exclusive access to
		 * current->files (due to unshare_files above).
		 */
		if (close_on_exec(fd, rcu_dereference_raw(current->files->fdt)))
			bprm->interp_flags |= BINPRM_FLAGS_PATH_INACCESSIBLE;
		bprm->filename = pathbuf;
	}
	bprm->interp = bprm->filename;

	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;

	would_dump(bprm, bprm->file);

	retval = exec_binprm(bprm);
	if (retval < 0)
		goto out;

	/* execve succeeded */
	current->fs->in_exec = 0;
	current->in_execve = 0;
	membarrier_execve(current);
	acct_update_integrals(current);
	task_numa_free(current);
	free_bprm(bprm);
	kfree(pathbuf);
	putname(filename);
	if (displaced)
		put_files_struct(displaced);
	return retval;

out:
	if (bprm->mm) {
		acct_arg_size(bprm, 0);
		mmput(bprm->mm);
	}

out_unmark:
	current->fs->in_exec = 0;
	current->in_execve = 0;

out_free:
	free_bprm(bprm);
	kfree(pathbuf);

out_files:
	if (displaced)
		reset_files_struct(displaced);
out_ret:
	putname(filename);
	return retval;
}

简单叙述一下调用流程:

file = do_open_execat(fd, filename, flags); //打开文件

retval = bprm_mm_init(bprm);//初始化二进制elf加载器

retval = prepare_binprm(bprm);//填充bin的binprm参数,权限检查,读取文件头的128个字节

retval = exec_binprm(bprm); //执行可执行文件

		retval= search_binary_handler(bprm);//加载适合的elf加载器
		retval = fmt->load_binary(bprm);
       //调用加载回调加载函数load_binary指向了 binfmt_aout.c中load_aout_binary函数		

通过函数指针load_binary调用了elf加载器的回调函数

retval = load_aout_binary(struct linux_binprm * bprm); //加载器回调函数

通过设置一下几步设置参数以后,拉起了一个新的进程

retval = flush_old_exec(bprm);//清空旧的进程空间,释放相关资源

setup_new_exec(bprm);//设置进程名字,读取进程代码段

start_thread(regs, ex.a_entry, current->mm->start_stack);//开启一个进程
//arm 32
#define start_thread(regs,pc,sp)					({										memset(regs->uregs, 0, sizeof(regs->uregs));				if (current->personality & ADDR_LIMIT_32BIT)					regs->ARM_cpsr = USR_MODE;					else										regs->ARM_cpsr = USR26_MODE;					if (elf_hwcap & HWCAP_THUMB && pc & 1)						regs->ARM_cpsr |= PSR_T_BIT;					regs->ARM_cpsr |= PSR_ENDSTATE;						regs->ARM_pc = pc & ~1;		/* pc */				regs->ARM_sp = sp;		/* sp */				nommu_start_thread(regs);					})

//arm 64
static inline void start_thread_common(struct pt_regs *regs, unsigned long pc)
{
	memset(regs, 0, sizeof(*regs));
	forget_syscall(regs);
	regs->pc = pc;
}
static inline void start_thread(struct pt_regs *regs, unsigned long pc,
				unsigned long sp)
{
	start_thread_common(regs, pc);
	regs->pstate = PSR_MODE_EL0t;
	regs->sp = sp;
}

由以上代码可知,execve族系统调用主要是通过elf加载器解释加载到内存,再通过修改arm的pc sp指针,使得该elf可执行文件得以运行。

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多