Linux Kernel - 可执行程序的加载过程【转】

2022/2/14 7:11:36

本文主要是介绍Linux Kernel - 可执行程序的加载过程【转】,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

转自:http://www.dosrc.com/mark/linux-3.18.6/2016/05/15/linux-kernel-loading-of-executable-program.html

重点知识总结

  • 可执行文件的装载也是一个系统调用(execve),只不过和fork系统调用一样有一些特殊。
  • Shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数。
  • 库函数exec*都是execve的封装例程。

可执行文件的文件格式,Shell环境,动态链接库。

  • (ELF文件格式),ELF格式的文件默认加载到进程空间的0X804800处。
  • Shell环境:加载一个新程序会覆盖当前进程的用户进程空间,所以Shell命令行要加载一个新程序时,先要fork一个新进程,在新进程中加载新程序,否则Shell进程就被覆盖了,那么命令行参数和环境变量是如何进入新程序堆栈的。通过Shell程序 -> execve -> sys_execve 然后在初始化新程序堆栈时拷贝进去。即先函数调用参数传递,再系统调用参数传递将命令行参数和环境变量传递进新程序堆栈。
  • 动态链接库:分为装载时动态链接和运行时动态链接,如何在64位环境下编译32位动态链接库参看下面链接。

    动态链接

可执行文件的文件格式的解析。

  • 寻找文件格式对应的解析模块,如下:

    sys_execve -> do_execve -> do_execve_common -> exec_binprm -> search_binary_handler

    search_binary_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);
          ...
        }
    

    对于ELF格式的可执行文件,语句fmt->load_binary(bprm);执行的应该是load_elf_binary(bprm);。 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,
      };
    
      static int __init init_elf_binfmt(void)
      {
          register_binfmt(&elf_format);
          return 0;
      }
    

    这里不同的文件格式解析模块就是观察者,可执行文件就是被观察者,list_for_each_entry遍历文件格式解析模块向其发送通知,通知文件格式解析模块来解析当前的可执行文件,只不过这里只通知对应的文件解析模块(对应的观察者),而不是所有的文件格式解析模块(所有的观察者)。

可执行文件的执行起点

  • 上面已经说了,对于ELF格式的文件。执行到fmt->load_binary(bprm);,实际上执行的是load_elf_binary(bprm);,在load_elf_binary(bprm);函数中的start_thread(regs, elf_entry, bprm->p);语句指明了可执行文件的起点。函数原型start_thread(struct pt_regs *regs, unsigned long new_ip, unsigned long new_sp)中的第二个参数就是新程序的起点,它通过修改内核堆栈中EIP的值作为新程序的起点,execve系统调用返回到用户态就从这里开始执行。

  • 对于静态链接的可执行文件elf_entry就是ELF头中定义的起点,对于动态链接的可执行文件,先加载连接器ld,将CPU控制权交给ld来加载依赖库并完成动态链接,这部分不由内核完成,源代码如下:

      if (elf_interpreter) {//需要动态链接
          unsigned long interp_map_addr = 0;
    
          elf_entry = load_elf_interp(&loc->interp_elf_ex,
                  interpreter, &interp_map_addr, load_bias);
          ...
      } else {//不需要动态链接
          elf_entry = loc->elf_ex.e_entry;
      }
    

wdk 原创作品转载请注明出处
相关链接 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000



这篇关于Linux Kernel - 可执行程序的加载过程【转】的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程