“拆解应用程序” 的需求

实现运行库和应用代码分离

  • Linux 中绝大部分应用都是动态链接的
    • 系统中只有一份 libc
  • Library 保持接口的向后兼容
    • 补丁发布后不再需要重编译所有依赖的应用
    • Semantic Versioning
      • “Compatible” 是个有些微妙的定义

大型项目内部也可以内部分解

  • 编译一部分,不用重新链接
  • libjvm.so, libart.so, ...
    • NEMU: “把 CPU 插上主板”

动态链接:今天不讲 ELF

和 ELF battle 的每一年:讲着讲着就讲不下去了

  • 每句话都没说错,但没人能跟上
    • 根本原因:概念上紧密相关的东西在实现中被 “拆散” 了
      • GOT[0], GOT[1], ... ???

换一种方法

  • 如果编译器、链接器、加载器都受你控制
  • 你怎么设计、实现一个 “最直观” 的动态链接格式?
    • 再去考虑怎么改进它,你就得到了 ELF!
  • 假设编译器可以为你生成位置无关代码 (PIC)

设计一个新的二进制文件格式

动态链接的符号查表就行了嘛

DL_HEAD

LOAD("libc.dl") # 加载动态库
IMPORT(putchar) # 加载外部符号
EXPORT(hello)   # 为动态库导出符号

DL_CODE

hello:
  ...
  call DSYM(putchar) # 动态链接符号
  ...

DL_END

用最小代价为 .dl 文件配齐全套工具链

编译器

  • 开局一条狗,出门全靠偷 (GCC, GNU as)

binutils

  • ld = objcopy (偷来的)
  • as = GNU as (偷来的)
  • 剩下的就需要自己动手了
    • readdl (readelf)
    • objdump
    • 你同样可以山寨 addr2line, nm, objcopy, ...

和最重要的加载器

  • 这个也得自己动手了

解决 dl 文件的设计缺陷

存储保护和加载位置

  • 允许将 .dl 中的一部分以某个指定的权限映射到内存的某个位置 (program header table)

允许自由指定加载器 (而不是 dlbox)

  • 加入 INTERP

空间浪费

  • 字符串存储在常量池,统一通过 “指针” 访问
    • 这是带来 ELF 文件难读的最根本原因

其他:不那么重要

  • 按需 RTFM/RTFSC

另一个重要的缺陷:性能

#define DSYM(sym)   *sym(%rip)

DSYM 是间接内存访问

extern void foo();
foo();

一种写法,两种情况

  • 来自其他编译单元 (静态链接)
    • 直接 PC 相对跳转即可
  • 动态链接库
    • 必须查表 (编译时不能决定)