#linker #dynamic #parallel #experiment #binary #elf #now

nightly m4b/dryad

一个近似并行、半功能的动态链接器实验,使用 Rust 编写

3 个版本

使用旧的 Rust 2015

0.1.2 2016年4月14日
0.1.1 2016年1月29日
0.1.0 2015年10月23日

#992文本处理

158 星 & 17 关注者

115KB
2K SLoC

欢迎

Build Status Floobits Status

dryad

dryad第一个唯一并行、64 位 ELF 动态链接器,针对 GNU/Linux 编写,完全使用 Rust 编写,并且是

  1. 不是并行的
  2. 尚未准备好投入生产
  3. 一个原型
  4. 实际上并不工作
  5. 处于高度动荡状态
  6. 并行可能是 a) 不可能的,b) 性能不佳,但尝试一下会很令人感兴趣

但所有这些都将随着时间的推移而消失!

由于多种原因,这项工作已经停滞,主要概述如下 这里,但我时不时地会玩弄它。

我有一些修复问题的想法,但还有很多事情要做!

如果您想做出贡献,请提交 PR 或建议,评论,问题,总是欢迎 :) 如果您想对其他有趣的二进制内容进行黑客攻击,goblincargo-sym 总是需要额外的手或两个。

构建

您需要安装 rustup 工具,然后切换到夜间版并添加 musl 目标

rustup default nightly
rustup target add x86_64-unknown-linux-musl

