2 个版本
使用旧的 Rust 2015
0.0.5 | 2018 年 9 月 25 日 |
---|---|
0.0.3 | 2017 年 6 月 9 日 |
0.0.2 |
|
0.0.1 |
|
#127 in 无标准库
230KB
5K SLoC
predicator
predicator 是一个 Rust 包,允许您编写可以加载和卸载、并在运行时优化的插件,支持 Rust 和 LLVM JIT 支持的任何其他语言。它非常适合过滤器、安全检查和存在于长期进程中的 10 秒到 100 秒的短期代码片段。
它是线程安全的,但插件目前仅限于 #[no_std]
包。长期计划是添加对 cargo 的支持,并尝试符号解析器。
静态链接程序可以工作,但如果它们使用第三方库(即使是 libc),则需要特别编译以包含这些定义。
迫不及待想要试试吗?
查看 src/main.rs
。这是创建和加载插件的全部所需。创建可以是在单独的进程中,甚至是在另一台机器上。
使用 cargo run
在您的机器上演示其工作。您需要 Rust nightly。截至 2017 年 6 月 9 日已测试于 Mac OS X。
编写插件
设置 Cargo.toml
为了生成合适的插件,需要仔细设置 Cargo.toml
中的值。
首先,确保 [profile]
部分包含以下两个条目:-
[profile.dev]
opt-level = 3
debug = true
rpath = false
lto = true
debug-assertions = true
codegen-units = 1
panic = 'abort'
[profile.release]
opt-level = 3
debug = false
rpath = false
lto = true
debug-assertions = false
codegen-units = 1
panic = 'abort'
lto = true
的设置尤为重要。而 panic = abort
的设置并非必需,但由于无法在 LLVM JIT 插件中将 panic 返回 Rust,这使得开发变得稍微容易一些,因为 panic 在开发和调试测试期间会安全地终止。
其次,添加或替换 [lib]
部分,使其与以下内容完全匹配:
crate-type = ["bin"]
编写 LLVM JIT 插件的代码
一个 LLVM JIT 插件需要一些样板代码才能运行。将此代码放在 lib.rs
的 顶部::
#![no_std]
#![feature(lang_items)]
#![feature(libc)]
#![no_main]
extern crate libc;
#[lang = "panic_fmt"]
fn panic_fmt() -> !
{
loop
{
}
}
#[lang = "eh_personality"]
extern fn eh_personality()
{
}
#[allow(unused_variables)]
#[cfg(not(debug_assertions))]
#[no_mangle]
pub extern fn main(argc: isize, argv: *const *const u8) -> isize
{
0
}
#[cfg(debug_assertions)]
#[no_mangle]
pub extern fn main(argc: isize, argv: *const *const u8) -> isize
{
test(argc, argv)
}
/// Change this code to run unit tests when compiled in `debug` mode
/// Return a non-zero value to mark a test failure
fn test(argc: isize, argv: *const *const u8) -> isize
{
0
}
此代码执行以下操作:
- 告诉 Rust 编译器只使用
core
包(也称为libcore
)- 这意味着需要(堆)内存分配的任何内容都不可用
- 没有集合(例如
Vec
、HashSet
等) - 没有
Box
、Rc
或Arc
- 没有字符串(例如
String
、str
、CString
、CStr
、OsString
和OsStr
) - 没有路径(例如
Path
、PathBuf
) - 以及其他类似的限制
- 没有集合(例如
- 此外,panic 的代码并不特别有用
- 这意味着需要(堆)内存分配的任何内容都不可用
- 告诉 Rust 编译器我们将提供
core
包中缺少的语言项函数 - 告诉 Rust 编译器我们将使用其工具链的
libc
包版本(在高级场景中并非必需) - 告诉 Rust 编译器不要生成用于支持 Rust 的
main()
函数的常规逻辑,因为插件不需要这些 - 使用
libc
包;如果没有它,插件将无法编译,因为它依赖于如crt1.o
这样的 libc 启动文件 - 添加了一个定义
panic_fmt()
的代码,该代码会无限循环;这不是最佳做法(更好的解决方案可能是将内容写入 stderr 或 syslog) - 添加了一段用于异常处理的代码,它实际上什么也不做
- 定义了一个适用于从 libc 调用的
main()
方法,该方法在发布模式下使用时不会做任何事情。这是为了能够编译代码 - 定义了一个将 C 和
test()
方法桥接的方法 - 定义了一个存根
test()
函数,您可以用测试代码的逻辑替换它
现在您已经准备好开始了。只需编写正常函数和代码。为了使函数可用,它需要是 pub
和 #[no_mangle]
。
例如,一个非常简单的函数可能如下所示:
#[no_mangle]
pub fn simple_plugin()
{
}
然后可以通过查找函数 simple_plugin
来将其用作 LLVM JIT 插件:
extern crate predicator;
use ::predicator::llvm::*;
fn main()
{
// Create a super context
let super_context = SuperContext::threadLocal();
// There needs to be at least one context per thread
let jit_context = super_context.newJitContext(NaiveSymbolResolver(0)).expect("Could not create a new JIT context");
// Can also be created from a slice, and from intermediate representation (.ll files)
let plugins = jit_context.loadPlugins(ModuleSourceCodeType::BitCode, &MemoryBufferCreator::File("/path/to/bitcode/file.bc")).expect("Could not parse bit code into module");
// Note that there is no way to know the correct arity or arguments for the function pointer
let simple_plugin_function_pointer = plugins.nullaryFunctionPointer::<()>("simple_plugin").expect("Missing function for simple_plugin");
// Execute the function
unsafe { simple_plugin_function_pointer() };
// Note that once `plugins` is dropped the function pointer is no longer valid
}
函数也可以使用在 core
包中定义的任何内容。请注意,在使用 libc
包中的内容时要小心,因为 LLVM JIT 插件可以与静态链接代码一起使用,因此 libc 函数和全局静态变量可能没有被链接到使用您的插件的应用程序中。在直接运行插件进行测试时,此注意事项不适用。
函数可以接受参数并返回结果,例如这个插件:
pub fn binary_plugin(size: usize, some_array: [u8; 2]) -> u32
{
17
}
然后可以这样使用:
let binary_plugin_function_pointer = orcJitStack.binaryFunctionPointer::<u32, usize, [u8; 64]>("binary_plugin").expect("Missing function for binary_plugin");
// Execute the function
let size = 20;
let some_array = [3, 9];
let result = unsafe { binary_plugin_function_pointer(size, some_array) };
assert!(result == 17, "result wasn't 17");
请注意,您正在跨越相当于 'C' 界限的情况。预测框架无法阻止您不捕获 panic!、传递 Box
值、类型不匹配等。建议您坚持使用非常简单的结构和原始类型。更复杂的结构也可以工作,但一旦出现问题就非常难以调试。您可以使用来自其他 no_std
包的类型,但应避免从它们导入全局(或线程局部)静态变量。在插件代码中定义全局静态和线程局部静态变量应该是没有问题的。
构建
要构建 LLVM JIT 插件,请使用:
cargo rustc --release --target x86_64-unknown-linux-musl -- --emit=llvm-bc
注意:我们需要检查是否需要 -C relocation-model=static
注意:如果您的 Cargo.toml
配置正确,则实际上可能不需要使用 -C lto
开关。
您可以在 cargo
定义的任何目标文件夹中找到输出。如果您已经分叉了这个包,您可以在 .cargo/target/x86_64-unknown-linux-musl/release/deps/NAME_OF_CRATE-HEX_RANDOM_VALUE.bc
中找到它,其中 NAME_OF_CRATE
是您的包名,RANDOM_VALUE
是 -C metadata=RANDOM_VALUE -C extra-filename=-RANDOM_VALUE
cargo 在构建过程中传递的随机值。例如路径可能为:.cargo/target/x86_64-unknown-linux-musl/release/deps/experiment_with_ffi-805d16cbb3e10aad.bc`。当 cargo 构建时,会产生包含此路径提示的构建输出:
Compiling experiment-with-ffi v0.0.0 (file:///Volumes/Source/GitHub/lemonrock/experiment-with-ffi)
Running `rustc --crate-name experiment_with_ffi src/lib.rs --crate-type bin --emit=dep-info,link -C opt-level=3 -C panic=abort -C lto --emit=llvm-bc -C metadata=24221fe0742db2e8 -C extra-filename=-24221fe0742db2e8 --out-dir /Volumes/Source/GitHub/lemonrock/experiment-with-ffi/.cargo/target/x86_64-unknown-linux-musl/release/deps --target x86_64-unknown-linux-musl -C ar=x86_64-linux-musl-ar -C linker=x86_64-linux-musl-cc -L dependency=/Volumes/Source/GitHub/lemonrock/experiment-with-ffi/.cargo/target/x86_64-unknown-linux-musl/release/deps -L dependency=/Volumes/Source/GitHub/lemonrock/experiment-with-ffi/.cargo/target/release/deps`
Finished release [optimized] target(s) in 0.25 secs
在这种情况下,NAME_OF_CRATE
是 experiment-with-ffi
,而 RANDOM_VALUE
是 24221fe0742db2e8
,如上所述,作为 -C metadata=24221fe0742db2e8 -C extra-filename=-24221fe0742db2e8
替代构建方法
调试模式
要使用调试模式构建,请使用:
cargo rustc --target x86_64-unknown-linux-musl -- --emit=llvm-bc
要检查插件创建的 LLVM IR 代码,请使用:
要检查生成的 LLVM IR,请使用:
cargo rustc --release --target x86_64-unknown-linux-musl -- --emit=llvm-ir
此代码在调试模式下没有构建,因为它变得非常难以阅读。如果您想以调试模式构建它,请在上面的命令中省略 --release
开关。
移除发布模式代码中的冗余信息
默认情况下,由 Rust 编译器生成的 LLVM IR 和位代码,即使在 --release
下生成,也包含一些额外的调试信息。这可以使用 opt
程序移除。
要从IR代码(.ll
文件)中删除它,请执行以下操作:
opt -strip-debug -S -o .cargo/target/x86_64-unknown-linux-musl/release/deps/NAME_OF_CRATE-RANDOM_VALUE.stripped.ll .cargo/target/x86_64-unknown-linux-musl/release/deps/NAME_OF_CRATE-RANDOM_VALUE.ll
以及从位代码(.bc
文件)中:
opt -strip-debug -o .cargo/target/x86_64-unknown-linux-musl/release/deps/NAME_OF_CRATE-RANDOM_VALUE.stripped.bc .cargo/target/x86_64-unknown-linux-musl/release/deps/NAME_OF_CRATE-RANDOM_VALUE.bc
注意缺少-S
开关。
有趣的是,生成的.ll
代码有时可能比.bc
代码更小。我观察到高达24%。
感谢
感谢https://github.com/jauhien/iron-kaleidoscope#chapter-3-optimizer-and-jit-support教程的帮助
许可
本项目的许可协议为AFGPL-3.0。
依赖项
~3MB
~52K SLoC