linux elf加载过程
来源:互联网 发布:彩虹秒赞最新源码 编辑:程序博客网 时间:2024/05/21 02:48
通过前面linux 进程的创建和加载我们知道,调用ececve()系统调用后会加载指定的可执行程序并且运行起来,接下来我们分析这个加载过程,跑到do_execve()函数中。
linux-4.10/fs/exec.c
int do_execve(struct filename *filename,1806 const char __user *const __user *__argv,1807 const char __user *const __user *__envp)1808 {1809 struct user_arg_ptr argv = { .ptr.native = __argv };1810 struct user_arg_ptr envp = { .ptr.native = __envp };1811 return do_execveat_common(AT_FDCWD, filename, argv, envp, 0);1812 }关键的实现就在do_execveat_common()这个函数里面了
/*1657 * sys_execve() executes a new program.1658 */1659 static int do_execveat_common(int fd, struct filename *filename,1660 struct user_arg_ptr argv,1661 struct user_arg_ptr envp,1662 int flags)1663 {1664 char *pathbuf = NULL;1665 struct linux_binprm *bprm;1666 struct file *file;1667 struct files_struct *displaced;1668 int retval; ...1693 retval = -ENOMEM;1694 bprm = kzalloc(sizeof(*bprm), GFP_KERNEL); //申请一个linux_binprm 结构体 ...1705 file = do_open_execat(fd, filename, flags); //打开可执行文件1706 retval = PTR_ERR(file);1707 if (IS_ERR(file))1708 goto out_unmark;1709 1710 sched_exec(); //找到loading最低的cpu执行这个加载1711 ...1736 retval = bprm_mm_init(bprm); //创建进程的内存地址空间 ...1748 retval = prepare_binprm(bprm); //读取可执行文件前面的128字节 ...1767 retval = exec_binprm(bprm); //加载可执行程序并执行 ...1771 /* execve succeeded */1772 current->fs->in_exec = 0;1773 current->in_execve = 0;1774 acct_update_integrals(current);1775 task_numa_free(current);1776 free_bprm(bprm);1777 kfree(pathbuf);1778 putname(filename);1779 if (displaced)1780 put_files_struct(displaced);1781 return retval; ...1803 }
bprm = kzalloc(sizeof(*bprm), GFP_KERNEL);
sizeof(*bprm) 的大小是多少,是不是就是这个结构体的大小,有疑问的时候,我们可以通过代码实际的运行结果来验证。
main.c
#include <stdio.h>struct debug_struct{ int a; int b; int c;};int main(){struct debug_struct *debug0; printf("sizeof(*debug0) is:%d \n",sizeof(*debug0)); struct debug_struct debug1; printf("sizeof(debug1) is:%d \n",sizeof(debug1)); return 0;}
通过代码的实际运行情况,我们知道sizeof(*bprm) 就是结构体linux_binprm的大小。
linux-4.10/fs/exec.c
1634 static int exec_binprm(struct linux_binprm *bprm)1635 {1636 pid_t old_pid, old_vpid;1637 int ret;1638 1639 /* Need to fetch pid before load_binary changes it */1640 old_pid = current->pid;1641 rcu_read_lock();1642 old_vpid = task_pid_nr_ns(current, task_active_pid_ns(current->parent));1643 rcu_read_unlock();1644 1645 ret = search_binary_handler(bprm);1646 if (ret >= 0) {1647 audit_bprm(bprm);1648 trace_sched_process_exec(current, old_pid, bprm);1649 ptrace_event(PTRACE_EVENT_EXEC, old_vpid);1650 proc_exec_connector(current);1651 }1652 1653 return ret;1654 }因为我们是重点分析可执行程序文件的加载流程,所以我们理所当然的认为在调用exec_binprm()时,相关的初始化操作已经完成,并且bprm已经准备好了。
linux-4.10/include/linux/binfmts.h
14 struct linux_binprm {15 char buf[BINPRM_BUF_SIZE]; //可执行程序文件的头 128字节 #define BINPRM_BUF_SIZE 12816 #ifdef CONFIG_MMU17 struct vm_area_struct *vma;18 unsigned long vma_pages;19 #else20 # define MAX_ARG_PAGES3221 struct page *page[MAX_ARG_PAGES];22 #endif23 struct mm_struct *mm;24 unsigned long p; /* current top of mem */ // 当前内存页最高地址25 unsigned int26 cred_prepared:1,/* true if creds already prepared (multiple27 * preps happen for interpreters) */28 cap_effective:1;/* true if has elevated effective capabilities,29 * false if not; except for init which inherits30 * its parent's caps anyway */31 #ifdef __alpha__32 unsigned int taso:1;33 #endif34 unsigned int recursion_depth; /* only for search_binary_handler() */35 struct file * file; //打开的可执行程序文件36 struct cred *cred;/* new credentials */ //程序的权限37 int unsafe;/* how unsafe this exec is (mask of LSM_UNSAFE_*) */38 unsigned int per_clear;/* bits to clear in current->personality */39 int argc, envc; //argc为传递的参数个数,envc 为环境变量参数个数40 const char * filename;/* Name of binary as seen by procps */ 程序的名称41 const char * interp;/* Name of the binary really executed. Most 程序的二进制名称42 of the time same as filename, but could be43 different for binfmt_{misc,script} */44 unsigned interp_flags;45 unsigned interp_data;46 unsigned long loader, exec;47 };structlinux_binprm 这个结构体很重要,接着看search_binary_handler 做了什么
linux-4.10/fs/exec.c
/*1579 * cycle the list of binary formats handler, until one recognizes the image1580 */1581 int search_binary_handler(struct linux_binprm *bprm)1582 {1583 bool need_retry = IS_ENABLED(CONFIG_MODULES);1584 struct linux_binfmt *fmt;1585 int retval;1586 1587 /* This allows 4 levels of binfmt rewrites before failing hard. */1588 if (bprm->recursion_depth > 5)1589 return -ELOOP;...1597 read_lock(&binfmt_lock);1598 list_for_each_entry(fmt, &formats, lh) { //可以认为就是一个for循环,遍历formats里面的成员1599 if (!try_module_get(fmt->module))1600 continue;1601 read_unlock(&binfmt_lock);1602 bprm->recursion_depth++;1603 retval = fmt->load_binary(bprm); //尝试加载该可执行程序1604 read_lock(&binfmt_lock);1605 put_binfmt(fmt);1606 bprm->recursion_depth--;...1617 }1618 read_unlock(&binfmt_lock);...1629 1630 return retval;1631 }1632 EXPORT_SYMBOL(search_binary_handler);
linux_binfmt 的定义如下,成员函数指针保存对应module的函数。
linux-4.10/include/linux/binfmts.h
71 /*72 * This structure defines the functions that are used to load the binary formats that73 * linux accepts.74 */75 struct linux_binfmt {76 struct list_head lh;77 struct module *module;78 int (*load_binary)(struct linux_binprm *);79 int (*load_shlib)(struct file *);80 int (*core_dump)(struct coredump_params *cprm);81 unsigned long min_coredump;/* minimal dump size */82 };
formats 是一个全局链表,调用__register_binfmt()就可以把module相关的信息注册进来。
linux-4.10/fs/exec.c
static LIST_HEAD(formats);73 static DEFINE_RWLOCK(binfmt_lock);74 75 void __register_binfmt(struct linux_binfmt * fmt, int insert)76 {77 BUG_ON(!fmt);78 if (WARN_ON(!fmt->load_binary))79 return;80 write_lock(&binfmt_lock);81 insert ? list_add(&fmt->lh, &formats) :82 list_add_tail(&fmt->lh, &formats);83 write_unlock(&binfmt_lock);84 }85 86 EXPORT_SYMBOL(__register_binfmt);
这个函数被进一步封装成register_binfmt()
linux-4.10/include/linux/binfmts.h
/* Registration of default binfmt handlers */87 static inline void register_binfmt(struct linux_binfmt *fmt)88 {89 __register_binfmt(fmt, 0);90 }
所以我们看到elf 模块在初始化的时候,注册了对应的信息。
linux-4.10/fs/binfmt_elf.c
84 static struct linux_binfmt elf_format = {85 .module= THIS_MODULE,86 .load_binary= load_elf_binary,87 .load_shlib= load_elf_library,88 .core_dump= elf_core_dump,89 .min_coredump= ELF_EXEC_PAGESIZE,90 };…2326 static int __init init_elf_binfmt(void)2327 {2328 register_binfmt(&elf_format);2329 return 0;2330 }
所以上面调用retval = fmt->load_binary(bprm); 就调用到了binfmt_elf.c中的load_elf_binary()函数。
668 static int load_elf_binary(struct linux_binprm *bprm)669 {...673 char * elf_interpreter = NULL; //解释器段中解释器的路径...683 struct pt_regs *regs = current_pt_regs(); //获取当前进程的寄存器...724 for (i = 0; i < loc->elf_ex.e_phnum; i++) {725 if (elf_ppnt->p_type == PT_INTERP) { //找到解释器段... 754 interpreter = open_exec(elf_interpreter); //加载解释器,返回的是一个文件...766 /* Get the exec headers */767 retval = kernel_read(interpreter, 0, //读取解释器的程序头表768 (void *)&loc->interp_elf_ex,769 sizeof(loc->interp_elf_ex));...777 }778 elf_ppnt++;779 }...855 setup_new_exec(bprm); //cred 机制856 install_exec_creds(bprm); //cred 机制。授权,权限相关...865 current->mm->start_stack = bprm->p; //指向栈顶866 867 /* Now we do a little grungy work by mmapping the ELF image into868 the correct location in memory. */869 for(i = 0, elf_ppnt = elf_phdata;870 i < loc->elf_ex.e_phnum; i++, elf_ppnt++) {875 if (elf_ppnt->p_type != PT_LOAD) //PT_LOAD 才需要加载876 continue;...933 error = elf_map(bprm->file, load_bias + vaddr, elf_ppnt, //将文件中的PT_LOAD段映射到对应的地址934 elf_prot, elf_flags, total_size);...981 }...1004 if (elf_interpreter) { //如果需要解释器,将解释器文件映射进来1005 unsigned long interp_map_addr = 0;1006 1007 elf_entry = load_elf_interp(&loc->interp_elf_ex, //elf_entry 返回解释器的起始位置1008 interpreter,1009 &interp_map_addr,1010 load_bias, interp_elf_phdata);1011 if (!IS_ERR((void *)elf_entry)) {1012 /*1013 * load_elf_interp() returns relocation1014 * adjustment1015 */1016 interp_load_addr = elf_entry; //保存解释器加载的其实位置1017 elf_entry += loc->interp_elf_ex.e_entry;//执行解释器的入口地址,PT_LOAD段起始位置加上偏移,也就是_start()函数入口1018 }...1029 } else {1030 elf_entry = loc->elf_ex.e_entry; //如果没有解释器,就是elf文件中指定的程序入口1031 if (BAD_ADDR(elf_entry)) {1032 retval = -EINVAL;1033 goto out_free_dentry;1034 }1035 }...1047 1048 retval = create_elf_tables(bprm, &loc->elf_ex, //拷贝参数到用户空间1049 load_addr, interp_load_addr); //load_addr指向可执行程序加载的起始位置,interp_load_addr是解释器加载的起始位置...1090 start_thread(regs, elf_entry, bprm->p); //跳转到用户空间去执行,即elf_entry执行的函数1091 retval = 0;..1095 return retval;...1108 }
我们看一下解释器的加载流程
519 static unsigned long load_elf_interp(struct elfhdr *interp_elf_ex,520 struct file *interpreter, unsigned long *interp_map_addr,521 unsigned long no_base, struct elf_phdr *interp_elf_phdata)522 {523 struct elf_phdr *eppnt;524 unsigned long load_addr = 0; //解释器的加载到内存的地址...total_size = total_mapping_size(interp_elf_phdata, //因为两个PT_LOAD段是连续的,所以这里计算出两个PT_LOAD段的大小541 interp_elf_ex->e_phnum);...547 eppnt = interp_elf_phdata;548 for (i = 0; i < interp_elf_ex->e_phnum; i++, eppnt++) {549 if (eppnt->p_type == PT_LOAD) { //PT_LOAD 段才会被加载到内存中...562 if (interp_elf_ex->e_type == ET_EXEC || load_addr_set)563 elf_type |= MAP_FIXED;564 else if (no_base && interp_elf_ex->e_type == ET_DYN)565 load_addr = -vaddr;566 567 map_addr = elf_map(interpreter, load_addr + vaddr, //将解释器的PT_LOAD 段映射进来568 eppnt, elf_prot, elf_type, total_size);...576 if (!load_addr_set &&577 interp_elf_ex->e_type == ET_DYN) {578 load_addr = map_addr - ELF_PAGESTART(vaddr);579 load_addr_set = 1;580 }...611 }612 }...637 error = load_addr;638 out:639 return error;640 }
最终会跳转到用户空间去执行,那么在跳转到用户空间之前,这些参数放在哪里,是什么样的格式呢?
149 static int150 create_elf_tables(struct linux_binprm *bprm, struct elfhdr *exec,151 unsigned long load_addr, unsigned long interp_load_addr)152 {153 unsigned long p = bprm->p; //栈顶位置154 int argc = bprm->argc; //传递的参数个数155 int envc = bprm->envc; //环境变量参数个数156 elf_addr_t __user *argv; //用户空间参数157 elf_addr_t __user *envp; //用户空间环境参数158 elf_addr_t __user *sp; //用户空间指针,一个指针而已...177 p = arch_align_stack(p);... //前面拷贝的参数就不分析了,我们只关心有用的参数216 /* Create the ELF interpreter info */217 elf_info = (elf_addr_t *)current->mm->saved_auxv; //218 /* update AT_VECTOR_SIZE_BASE if the number of NEW_AUX_ENT() changes */219 #define NEW_AUX_ENT(id, val) \ //类似与键值对的形式,第一个参数是key,第二个是值,都是一个整形值220 do { \221 elf_info[ei_index++] = id; \222 elf_info[ei_index++] = val; \223 } while (0)224 ... 234 NEW_AUX_ENT(AT_HWCAP, ELF_HWCAP);235 NEW_AUX_ENT(AT_PAGESZ, ELF_EXEC_PAGESIZE);236 NEW_AUX_ENT(AT_CLKTCK, CLOCKS_PER_SEC);237 NEW_AUX_ENT(AT_PHDR, load_addr + exec->e_phoff);238 NEW_AUX_ENT(AT_PHENT, sizeof(struct elf_phdr));239 NEW_AUX_ENT(AT_PHNUM, exec->e_phnum);240 NEW_AUX_ENT(AT_BASE, interp_load_addr);241 NEW_AUX_ENT(AT_FLAGS, 0);242 NEW_AUX_ENT(AT_ENTRY, exec->e_entry);243 NEW_AUX_ENT(AT_UID, from_kuid_munged(cred->user_ns, cred->uid));244 NEW_AUX_ENT(AT_EUID, from_kuid_munged(cred->user_ns, cred->euid));245 NEW_AUX_ENT(AT_GID, from_kgid_munged(cred->user_ns, cred->gid));246 NEW_AUX_ENT(AT_EGID, from_kgid_munged(cred->user_ns, cred->egid));247 NEW_AUX_ENT(AT_SECURE, security_bprm_secureexec(bprm));248 NEW_AUX_ENT(AT_RANDOM, (elf_addr_t)(unsigned long)u_rand_bytes);...265 /* AT_NULL is zero; clear the rest too */266 memset(&elf_info[ei_index], 0, //后面的的内容赋值为0,可以认为elf_info执行一个比较大的buffer,只是用了前面,没有使用的这里赋值为0267 sizeof current->mm->saved_auxv - ei_index * sizeof elf_info[0]);268 269 /* And advance past the AT_NULL entry. */270 ei_index += 2; //后面两个都是0表示结束271 272 sp = STACK_ADD(p, ei_index); //sp反过来想就是指向上面两个0结束的位置273 274 items = (argc + 1) + (envc + 1) + 1; //参数的个数,多加了三个1是因为前面一个1用于保存参数格式,后面两个1用于参数结束标识275 bprm->p = STACK_ROUND(sp, items); //这时bprm->p 指向了参数结束位置,等于sp + items,宏STACK_ROUND为了对齐...294 /* Now, let's put argc (and argv, envp if appropriate) on the stack */295 if (__put_user(argc, sp++)) //保存传递的参数个数,可以理解为就是main()函数里面的参数296 return -EFAULT;297 argv = sp; //对应的参数的其实地址298 envp = argv + argc + 1; //299 300 /* Populate argv and envp */301 p = current->mm->arg_end = current->mm->arg_start;302 while (argc-- > 0) {303 size_t len;304 if (__put_user((elf_addr_t)p, argv++))305 return -EFAULT;306 len = strnlen_user((void __user *)p, MAX_ARG_STRLEN);307 if (!len || len > MAX_ARG_STRLEN)308 return -EINVAL;309 p += len;310 }311 if (__put_user(0, argv)) //保存环境变量的参数的参数个数312 return -EFAULT;313 current->mm->arg_end = current->mm->env_start = p;314 while (envc-- > 0) {315 size_t len;316 if (__put_user((elf_addr_t)p, envp++))317 return -EFAULT;318 len = strnlen_user((void __user *)p, MAX_ARG_STRLEN);319 if (!len || len > MAX_ARG_STRLEN)320 return -EINVAL;321 p += len;322 }323 if (__put_user(0, envp)) //参数结束标识324 return -EFAULT;325 current->mm->env_end = p;326 327 /* Put the elf_info on the stack in the right place. */328 sp = (elf_addr_t __user *)envp + 1;329 if (copy_to_user(sp, elf_info, ei_index * sizeof(elf_addr_t)))330 return -EFAULT;/* 所以上面的操作执行完成后,用户空间的栈如下 * | 0 | * | 0 | * | elf_info[valn] | * | elf_info[idn] | * | ... | * | elf_info[val0] | * | elf_info[id0] | * | 0 | * | envn | * | ... | * | env0 | * | 0 | * | argn | * | .... | * | arg0 | * bprm->p -> | argc | */331 return 0;332 }
linux-4.10/arch/arm64/include/asm/processor.h
107 static inline void start_thread_common(struct pt_regs *regs, unsigned long pc)108 {109 memset(regs, 0, sizeof(*regs));110 regs->syscallno = ~0UL;111 regs->pc = pc;112 }113 114 static inline void start_thread(struct pt_regs *regs, unsigned long pc,115 unsigned long sp)116 {117 start_thread_common(regs, pc); //返回用户空间时的执行地址118 regs->pstate = PSR_MODE_EL0t;119 regs->sp = sp; //对应的就是 bprm->p120 }也就是给pc寄存器赋值为用户空间函数的执行入口,sp寄存器用于传递对应的参数,参数保存的格式上面有详细给出来
bionic/linker/arch/arm64/begin.S
29 #include <private/bionic_asm.h>30 31 ENTRY(_start)32 mov x0, sp33 bl __linker_init34 35 /* linker init returns the _entry address in the main image */36 br x037 END(_start)首先是跳转到linker解释器 的start_(),用户空间执行的第一个函数不一定是main()函数,_start()函数就是将sp赋值给x0寄存器,然后跳到__linker_init(),参数就是x0对应的值,也就是在sp执行的地址,__linker_init()执行返回后,参数也会放在x0,所以最后的br x0 就是跳到__linker_init()返回的函数地址。__linker_init() 也就是解释器的执行流程下篇在做分析。
- linux elf加载过程
- linux中ELF加载过程分析
- linux中ELF加载过程分析
- LINUX平台下ELF文件加载过程
- linux中ELF加载过程分析
- linux中ELF加载过程分析
- ELF在Linux下的加载过程
- linux中ELF加载过程分析
- linux中ELF加载过程分析
- 分析ELF的加载过程
- 概括分析elf加载过程
- 分析ELF的加载过程
- ELF文件的格式和加载过程
- ELF文件加载过程代码分析
- ELF文件的格式和加载过程
- ELF文件的格式和加载过程
- linux中ELF加载过程分析 - 博青港湾-技术空间 - CSDNBlog
- ELF文件的加载过程(load_elf_binary函数详解)--Linux进程的管理与调度(十三)
- Database—数据库基础
- Verilog序列检测器-两例
- Java 23种设计模式之原型模式
- [BZOJ4002][JLOI2015]有意义的字符串(结论+矩阵乘法)
- C#去除HTML标签
- linux elf加载过程
- 变分推断
- 网络判断
- [BZOJ2286][SDOI2011]消耗战(虚树+树形DP)
- 没有意大利炮的团长
- 文章标题
- 机器学习实战——岭回归、缩减法
- 【贪心+最小割】BZOJ2521 [Shoi2010]最小生成树
- 推荐机制 协同过滤和基于内容推荐的区别