An almost-parallel, semi-functioning, dynamic linker experiment, written in Rust
Rust
173
222 commits
updated Feb 7, 2018

dryad is the first and only parallel, 64-bit ELF dynamic linker for GNU/Linux, written from scratch in Rust, and is:
but all most of these things will disappear in time!
Work has stalled on this for a number of reasons, primarily as outlined here, but I tinker with it from now and then.
I have some ideas to fix things, but so many things to work on!
If you want to contribute, PRs or suggestions, comments, issues, always welcome :) If you want to hack on some other fun binary stuff, goblin or cargo-sym could always use an extra hand or two.
You need to install rustup tool, and then switch to nightly and add the musl target:
rustup default nightly
rustup target add x86_64-unknown-linux-musl
All you really need now is rustup, an internet connection, and a linker for the target you're linking:
ld (or ld.gold)curlUnfortunately, I currently do not support cross compiling at the moment (which is an unusual use case anyway), so you will need an x86-64 GNU/Linux machine, otherwise it will fail.
Once that's settled you can then proceed as normal:
./gen_tests.sh - builds the test binaries (do this once) (will add this as a make target soon)make - compiles dryad.so.1 and copies it to /tmpmake run - runs ./dryad.so.1, this should run correctly without segfaulting, please file a bug if it does not.test/test - runs the test binary test, whose PT_INTERPRETER is /tmp/dryad.so.1The Makefile does three things:
cargo build -target=x86_64-unknown-linux-musl --liblibasm entry function and runtime resolver functions with the dryad static library, and then the rust standard libs, and pthreads and libc and etc., and provides the very important linker flags such as -pie, -Bsymbolic, -I/tmp/dryad.so.1, -soname dryad.so.1, etc.dryad.so.1, into /tmp/dryad.so.1 because that's what PT_INTERPRETER is set to in the test binaries. In the future we'll obviously make this /usr/lib/dryad.so.1, or wherever the appropriate place for the dynamic linker is (GNU's is called ld-linux-x86-64.so.2 btw).Really, stage 1 and 2 from above is the problem in the cargo pipeline, which is why I still need to manually link. Additionally, rustc doesn't like to compile a musl binary as a shared object.
I believe some of these issues will go away if I transfer the start assembly into inline assembly in Rust source code (thereby potentially eliminating step 1), but the musl issue could be a problem. (we use inline asm in separate rust crate now!)
The last step, running test/test (or any of the other test binaries in test), will output a ton of information and then segfault your machine, or perhaps not run at all, or really do any number of things --- I really can't say, since I've only tested on a single machine so far.
NOTE: if you're on Ubuntu or another linux distro which doesn't place libc in /usr/lib, you'll need to pass LD_LIBRARY_PATH=/path/to/libc to your test/test, i.e.: LD_LIBRARY_PATH=/path/to/libc test/test. Furthermore, if libc doesn't have symbolic links for the soname pointing to the actual binary, or the actual binary is installed as the soname, then it also won't work. We need ld.so.cache reader and parser for this - feel free to work on it!
However, dryad is almost capable of interpreting a (simple) binary (like test/test) which uses libc.so.6.
Specifically, this means is that dryad at a high level does the following:
mmap's all binaries in the flattened dependency list_dryad_resolve_symbol), and its "rendezvous" data structureLD_BIND_NOW is set, prebinds all function symbols.LD_BIND_NOW is not set) lazily binds function callsThere are several major, and many minor tasks that need to be finished to be even remotely "complete". The first and most major one is properly setting up TLS. Currently, it hacks it about by just calling the musl symbol __init_tls so we don't segfault on fs:0 accesses and their ilk.
But it really needs to be properly setup, as it's a delicate procedure.
This is easily the least documented part of the entire dynamic linking process I have come across, so work is slow going. Also there are some questions about how this will work exactly, which I'll detail at some other time, or in a blog post.
Lastly, dryad should be capable of interpreting itself, which you can verify by invoking ./dryad.so.1 (yes, dryad is it's own program interpreter).
The primary goal of this project is to completely document:
The current state of documentation and information on this subject is an embarassment, and I'm continually appalled at the lack of materials, documentation, etc. I've jokingly told people I'm worried what will happen when all the old C programmers die - but I'm not really joking.
Code is not documentation. If it were, then this project would have been easy and finished some time ago.
As such, I hope to thoroughly document the implementation, the process, and maybe even my experiences.
I will be updating this section with more content shortly, please bear with me.
The current target implementation for dryad is an ELF x86-64 GNU/Linux system.
This is important to note:
I would like to have a working implementation for an ELF x86-64 GNU/Linux target before/if beginning work on other architectures or systems.
That being said, in particular I'm not very interested in porting dryad to work on 32-bit Linux systems, because:
Contributions wholeheartedly welcome! Let's build a production dynamic linker in Rust for use in x86-64 GNU/Linux systems (and beyond)! Or not, that's cool too.
If you don't know anything about dynamic linking on x86-64 GNU systems for ELF, that's totally OK, because as far as I can tell, no one really does anymore. Here are some random resources if you're curious:
man ld-so for dynamic linking basicsman dlopen for runtime dynamic linking basicsman 3 getauxval for information on the auxiliary vector passed by the kernel to programsHere are some major todos off the top of my head
dlfcn.h implementation and shared object bindings for runtime dynamic loading support/etc/ld.so.cache loader and parserAlways remember:
Be excellent to each other
Rust
91.9%
Makefile
4.7%
Assembly
2.3%
An almost-parallel, semi-functioning, dynamic linker experiment, written in Rust
Rust
173
222 commits
updated Feb 7, 2018

dryad is the first and only parallel, 64-bit ELF dynamic linker for GNU/Linux, written from scratch in Rust, and is:
but all most of these things will disappear in time!
Work has stalled on this for a number of reasons, primarily as outlined here, but I tinker with it from now and then.
I have some ideas to fix things, but so many things to work on!
If you want to contribute, PRs or suggestions, comments, issues, always welcome :) If you want to hack on some other fun binary stuff, goblin or cargo-sym could always use an extra hand or two.
You need to install rustup tool, and then switch to nightly and add the musl target:
rustup default nightly
rustup target add x86_64-unknown-linux-musl
All you really need now is rustup, an internet connection, and a linker for the target you're linking:
ld (or ld.gold)curlUnfortunately, I currently do not support cross compiling at the moment (which is an unusual use case anyway), so you will need an x86-64 GNU/Linux machine, otherwise it will fail.
Once that's settled you can then proceed as normal:
./gen_tests.sh - builds the test binaries (do this once) (will add this as a make target soon)make - compiles dryad.so.1 and copies it to /tmpmake run - runs ./dryad.so.1, this should run correctly without segfaulting, please file a bug if it does not.test/test - runs the test binary test, whose PT_INTERPRETER is /tmp/dryad.so.1The Makefile does three things:
cargo build -target=x86_64-unknown-linux-musl --liblibasm entry function and runtime resolver functions with the dryad static library, and then the rust standard libs, and pthreads and libc and etc., and provides the very important linker flags such as -pie, -Bsymbolic, -I/tmp/dryad.so.1, -soname dryad.so.1, etc.dryad.so.1, into /tmp/dryad.so.1 because that's what PT_INTERPRETER is set to in the test binaries. In the future we'll obviously make this /usr/lib/dryad.so.1, or wherever the appropriate place for the dynamic linker is (GNU's is called ld-linux-x86-64.so.2 btw).Really, stage 1 and 2 from above is the problem in the cargo pipeline, which is why I still need to manually link. Additionally, rustc doesn't like to compile a musl binary as a shared object.
I believe some of these issues will go away if I transfer the start assembly into inline assembly in Rust source code (thereby potentially eliminating step 1), but the musl issue could be a problem. (we use inline asm in separate rust crate now!)
The last step, running test/test (or any of the other test binaries in test), will output a ton of information and then segfault your machine, or perhaps not run at all, or really do any number of things --- I really can't say, since I've only tested on a single machine so far.
NOTE: if you're on Ubuntu or another linux distro which doesn't place libc in /usr/lib, you'll need to pass LD_LIBRARY_PATH=/path/to/libc to your test/test, i.e.: LD_LIBRARY_PATH=/path/to/libc test/test. Furthermore, if libc doesn't have symbolic links for the soname pointing to the actual binary, or the actual binary is installed as the soname, then it also won't work. We need ld.so.cache reader and parser for this - feel free to work on it!
However, dryad is almost capable of interpreting a (simple) binary (like test/test) which uses libc.so.6.
Specifically, this means is that dryad at a high level does the following:
mmap's all binaries in the flattened dependency list_dryad_resolve_symbol), and its "rendezvous" data structureLD_BIND_NOW is set, prebinds all function symbols.LD_BIND_NOW is not set) lazily binds function callsThere are several major, and many minor tasks that need to be finished to be even remotely "complete". The first and most major one is properly setting up TLS. Currently, it hacks it about by just calling the musl symbol __init_tls so we don't segfault on fs:0 accesses and their ilk.
But it really needs to be properly setup, as it's a delicate procedure.
This is easily the least documented part of the entire dynamic linking process I have come across, so work is slow going. Also there are some questions about how this will work exactly, which I'll detail at some other time, or in a blog post.
Lastly, dryad should be capable of interpreting itself, which you can verify by invoking ./dryad.so.1 (yes, dryad is it's own program interpreter).
The primary goal of this project is to completely document:
The current state of documentation and information on this subject is an embarassment, and I'm continually appalled at the lack of materials, documentation, etc. I've jokingly told people I'm worried what will happen when all the old C programmers die - but I'm not really joking.
Code is not documentation. If it were, then this project would have been easy and finished some time ago.
As such, I hope to thoroughly document the implementation, the process, and maybe even my experiences.
I will be updating this section with more content shortly, please bear with me.
The current target implementation for dryad is an ELF x86-64 GNU/Linux system.
This is important to note:
I would like to have a working implementation for an ELF x86-64 GNU/Linux target before/if beginning work on other architectures or systems.
That being said, in particular I'm not very interested in porting dryad to work on 32-bit Linux systems, because:
Contributions wholeheartedly welcome! Let's build a production dynamic linker in Rust for use in x86-64 GNU/Linux systems (and beyond)! Or not, that's cool too.
If you don't know anything about dynamic linking on x86-64 GNU systems for ELF, that's totally OK, because as far as I can tell, no one really does anymore. Here are some random resources if you're curious:
man ld-so for dynamic linking basicsman dlopen for runtime dynamic linking basicsman 3 getauxval for information on the auxiliary vector passed by the kernel to programsHere are some major todos off the top of my head
dlfcn.h implementation and shared object bindings for runtime dynamic loading support/etc/ld.so.cache loader and parserAlways remember:
Be excellent to each other
Rust
91.9%
Makefile
4.7%
Assembly
2.3%