Windyland 内核崩坏

linux是如何加载ELF格式的文件的

可执行文件/库文件的格式

最早期的时候linux(unix) 是使用了一种a.out 的文件格式, 但是此类格式没有加载动态库的功能, 后来出现的COFF支持了动态库的功能, 但是每次动态库的地址每次都是变化的。后来1994年出现的ELF格式(PIC)解决了包括这个功能在内的 一些遗留问题,从那个时代开始ELF成为linux世界的主流文件格式。可参考 0

链接链接

可执行文件一般都是由三步阶段:编译、链接、加载, 进入系统的。因为如果是依赖某些动态库,比如常见的libc, 就需要在加载时候根据可执行文件的地址和动态库的对应符号的地址推算出被调用函数的地址, 所以这个过程又被称为动态链接。

下面假定为PIC 模型(Position Independent Code)

ELF 格式

  • 读取ELF文件的头部,分析出PT_INTERP 端(interpreter),确定需要的动态链接器的地址。

    通常是 /lib/ld-linux.so.2 (或者是 /lib64/ld-linux-x86-64.so.2)

  • 读取ELF文件的DT_RPATH 或者是DT_RUNPATH 部分,RPATH是现代可执行文件格式的重要部分,一般被称为 runtime path, 在环境变量LD_LIBRARY_PATH 之后, 动态连接器会最先搜寻的路径, 其后才是/usr/lib/lib以及/etc/ld.conf 中定义的路径。可参考 1

  • RPATH 中有一特定的值是$ORIGIN, 可用于指向目标文件所在的目录。

    可以在编译时候通过 -Wl,rpath=xxx 来指定 RPATH 比如 -Wl,rpath=\$ORIGIN

  • 读取ELF文件的RPATH 以后动态连接器会根据ELF文件的 DT_NEEDED 字段来推算此文件需要的动态库的实际地址,一条 DT_NEEDED 对应一条动态库

  • 读取DT_NEEEDEDDT_RPATH 可以通过 binutils包里的 objdump 命令完成: objdump -x xxx

    输出很长,截取最前面一段就可以

  • 或者glibc 版本比较新,会带上一个 readelf 命令: readelf -d xxx

  • 最后可以通过 ldd xxx 查看最后经过计算以后、实际会加载的动态库列表

patchelf

Mach-O 格式的差异

  • 使用的是DYLD_LIBRARY_PATH (代替LD_LIBRARY_PATH), 而且多了一个DYLD_FALLBACK_LIBRARY_PATH
  • RPATH 是在LD_RPATH里, 而且没有$ORIGIN 但是有@executable_path@loader_path
  • NEEDED 字段被称为 LD_LOAD_DYLIB, 提供信息更多, 通过otool -l xxx 可以查看
  • 由于没有ldd命令,所以可以通过 otool -L xxx 代替
  • 由于没有patchelf命令,所以可以通过 install_name_tool 代替

参考资料