如何将 Rust 编译为 LLVM 位代码(包括依赖项)?

问题描述 投票:0回答:2

我正在使用 SAW 验证一些 Rust 代码。 SAW 要求您编译为LLVM 位代码,然后您可以导入并验证。我知道您可以使用 rustc 的

--emit=llvm-bc
标志生成位码,这对于没有依赖项的项目非常有用。

尝试编译使用外部板条箱的项目时会出现问题。这是一个 Cargo.toml 文件示例:

[package]
name = "foobar"
version = "0.1.0"
edition = "2018"

[dependencies]
pythagoras = "0.1.1"

这是一个基本的

src/lib.rs
,我们可能想要编译和验证:

pub use pythagoras;

#[no_mangle]
pub extern "C" fn calc_hypot(a: u32, b: u32) -> f64 {
    pythagoras::theorem(a, b)
}

我们可以将其编译为如下位码:

RUSTFLAGS="--emit=llvm-bc" cargo build --release
。问题是当前模块及其依赖项的位码是单独生成的(在
target/release/deps/foobar-something.bc
target/release/deps/pythagoras-somethingelse.bc
中)。它们仅在生成实际编译库时才会组合。

有没有办法生成一个包含当前模块及其所有依赖项的单个位码文件,以便可以导入该文件,并且不会引用任何外部名称?我意识到这是一个相当小众的案例,因此 hacky 解决方案(例如:编译为 C 静态库,然后以某种方式将其转换回 LLVM 位代码)也是完全合理的。

rust compilation llvm llvm-ir
2个回答
6
投票

扩展Aiden4的评论:

  • 删除当前目标目录以防止使用任何旧工件:
    rm -r target/
  • 使用
    RUSTFLAGS="--emit=llvm-bc" cargo build --release
  • 进行编译
  • 使用
    llvm-link target/release/deps/*.bc > withdeps.bc
  • 将位码文件链接在一起

这将使您获得几乎所有依赖项。事实证明,所有 Rust 程序都隐式依赖于

core
std
(虽然你可以使用不稳定的
#![no_core]
来避免这种情况,但祝你好运,真正以这种方式编译任何东西),所以你可能想要也获取它的位码。

最简单的方法是将标准库从源代码编译为位代码。

cargo
对从源构建标准库具有实验性支持,因此只需将
-Z build-std --target x86_64-unknown-linux-gnu
(并根据需要更新目标)附加到
cargo
构建命令即可。当使用
--target
时(这是
-Z build-std
所必需的),构建文件将放置在特定于目标的目录中,在本例中为
target/x86_64-unknown-linux-gnu/release/deps/
。 targetless 目录包含标准库的构建依赖项:我们不希望这样!

我们不想链接所有标准库。我们实际上只需要

std
及其依赖项:这里不需要
proc_macro
,因为我们正在编译为二进制文件,而不是过程宏。我们还需要链接
proc_abort
panic_unwind
,将其与我们选择的展开代码生成设置相匹配。默认是展开,所以让我们删除另一个,
proc_abort
。让我们将这些库发送到砧板:
rm target/x86_64-unknown-linux-gnu/release/deps/{panic_abort,proc_macro}-*.bc

这次让我们尝试真正的链接:

rm -r target/
RUSTFLAGS="--emit=llvm-bc" cargo build --release -Z build-std --target x86_64-unknown-linux-gnu
rm target/x86_64-unknown-linux-gnu/release/deps/{panic_abort,proc_macro}-*.bc
llvm-link target/x86_64-unknown-linux-gnu/release/deps/*.bc > withalldeps.bc

耶,成功了!好吧,除了对未定义函数的调用仍然设法溜过去。

__rust_alloc
__rust_dealloc
__rust_realloc
__rust_alloc_zeroed
是在使用 Rust 的 LLVM 分支时定义的神奇函数。标准库还依赖于
libpthread
dlsym
,它们是通常用 C 实现的与语言无关的库/函数。您可以使用
clang
和支持使用 Clang (GNU libc 没有,我认为 musl 可能在这里工作?)如果需要的话可以得到它。此外,如果您要编译为可执行文件,则很难从
libc
中找到
main
    


0
投票
链接

学会了如何解决

_start
__rust_alloc
__rust_dealloc
__rust_realloc
我测试了它,它有效。

这是我用来编译 Rust 程序的命令

__rust_alloc_zeroed

然后,在您的
RUSTFLAGS="-C save-temps -Zlocation-detail=none -Zfmt-debug=none --emit=llvm-bc" \ cargo +nightly build -Z build-std=std,panic_abort \ -Z build-std-features="optimize_for_size" \ --target x86_64-unknown-linux-gnu

中,您可以找到编译成位码的文件。就我而言,重要的文件名为

target/x86_64-unknown-linux-gnu/debug/deps
。如果你运行
function-*.rcgu.bc
,你会发现这样的符号:
llvm-nm function-*.rcgu.bc

这意味着这个文件包含了我们需要的东西。

所以当你运行

U __rdl_alloc U __rdl_alloc_zeroed U __rdl_dealloc U __rdl_realloc U __rg_oom ---------------- T __rust_alloc ---------------- T __rust_alloc_error_handler ---------------- D __rust_alloc_error_handler_should_panic ---------------- T __rust_alloc_zeroed ---------------- T __rust_dealloc ---------------- D __rust_no_alloc_shim_is_unstable ---------------- T __rust_realloc

时,你还应该包括这个

llvm-link *.bc -o withdeps.bc
    

© www.soinside.com 2019 - 2024. All rights reserved.