第七课 可执行程序的装载(2)
2016-04-09 21:59
274 查看
可执行程序的装载之前的工作
通过shell程序启动一个可执行程序,shell程序到底做了什么?在执行exec调用可执行程序之前,shell要做哪些工作?
举个例子:
ls -l /usr/bin
实际上shell调用了可执行程序ls。-l 和/usr/bin是ls程序的入口参数。shell命令将会调用exec函数并把参数传递给可执行程序的main函数。
shell首先创建一个子进程,然后在子进程中调用exec函数,从而执行新的程序。
命令行参数是如何传递给新的程序的main函数呢?换句话说,是如何进入到新的进程的堆栈中的呢?再来看一下函数的调用过程。
shell函数调用execve函数,调用sys_execve系统调用,在初始化新的程序堆栈的时候,将会依据系统调用和入参的指针值拷贝信息到自己的堆栈中。此时旧的堆栈信息将会被清空。仅仅在内存中存储了一份,是新的一份参数。
动态库有两种使用方法,一种是动态链接,一种是动态装载。
动态链接是直接调用函数
动态装载是直接操作*.so文件。此种是程序自身去状态库。
当前的可执行程序在执行中,当执行到execve时,陷入到内核态,把当前可执行程序被覆盖掉之后,函数在返回时将会进入到新的可执行程序的执行起点。因此main函数的执行环境必须在内核中首先创建好,才能正确的执行。
下面我们来追踪execve函数调用流程:
上面是函数实现,调用了do_execve_common这个函数。
上述代码中,在为下载做准备的时候,仅仅围绕着结构体linux_binprm 展开。linux_binprm 结构体定义如下:
代码最后调用函数exec_binprm继续执行,函数实现为:
这里search_binary_handler寻找二进制handler是指寻找二进制文件的处理函数。
寻找函数中一段关键代码是:
寻找到能够解析当前文件的fmt之后,执行对应的load_binary函数。load_binary函数在别处初始化,此时elf对应的解析函数是:
在函数load_elf_binary最后调用了如下函数:
start_kernel中函数分析:
regs实际上是内核的栈底的位置,这里在不断的修改regs中值。栈底的ip值被赋予了新的值,sp被赋予了新的值。
ip就是新的程序的入口点,通过上述的调用流程可知是在函数load_elf_binary中elf_entry这个值。而在静态链接文件中,这个值就是在elf文件中的函数入口点的定义值。
通过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文件中的函数入口点的定义值。
相关文章推荐
- 20145326 《Java程序设计》第6周学习总结
- hdu1669+二分多重匹配+二分
- 第9课 函数重载分析(下)
- Linux学习三部曲(之三)
- 第四次作业
- Android利用Cookie实现码源登录效果二
- Liunx文本处理三剑客之awk
- leetcode---Valid Palindrome
- nginx源码包编译安装
- [Web API] Client 指定請求及回應格式(xml/json)
- 解决Ubuntu下chrome总是在Dock上留两个图标
- android 面试总结(2)
- ubuntu14.04LTS安装steam
- 排序
- Nginx安装手册
- Landoj 1603 - Minimum Sum (暴力&模拟)
- 【剑指 offer】(二十九)—— 数组中出现次数超过一半的数字(及该数字出现的次数)
- 记票统计(华为oj)
- CentOS 7卸载openjdk
- Matlab并行编程方法