cdisselkoen/llvm-ir
GitHub: cdisselkoen/llvm-ir
一个将LLVM IR解析为类型安全Rust数据结构的库,专为程序分析和静态分析场景设计,解析后可在纯Rust环境中进行IR遍历和分析。
Stars: 678 | Forks: 61
# llvm-ir:自然 Rust 数据结构中的 LLVM IR
[](https://crates.io/crates/llvm-ir)
[](https://raw.githubusercontent.com/cdisselkoen/llvm-ir/main/LICENSE)
`llvm-ir` 旨在提供 LLVM IR 的 Rust 化表示。
其核心思想是 LLVM [`Instruction`] 不应是不透明的数据类型,而应是包含诸如 [`Add`]、[`Call`] 和 [`Store`] 等变体的 `enum`。
同样,[`BasicBlock`]、[`Function`] 和 [`Module`] 等类型应是包含尽可能多信息的 Rust 结构体。
与其他安全的 LLVM 绑定(如 [`inkwell`])不同,`llvm-ir` 不依赖于对 LLVM API 的持续 FFI。
它仅在初始解析步骤中使用 LLVM API,以提取构建其丰富的 LLVM IR 表示所需的所有数据。
一旦 `llvm-ir` 通过解析 LLVM 文件(使用优秀的 [`llvm-sys`] 低级 LLVM 绑定)创建了 [`Module`] 数据结构,它就会丢弃 LLVM FFI 对象并不再进行进一步的 FFI 调用。
这允许您在纯安全 Rust 中处理生成的 LLVM IR。
`llvm-ir` 旨在用于消费 LLVM IR,尚不一定用于生成 LLVM IR。
也就是说,它的目标是用于希望读取和分析 LLVM IR 的程序分析及相关应用。
未来,`llvm-ir` 或许能够将其 `Module` 输出回 LLVM 文件,甚至直接发送到 LLVM 库进行编译。
如果您对此感兴趣,欢迎贡献代码!
(或者在此期间,可以查看 [`inkwell`],它是用于生成 LLVM IR 的另一种安全接口。)
但如果您正在寻找一种良好的、面向读取的 LLVM IR 表示形式以便在纯 Rust 中工作,这正是 `llvm-ir` 目前所能提供的。
## 入门指南
此 crate 位于 [crates.io](https://crates.io/crates/llvm-ir),因此您可以直接将其作为依赖项添加到 `Cargo.toml` 中,并选择与您想要的 LLVM 版本对应的功能:
```
[dependencies]
llvm-ir = { version = "0.11.3", features = ["llvm-19"] }
```
目前支持的 LLVM 版本包括 `llvm-9`、`llvm-10`、`llvm-11`、`llvm-12`、`llvm-13`、`llvm-14`、`llvm-15`、`llvm-16`、`llvm-17`、`llvm-18` 和 `llvm-19`。
然后,入门最简单的方法是将一些现有的 LLVM IR 解析为此 crate 的数据结构。
为此,您需要 LLVM bitcode (`*.bc`) 或文本格式 IR (`*.ll`) 文件。
如果您目前有 C/C++ 源代码(例如 `source.c`),可以使用 `clang` 的 `-c` 和 `-emit-llvm` 标志生成 `*.bc` 文件:
```
clang -c -emit-llvm source.c -o source.bc
```
或者,要将 Rust 源代码编译为 LLVM bitcode,可以使用 `rustc` 的 `--emit=llvm-bc` 标志。
无论哪种情况,一旦您有了 bitcode 文件,就可以使用 `llvm-ir` 的 `Module::from_bc_path` 函数:
```
use llvm_ir::Module;
let module = Module::from_bc_path("path/to/my/file.bc")?;
```
或者如果您有文本格式的 IR 文件,可以使用 `Module::from_ir_path()`。
您可能还对 [`llvm-ir-analysis`] crate 感兴趣,它为 `llvm-ir` 函数计算控制流图、支配树等。
## 文档
`llvm-ir` 的文档可以在 [docs.rs](https://docs.rs/llvm-ir) 上找到,当然您也可以使用 `cargo doc --open` 生成本地文档。
文档在适当的时候包含了指向 LLVM 文档相关部分的链接。
请注意,某些数据结构会根据您选择的 LLVM 版本略有不同。docs.rs 文档是使用 `llvm-10` 功能生成的;对于其他 LLVM 版本,您可以使用 `cargo doc --features=llvm- --open` 获取相应的文档,其中 `` 是您使用的 LLVM 版本。
## 兼容性
从 `llvm-ir` 0.7.0 开始,LLVM 版本通过 Cargo feature 标志选择。这意味着单个 crate 版本可用于任何受支持的 LLVM 版本。目前,`llvm-ir` 支持 LLVM 版本 9 到 19,通过 feature 标志 `llvm-9` 到 `llvm-19` 选择。
您应该选择与您链接的 LLVM 库版本(即您系统上可用的版本)相对应的 LLVM 版本。
较新的 LLVM 应该能够读取由较旧 LLVM 生成的 bitcode,因此即使 bitcode 是由早于 LLVM 9 的 LLVM 生成的,您也应该能够使用此 crate 解析早于您通过 crate feature 选择的 LLVM 版本的 bitcode。但是,我们对此并未进行广泛测试。
`llvm-ir` 可在 Rust 稳定版上运行。截至撰写本文时,它需要 Rust 1.65+。
## 开发/调试
对于开发或调试,除了 `*.bc` 文件外,您可能还需要 LLVM 文本格式 (`*.ll`) 文件。
对于 C/C++ 源代码,您可以通过向 `clang` 传递 `-S -emit-llvm` 而不是 `-c -emit-llvm` 来生成这些文件。
例如,
```
clang -S -emit-llvm source.c -o source.ll
```
对于 Rust 源代码,您可以使用 `rustc` 的 `--emit=llvm-ir` 标志。
此外,在生成 bitcode 时,您可能希望向 `clang`、`clang++` 或 `rustc` 传递 `-g` 标志。
这将生成带有 debuginfo 的 LLVM bitcode,从而确保 [`Instruction`]、[`Terminator`]、[`GlobalVariable`] 和 [`Function`] 附加了有效的 [`DebugLoc`]。(参见 [`HasDebugLoc`] trait。)
另请注意,这些 `DebugLoc` 仅在 LLVM 9 及更高版本中可用;以前版本的 LLVM 在 C API 的此接口中存在错误,会导致段错误。
## 限制
LLVM IR 的某些功能尚未在 `llvm-ir` 的数据结构中表示。
最显著的是,`llvm-ir` 恢复了调试位置元数据(用于映射回源代码位置),但未尝试恢复任何其他调试元数据。
包含元数据的 LLVM 文件仍然可以正常解析,但生成的 `Module` 结构将不包含任何元数据,调试位置除外。
`llvm-ir` 的数据结构中缺少其他一些功能,因为 LLVM C API 和 Rust `llvm-sys` crate 中缺少针对它们的 getter,这些功能仅存在于 LLVM C++ API 中。
这些包括但不限于:
- 各种浮点运算上的 "fast-math flags"
- 内联汇编函数的内容
- 关于可变参数 `LandingPad` 指令中子句的信息
- 关于 `BlockAddress` 常量表达式操作数的信息
- 关于 `TargetExtType` 类型的信息
- 与函数关联的 ["prefix data"](https://releases.llvm.org/16.0.0/docs/LangRef.html#prefix-data)
- 大于 64 位(且不适合 64 位)的常量整数的值 —— 参见 [#5](https://github.com/cdisselkoen/llvm-ir/issues/5)
- 可从 `CallBr` 终结符访问的 "other labels"(在 LLVM 9 中引入)
- (LLVM 16 及更低版本 —— 在 LLVM 17 及更高版本中修复)`Add`、`Sub`、`Mul` 和 `Shl` 上的 `nsw` 和 `nuw` 标志,以及 `UDiv`、`SDiv`、`LShr` 和 `AShr` 上的 `exact` 标志。C API 具有创建指定这些标志值的新指令的功能,但不能查询现有指令上这些标志的值。
- (LLVM 9 及更低版本 —— 在 LLVM 10 及更高版本中修复)`AtomicRMW` 指令的操作码,即 `Xchg`、`Add`、`Max`、`Min` 等。
更多相关讨论请参见 [LLVM bug #42692](https://bugs.llvm.org/show_bug.cgi?id=42692)。
非常欢迎任何填补 C API 这些空白的贡献!
## 致谢
`llvm-ir` 最初的灵感来源于 [`llvm-hs-pure` Haskell package]。
`llvm-ir` 最初版本中的大多数数据结构本质上是 `llvm-hs-pure` 中数据结构从 Haskell 到 Rust 的翻译(经过一些调整)。
## 0.7.0 版本更新日志
`llvm-ir` 0.7.0 包含与以前版本相比的几项相当重大的更改,概述如下。
- LLVM 版本现在通过 Cargo 功能选择。您必须准确选择 `llvm-8`、`llvm-9` 或 `llvm-10` 功能之一。以前,我们有用于 LLVM 10 的 `0.6.x` 分支、用于 LLVM 9 的 `0.5.x` 分支,并且不正式支持 LLVM 8。现在,单个版本支持 LLVM 8、9 和 10。
- (注:0.7.0 之后的此 crate 版本也增加了对更高 LLVM 版本的支持。例如,0.7.3 及更高版本也支持 LLVM 11;0.7.5 及更高版本也支持 LLVM 12。Crate 版本 0.11.0 移除了对 LLVM 8 的支持。)
- [`FunctionAttribute`] 和 [`ParameterAttribute`] 现在是具有描述性变体(如 `NoInline`、`StackProtect` 等)的正规枚举。以前,属性是不透明的数字代码,难以解释。
- 一些旨在提高运行时性能,尤其是内存消耗的更改,特别是在解析大型 LLVM 模块时。这涉及对公共接口的许多破坏性更改:
- 大多数 [`Type`] 的用户现在拥有 [`TypeRef`] 而不是直接拥有 `Type`。这包括 `Operand::LocalOperand`、`GlobalVariable`、`Instruction` 的许多变体、`Constant` 的许多变体以及 `Type` 本身的一些变体等。请参阅关于 [`TypeRef`] 的文档。
- 类似地,大多数 [`Constant`] 的用户现在拥有 [`ConstantRef`] 而不是直接拥有 `Constant`。请参阅关于 [`ConstantRef`] 的文档。
- 要获取 [`Typed`] 对象的类型,提供的 `.get_type()` 方法现在需要一个额外的参数;大多数用户可能更喜欢 [`module.type_of()`](或 `module.types.type_of()`)。
- `Type::NamedStructType` 不再带有对其内部类型的弱引用;相反,您可以使用 [`module.types.named_struct_def()`] 查找名称以获取模块中任何命名结构体类型的定义。
- 所需的 Rust 版本从 1.36+ 提高到了 1.39+。
- (注:0.7.0 之后的此 crate 版本进一步提高此要求。有关当前的所需 Rust 版本,请参阅上面的“兼容性”部分。)
标签:LLVM, LLVM IR, Rust, TLS抓取, 中间表示, 二进制分析, 云安全监控, 云安全运维, 云资产清单, 代码分析, 凭证管理, 可视化界面, 可配置连接, 安全Rust, 开发库, 数据结构, 无FFI, 浏览器安全, 程序分析, 编程工具, 编译器, 网络流量审计, 解析器, 软件安全, 远程代码执行, 逆向工程, 静态分析