内核如何获得在linux下运行的可执行二进制文件?

时间:2011-12-02 06:25:42

标签: c linux

内核如何获得在linux下运行的可执行二进制文件?

这似乎是一个简单的问题,但任何人都可以帮助我深入挖掘?如何将文件加载到内存以及如何开始执行代码?

任何人都可以帮助我,一步一步地告诉我们发生了什么吗?

4 个答案:

答案 0 :(得分:24)

Linux 4.0上exec系统调用的最佳时刻

找到所有这些的最佳方法是使用QEMU GDB步骤调试内核:How to debug the Linux kernel with GDB and QEMU?

  • fs/exec.c定义SYSCALL_DEFINE3(execve

    处的系统调用

    简单地转发到do_execve

  • do_execve

    转发至do_execveat_common

  • do_execveat_common

    要查找下一个主要功能,请跟踪上次修改返回值retval的时间。

    开始构建struct linux_binprm *bprm来描述该程序,并将其传递给exec_binprm以执行。

  • exec_binprm

    再次按照返回值查找下一个主要电话。

  • search_binary_handler

    • 处理程序由可执行文件的第一个魔术字节确定。

      两个最常见的处理程序是解释文件(#! magic)和ELF(\x7fELF magic)的处理程序,但内核中还有其他内置程序,例如: a.out。用户也可以注册自己的/proc/sys/fs/binfmt_misc

      ELF处理程序在fs/binfmt_elf.c定义。

      另请参阅:Why do people write the #!/usr/bin/env python shebang on the first line of a Python script?

    • formats列表包含所有处理程序。

      每个处理程序文件包含以下内容:

      static int __init init_elf_binfmt(void)
      {
          register_binfmt(&elf_format);
          return 0;
      }
      

      elf_format是该文件中定义的struct linux_binfmt

      __init是神奇的,并将该代码放入一个魔术部分,在内核启动时被调用:What does __init mean in the Linux kernel code?

      链接器级依赖注入!

    • 如果解释器无限地执行自身,还会有一个递归计数器。

      试试这个:

      echo '#!/tmp/a' > /tmp/a
      chmod +x /tmp/a
      /tmp/a
      
    • 我们再次按照返回值查看接下来会发生什么,并看到它来自:

      retval = fmt->load_binary(bprm);
      

      其中load_binary为结构上的每个处理程序定义:C风格的多态性。

  • fs/binfmt_elf.c:load_binary

    实际工作是否正确:

    • 根据规范
    • 解析ELF文件
    • 根据解析后的ELF设置进程初始程序状态(内存为struct linux_binprm,注册为struct pt_regs
    • 致电start_thread,这是真正开始安排的地方

TODO:继续进行源分析。我期望接下来会发生什么:

  • 内核解析ELF的INTERP头以找到动态加载器(通常设置为/lib64/ld-linux-x86-64.so.2)。
  • 如果存在:
    • 内核将动态加载程序和要执行的ELF映射到内存
    • 启动动态加载程序,将指针指向内存中的ELF。
    • 现在在userland中,加载器以某种方式解析elf标头,并对它们进行dlopen
    • dlopen使用可配置的搜索路径查找这些库(ldd和朋友),将它们映射到内存,并以某种方式通知ELF在哪里找到其缺失的符号
    • loader调用ELF的_start
  • 否则,内核直接将可执行文件加载到内存中,而不使用动态加载程序。

    因此,必须特别检查可执行文件是否为PIE,如果将其放在内存中的随机位置:What is the -fPIE option for position-independent executables in gcc and ld?

答案 1 :(得分:8)

system calls中的两个linux kernel是相关的。 fork系统调用(或者vforkclone)用于创建新进程,类似于调用进程(除init之外的每个Linux用户进程都是由fork或朋友创建。 execve系统调用用一个新的进程地址空间替换(主要是从ELF可执行文件和匿名段中排序mmap段,然后初始化寄存器,包括堆栈指针)。 x86-64 ABI supplementLinux assembly howto提供了详细信息。

动态链接发生在execve之后,涉及/lib/x86_64-linux-gnu/ld-2.13.so文件,ELF被视为“解释器”。

答案 2 :(得分:7)

阅读已经引用的ELF docs后,您应该read the kernel code实际执行此操作。

如果您无法理解该代码,请构建UML Linux,然后您可以在调试器中单步执行该代码。

答案 3 :(得分:2)

您可以从了解可执行文件格式(例如ELF)开始。 http://en.wikipedia.org/wiki/Executable_and_Linkable_Format

ELF文件包含几个带标题的部分,这些部分描述了二进制文件应该如何以及在何处加载到内存中。

然后,我建议阅读linux中加载二进制文件并处理动态链接的部分ld-linux。这也是ld-linux的一个很好的描述:http://www.cs.virginia.edu/~dww4s/articles/ld_linux.html