本文介绍一个使用Rust实现的ELF加载器。
下面是elf_loader的仓库链接:
github:
https://github.com/weizhiao/elf_loaderhttps://github.com/weizhiao/elf_loader
crates.io:
https://crates.io/crates/elf_loaderhttps://crates.io/crates/elf_loader
1 它能做什么?
elf_loader能够从内存、文件加载各种形式的elf文件,包括Executable file、Shared object file和Position-Independent Executable file。
PS:后面这两个其实是一类格式,Position-Independent Executable file其实就是带有入口的Shared object file,即可以执行的Shared object file。
鉴于此,它能够被使用在以下地方:
1 在操作系统内核中使用它作为elf文件的加载器
2 使用它实现Rust版本的动态链接器。PS:我自己也实现了一个https://github.com/weizhiao/dlopen-rs
3 在嵌入式设备上使用它加载elf动态库
2 优势
✨ 可以在 no_std环境中工作✨
elf_loader不依赖Rust std,也不强制依赖libc和操作系统,因此可以在内核和嵌入式设备等no_std环境中使用。
✨速度快✨
elf_loader吸取了musl和glibc里ld.so实现的优点,并充分利用了Rust的一些特性(比如静态分发),可以生成性能出色的代码。基于elf_loader实现的动态链接器dlopen-rs性能比libloading(ld.so)更好。
✨非常容易移植,具有良好的可扩展性✨
如果你想要移植elf_loader,你只需为你的平台实现 Mmap和ElfObject trait。在实现Mmap trait时可以参考elf_loader提供的默认实现:
elf_loader/src/mmap at main · weizhiao/elf_loader
此外你可以使用elf_loader提供的hook函数来拓展elf_loader的功能,实现其他任何你想要的功能,在使用hook函数时可以参考dlopen-rs里的实现:https://github.com/weizhiao/dlopen-rs/blob/main/src/loader/mod.rs
✨提供异步接口✨
elf_loader提供了加载elf文件的异步接口,这使得它在某些并发加载elf文件的场景下有更高的性能上限。不过你需要根据自己的应用场景实现 Mmap和ElfObjectAsync trait。比如不使用mmap来直接映射elf文件,转而使用mmap+文件读取的方式(mmap创建内存空间再通过文件读取将elf文件的内容读取到mmap创建的空间中)来加载elf文件,这样就能充分利用异步接口带来的优势。
✨编译期检查✨
elf_loader利用Rust的生命周期机制,在编译期检查elf文件的依赖库是否被提前销毁,大大提高了安全性。 比如说有三个被elf_loader加载的动态库a,b,c,其中c依赖b,b依赖a,如果a,b中的任意一个在c drop之前被drop了,那么将不会程序通过编译。你可以在examples/relocate中验证这一点:elf_loader/examples/relocate.rs at main · weizhiao/elf_loader · GitHub
3 示例
加载一个简单的动态库:
use elf_loader::{Loader, mmap::MmapImpl, object::ElfFile};
use elf_loader::{Loader, mmap::MmapImpl, object::ElfFile};
use std::{collections::HashMap, ptr::null};
fn main() {
fn print(s: &str) {
println!("{}", s);
}
// liba.so依赖的符号
let mut map = HashMap::new();
map.insert("__gmon_start__", null());
map.insert("__cxa_finalize", null());
map.insert("_ITM_registerTMCloneTable", null());
map.insert("_ITM_deregisterTMCloneTable", null());
map.insert("print", print as _);
let pre_find = |name: &str| -> Option<*const ()> { map.get(name).copied() };
// 加载动态库liba.so
let mut loader = Loader::<MmapImpl>::new();
let liba = loader
.easy_load_dylib(ElfFile::from_path("target/liba.so").unwrap())
.unwrap();
// 重定位liba.so中的符号
let a = liba.easy_relocate([].iter(), &pre_find).unwrap();
// 调用liba.so中的函数a
let f = unsafe { a.get::<fn() -> i32>("a").unwrap() };
f();
}
4 补充
如果你在使用时遇到任何问题,都可以在本文的评论区或者github上提出问题,此外十分欢迎任何对elf加载器感兴趣的朋友贡献代码(改进elf_loader本身,增加样例,修改文档中存在的问题都可以)。如果觉得elf_loader对你有帮助的话不妨点个star。^v^