您现在真正需要的是 rustup、互联网连接和您要链接的目标的链接器

  • ld(或 ld.gold
  • curl
  • 互联网连接
  • x86-64 GNU/Linux 机器

不幸的是,我目前不支持交叉编译(这无论如何都是一种不寻常的使用情况),因此您将需要一个 x86-64 GNU/Linux 机器,否则将会失败。

一旦确定,您就可以像平常一样继续操作

  1. ./gen_tests.sh - 构建测试二进制文件(只需做一次)(很快将添加为make目标)
  2. make - 编译 dryad.so.1 并将其复制到 /tmp
  3. make run - 运行 ./dryad.so.1,这个应该能正常运行而不会段错误,如果它没有运行,请提交一个bug报告。
  4. test/test - 运行测试二进制文件 test,其 PT_INTERPRETER/tmp/dryad.so.1

编译和链接要求

Makefile 执行以下三项操作

  1. 将dryad编译成一个静态库,基本上是:cargo build -target=x86_64-unknown-linux-musl --lib
  2. libasm 的入口函数和运行时解析函数链接到dryad静态库,然后是rust标准库,以及pthread、libc等,并提供重要的链接器标志,如 -pie-Bsymbolic-I/tmp/dryad.so.1-soname dryad.so.1等。
  3. 将生成的二进制文件 dryad.so.1 复制到 /tmp/dryad.so.1,因为测试二进制文件中的 PT_INTERPRETER 就设置为这个路径。将来我们显然会将其放置在 /usr/lib/dryad.so.1 或动态链接器适当的路径上(GNU的叫做 ld-linux-x86-64.so.2)。

实际上,上面的阶段 12 是 cargo 管道中的问题,这也是为什么我仍然需要手动链接。另外,rustc 不喜欢将 musl 二进制文件编译成共享对象。

我相信,如果我将启动汇编代码转移到 Rust 源代码中的内联汇编中(从而可能消除步骤 1),一些问题就会消失,但 musl 问题可能仍然是一个问题。(我们现在在单独的 Rust crate 中使用内联汇编!)

运行

最后一步,运行 test/test(或 test 中的其他任何测试二进制文件),将输出大量信息,然后使您的机器段错误,或者可能根本不运行,或者真的做任何其他事情——我真的无法说,因为我到目前为止只在单台机器上进行了测试。

注意:如果您使用的是 Ubuntu 或其他未将 libc 放置于 /usr/lib 的 Linux 发行版,您需要将 LD_LIBRARY_PATH=/path/to/libc 传递给您的 test/test,即:LD_LIBRARY_PATH=/path/to/libc test/test。此外,如果 libc 没有指向实际二进制的 soname 符号链接,或者实际二进制 作为 soname 安装,则它也无法正常工作。我们还需要为它提供 ld.so.cache 读取器和解析器 - 随意工作吧!

然而,dryad 几乎能够解释使用 libc.so.6 的(简单的)二进制(如 test/test)。

具体来说,这意味着在高级上 dryad 执行以下操作

  1. 重新定位自身
  2. 加载并映射扁平依赖列表中的所有二进制文件
  3. 重新定位每个加载的二进制文件(技术上,重新定位最常见的重定位符号的子集)
  4. 为每个二进制文件设置 GOT,并使用其运行时符号解析函数(_dryad_resolve_symbol)和“rendezvous”数据结构
  5. 解析 GNU ifuncs,并且如果设置了 LD_BIND_NOW,则预绑定所有函数符号。
  6. 传递控制权给可执行文件
  7. (如果未设置 LD_BIND_NOW,则可选地延迟绑定函数调用)
  8. 段错误

很多 主要任务和 许多 次要任务需要完成,才能达到“完整”的水平。首要和最重要的任务是正确设置 TLS。目前,它通过简单地调用 musl 符号 __init_tls 来处理它,这样我们就不在 fs:0 访问及其类似操作上发生段错误。

但它确实需要正确设置,因为它是一个复杂的过程。

这是我在整个动态链接过程中遇到的最不详细的文档部分,所以进展缓慢。还有一些关于它将如何工作的疑问,我将在其他时候或在一篇博客文章中详细说明。

最后,dryad 应该 能够解释自身,您可以通过调用 ./dryad.so.1(是的,dryad 是自己的程序解释器)来验证这一点。

项目目标

1. 记录动态链接器

此项目的主要目标是完全记录

  1. GNU/Linux ELF x86-64 系统上的动态链接 过程
  2. 此类过程的一个实现

关于此主题的文档和信息目前处于 尴尬 状态,我一直在对材料的缺乏、文档等的缺失感到震惊。我曾开玩笑地说,我担心当所有老 C 程序员都死了会发生什么——但我不完全是开玩笑。

代码不是文档。如果它是,那么这个项目就会很容易,并且很久以前就完成了。

因此,我希望详细记录实现、过程,也许甚至我的经验。

我将在不久的将来更新这部分内容,请耐心等待。

2. 实现动态链接器

dryad 的当前目标实现是 ELF x86-64 GNU/Linux 系统。

请注意这一点

  1. ELF 加载器仅支持 64 位变体
  2. 汇编代码假定 x86-64 指令集
  3. 链接器目前针对Linux,尽管这个目标可能不会一成不变。

在开始其他架构或系统的工作之前/如果,我希望能有一个适用于ELF x86-64 GNU/Linux目标的实现。

话虽如此,特别是我对将Dryad移植到32位Linux系统上并不感兴趣,因为

  1. 在我看来,32位系统已经过时了
  2. 将大大增加源代码中的ELF目标复杂性,因为配置标志取决于我们在构建时想要切换的目标等。
  3. 32位ELF动态链接有更好的文档记录,我想要记录一个64位动态链接器

贡献

热情欢迎贡献!让我们为x86-64 GNU/Linux系统(以及更广泛的应用)构建一个生产级的动态链接器!或者不这么做也行。

如果你对x86-64 GNU系统上ELF的动态链接一无所知,那完全没问题,因为据我所知,没有人 anymore真的在做。如果你好奇,这里有一些随机资源

  1. ELF规范
  2. x86-64 System V应用程序二进制接口
  3. ELF TLS规范
  4. 谷歌的Bionic动态链接器源代码
  5. glibc动态链接器源代码
  6. musl dynlink.c代码
  7. Sco动态链接文档
  8. iecc动态链接文章
  9. ELF加载教程
  10. 关于GOT[0] - GOT[2]值的详细信息
  11. man ld-so用于动态链接基础知识
  12. man dlopen用于运行时动态链接基础知识
  13. man 3 getauxval关于内核传递给程序的辅助向量信息
  14. 我还希望能添加几篇文章,讲述我在本质上已经废弃的博客上的一些_mis_adventures

待办事项

以下是我脑海中的一些主要待办事项

  1. 重大:正确初始化动态链接器的TLS:这是最后的冲刺。
  2. 重大dlfcn.h实现和共享对象绑定以支持运行时动态加载
  3. 次要/etc/ld.so.cache加载程序和解析器
  4. 更好的文档
  5. 修复代码中的任何待办事项
  6. 使用Rust最佳实践使不安全代码更安全;绝对需要Rust专家!
  7. 添加配置文件
  8. 添加测试
  9. 实现动态链接而不产生段错误
  10. 做所有的事情

结语

始终记住

互相之间要优秀

依赖关系

~3MB
~65K SLoC