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  }


最后调用start_thread()转到用户空间执行

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() 也就是解释器的执行流程下篇在做分析。