您的位置:首页 > 其它

第七课 可执行程序的装载(2)

2016-04-09 21:59 274 查看
可执行程序的装载之前的工作

通过shell程序启动一个可执行程序,shell程序到底做了什么?在执行exec调用可执行程序之前,shell要做哪些工作?

举个例子:

ls -l /usr/bin

实际上shell调用了可执行程序ls。-l 和/usr/bin是ls程序的入口参数。shell命令将会调用exec函数并把参数传递给可执行程序的main函数。

int execve(const char * filename,char * const argv[ ],char * const envp[ ]);


shell首先创建一个子进程,然后在子进程中调用exec函数,从而执行新的程序。

命令行参数是如何传递给新的程序的main函数呢?换句话说,是如何进入到新的进程的堆栈中的呢?再来看一下函数的调用过程。

shell函数调用execve函数,调用sys_execve系统调用,在初始化新的程序堆栈的时候,将会依据系统调用和入参的指针值拷贝信息到自己的堆栈中。此时旧的堆栈信息将会被清空。仅仅在内存中存储了一份,是新的一份参数。

粘贴实验结果。

动态库有两种使用方法,一种是动态链接,一种是动态装载。

动态链接是直接调用函数

动态装载是直接操作*.so文件。此种是程序自身去状态库。

装载过程

execve是一种系统调用,来完成可执行程序的执行。但是他是一种特殊的系统调用。与fork函数类似,都是特殊的。

当前的可执行程序在执行中,当执行到execve时,陷入到内核态,把当前可执行程序被覆盖掉之后,函数在返回时将会进入到新的可执行程序的执行起点。因此main函数的执行环境必须在内核中首先创建好,才能正确的执行。

下面我们来追踪execve函数调用流程:

int do_execve(struct filename *filename,
const char __user *const __user *__argv,
const char __user *const __user *__envp)
{
struct user_arg_ptr argv = { .ptr.native = __argv };
struct user_arg_ptr envp = { .ptr.native = __envp };
return do_execve_common(filename, argv, envp);
}


上面是函数实现,调用了do_execve_common这个函数。

/*
* sys_execve() executes a new program.
*/
static 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;

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;

//为当前的进程复制一份已经打开的文件句柄,这个函数是对copy_files的进一步的封装。
retval = unshare_files(&displaced);
if (retval)
goto out_ret;
//结构体开辟空间
retval = -ENOMEM;
bprm = kzalloc(sizeof(*bprm), GFP_KERNEL);
if (!bprm)
goto out_files;
//给出新的credit,叫做证书或者权限
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;

//因为接下来要执行新的程序,在多cpu的情况下,为了负载均衡,就要用到调度器来处理。这个函数还应用在fork函数中。这是linux下的创建时负载均衡机制。
sched_exec();

bprm->file = file;
bprm->filename = bprm->interp = filename->name;

retval = bprm_mm_init(bprm);
if (retval)
goto out_unmark;
//传递argc个数
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;

//填充bprm其他字段
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;

/* execve succeeded */
current->fs->in_exec = 0;
current->in_execve = 0;
acct_update_integrals(current);
task_numa_free(current);
free_bprm(bprm);
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);

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


上述代码中,在为下载做准备的时候,仅仅围绕着结构体linux_binprm 展开。linux_binprm 结构体定义如下:

/*
* This structure is used to hold the arguments that are used when loading binaries.
*/
struct linux_binprm {
char buf[BINPRM_BUF_SIZE];
#ifdef CONFIG_MMU
struct vm_area_struct *vma;
unsigned long vma_pages;
#else
# define MAX_ARG_PAGES  32
struct page *page[MAX_ARG_PAGES];
#endif
struct mm_struct *mm;
unsigned long p; /* current top of mem */
unsigned int
cred_prepared:1,/* true if creds already prepared (multiple
* preps happen for interpreters) */
cap_effective:1;/* true if has elevated effective capabilities,
* false if not; except for init which inherits
* its parent's caps anyway */
#ifdef __alpha__
unsigned int taso:1;
#endif
unsigned int recursion_depth; /* only for search_binary_handler() */
struct file * file;
struct cred *cred;  /* new credentials */
int unsafe;     /* how unsafe this exec is (mask of LSM_UNSAFE_*) */
unsigned int per_clear; /* bits to clear in current->personality */
int argc, envc;
const char * filename;  /* Name of binary as seen by procps */
const char * interp;    /* Name of the binary really executed. Most of the time same as filename, but could be
different for binfmt_{misc,script} */
unsigned interp_flags;
unsigned interp_data;
unsigned long loader, exec;
};


代码最后调用函数exec_binprm继续执行,函数实现为:

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;
}


这里search_binary_handler寻找二进制handler是指寻找二进制文件的处理函数。

寻找函数中一段关键代码是:

list_for_each_entry(fmt, &formats, lh) {
if (!try_module_get(fmt->module))
continue;
read_unlock(&binfmt_lock);
bprm->recursion_depth++;
retval = fmt->load_binary(bprm);
read_lock(&binfmt_lock);
put_binfmt(fmt);
bprm->recursion_depth--;


寻找到能够解析当前文件的fmt之后,执行对应的load_binary函数。load_binary函数在别处初始化,此时elf对应的解析函数是:

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_elf_binary最后调用了如下函数:

start_thread(regs, elf_entry, bprm->p);
retval = 0;


start_kernel中函数分析:

void
start_thread(struct pt_regs *regs, unsigned long new_ip, unsigned long new_sp)
{
set_user_gs(regs, 0);
regs->fs        = 0;
regs->ds        = __USER_DS;
regs->es        = __USER_DS;
regs->ss        = __USER_DS;
regs->cs        = __USER_CS;
regs->ip        = new_ip;
regs->sp        = new_sp;
regs->flags     = X86_EFLAGS_IF;
/*
* force it to the iret return path by making it look as if there was
* some work pending.
*/
set_thread_flag(TIF_NOTIFY_RESUME);
}
EXPORT_SYMBOL_GPL(start_thread);


regs实际上是内核的栈底的位置,这里在不断的修改regs中值。栈底的ip值被赋予了新的值,sp被赋予了新的值。

ip就是新的程序的入口点,通过上述的调用流程可知是在函数load_elf_binary中elf_entry这个值。而在静态链接文件中,这个值就是在elf文件中的函数入口点的定义值。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: