rust-lang/miri
GitHub: rust-lang/miri
Miri 是 Rust 的未定义行为检测工具,通过解释执行中间表示来发现内存安全、类型违规、别名冲突和数据竞争等问题。
Stars: 6280 | Forks: 482
# Miri
Miri 是一个用于 Rust 的[未定义行为][reference-ub]检测工具。它可以运行 cargo 项目的二进制文件和测试套件,并检测未能维持其安全性要求的非安全代码。例如:
* 越界内存访问和释放后使用
* 未初始化数据的无效使用
* 违反内建函数前置条件(执行到了 [`unreachable_unchecked`],使用重叠范围调用 [`copy_nonoverlapping`] 等)
* 未充分对齐的内存访问和引用
* 违反基本类型不变量(例如,不是 0 或 1 的 `bool`,或者无效的枚举判别值)
* 数据竞争和*部分*弱内存效果的模拟,即,原子读取可能会返回过时的值
* **实验性**:违反管理引用类型别名的 [Stacked Borrows] 规则
* **实验性**:违反 [Tree Borrows] 别名规则,作为 [Stacked Borrows] 的可选替代方案
除此之外,Miri 还会告诉你关于内存泄漏的信息:当在执行结束时仍有内存被分配,并且该内存无法从全局 `static` 变量中访问到时,Miri 将引发错误。
你可以使用 Miri 在其他目标平台上模拟程序,例如,以确保字节级的数据操作在小端和大端系统上都能正常工作。请参阅下面的[交叉解释](#cross-interpretation-running-for-different-targets)。
Miri 已经发现了许多[真实世界的 bug](#bugs-found-by-miri)。如果你使用 Miri 发现了 bug,我们希望你能告诉我们,我们会将其添加到列表中!
默认情况下,Miri 通过将程序与宿主系统隔离来确保完全确定性的执行。一些通常会访问宿主的 API,例如为随机数生成器收集熵、环境变量和时钟,被确定性的“虚假”实现所取代。设置 `MIRIFLAGS="-Zmiri-disable-isolation"` 可以改为访问真实的系统 API。(请注意,这种隔离*不是*一个适当的沙箱,隔离中的漏洞被视为普通 bug 而不是安全漏洞。此外,“虚假”的系统 RNG API 使得 Miri **不适合加密用途**!请勿使用 Miri 生成密钥。)
综上所述,请注意 Miri **不能捕获程序中所有违反 Rust 规范的行为**,尤其是因为目前还没有这样的规范。Miri 使用自己的近似标准来判断在 Rust 中什么是和什么不是未定义行为。据我们所知,所有有可能影响程序正确性的未定义行为都*正在*被 Miri 检测(除了[遗漏的 bug][I-misses-ub]),但你应该查阅[参考文档][reference-ub]以获取未定义行为的官方定义。Miri 将随 Rust 编译器一起更新,以抵御当前编译器所理解的未定义行为 (UB),但它对未来版本的 rustc 不做任何承诺。
Miri 用户应注意的进一步注意事项:
* 如果程序依赖于数据布局的未指明细节,它在 Miri 中仍然可以正常运行——但在不同的编译器版本或不同的平台上可能会出现问题(包括引发 UB)。(你可以使用 `-Zrandomize-layout` 来检测其中一些情况。)
* 当程序执行依赖于确切的内存分配位置或并发线程的确切交错顺序等因素时,其执行是非确定性的。Miri 测试的是你程序的许多可能执行中的一种,但它会遗漏仅在另一种可能执行中才会出现的 bug。你可以通过使用不同的 `-Zmiri-seed` 值运行 Miri 来在一定程度上缓解这个问题,但这仍然远远不能探索所有可能的执行。
* Miri 作为独立于平台的解释器运行程序,因此程序无法访问大多数特定于平台的 API 或 FFI。一些 API 已经被实现(例如打印到标准输出、访问环境变量和基本文件系统访问),但大多数还没有:例如,Miri 目前不支持网络。系统 API 支持因目标而异;如果你在 Windows 上运行,使用 `--target x86_64-unknown-linux-gnu` 以获得更好的支持是个好主意。
* 弱内存模拟并不完整:存在 Miri 永远不会产生的合法行为。然而,Miri 会产生许多在真实硬件上难以观察到的行为,因此它在发现弱内存并发 bug 方面能提供很大帮助。要真正确定复杂的原子代码,请使用专用工具,如 [loom](https://github.com/tokio-rs/loom)。
此外,Miri 从根本上无法确保你的代码是*健壮的*。[健壮性]是指在从任意安全代码调用时,即使与其他健壮的代码结合,也绝不会导致未定义行为的特性。相反,Miri 只能告诉你*与代码交互的特定方式*(例如,测试套件)是否在*特定执行中*(可能有很多种,例如涉及并发或其他形式的非确定性时)导致了任何未定义行为。当 Miri 发现 UB 时,你的代码肯定是不健壮的,但当 Miri 没有发现 UB 时,你可能只是需要测试更多的输入或更多可能的非确定性选择。
## 使用 Miri
通过 `rustup` 在 Rust nightly 版本上安装 Miri:
```
rustup +nightly component add miri
```
以下所有命令都假定 nightly 工具链已通过 `rustup override set nightly` 固定。或者,在以下每个命令中使用 `cargo +nightly`。
现在你可以在 Miri 中运行你的项目:
- 要通过 Miri 运行项目中的所有测试,请使用 `cargo miri test`。
- 如果你有一个二进制项目,你可以使用 `cargo miri run` 通过 Miri 运行它。
第一次运行 Miri 时,它将执行一些额外的设置并安装一些依赖项。在安装任何内容之前,它会要求你确认。
`cargo miri run/test` 支持与 `cargo run/test` 完全相同的标志。例如,`cargo miri test filter` 仅运行名称中包含 `filter` 的测试。
你可以通过 `MIRIFLAGS` 将[标志][miri-flags]传递给 Miri。例如,`MIRIFLAGS="-Zmiri-disable-stacked-borrows" cargo miri run` 在不检查引用别名的情况下运行程序。
通过 `cargo miri` 编译代码时,对于将在 Miri 下解释的代码,将设置 `cfg(miri)` 配置标志。你可以使用它来忽略在 Miri 下因执行 Miri 不支持的操作而失败的测试用例:
```
#[test]
#[cfg_attr(miri, ignore)]
fn does_not_work_on_miri() {
tokio::run(futures::future::ok::<_, ()>(()));
}
```
Miri 不能做的事情有无数种,无法一一列举,但解释器在发现不支持的内容时会明确告诉你:
```
error: unsupported operation: can't call foreign function: bind
...
= help: this is likely not a bug in the program; it indicates that the program \
performed an operation that Miri does not support
```
### 交叉解释:针对不同目标运行
Miri 不仅可以为你的宿主目标运行二进制文件或测试套件,它还可以执行针对任意外部目标的交叉解释:`cargo miri run --target x86_64-unknown-linux-gnu` 将像运行 Linux 程序一样运行你的程序,无论你的宿主操作系统是什么。如果你使用的是 Windows,这将特别有用,因为 Linux 目标比 Windows 目标受到更好的支持。
你还可以使用此功能来测试与宿主平台属性不同的平台。例如,`cargo miri test --target s390x-unknown-linux-gnu` 将在大端目标上运行你的测试套件,这对于测试对字节序敏感的代码非常有用。
### 控制目标特性
控制目标特性的工作方式与常规的 rustc 调用类似:
`RUSTFLAGS="-Ctarget-features=+avx512f" cargo miri test` 在启用 AVX512 的情况下运行测试。(目前 Miri 仅支持极少数的 AVX512 内建函数。)`-Ctarget-cpu` 也适用。如果目标特性也与文档测试相关,你还必须设置 `RUSTDOCFLAGS`。
与常规 rustc 不同,此标志有*两个*作用:它在具有该目标特性的情况下构建代码(这会影响 `cfg(target_feature)`),并且它告诉 Miri 将被解释程序运行的“虚拟 CPU”视为具有该特性(这意味着代码被允许调用相应的内建函数)。
### 测试多个不同的执行
执行的某些部分是由 Miri 随机选择的,例如存储分配的确切基地址和并发执行线程的交错顺序。有时,探索多个不同的执行是很有用的,例如,为了确保你的代码不依赖于新分配的偶然“超级对齐”,并测试不同的线程交错。
这可以通过 `-Zmiri-many-seeds` 标志来完成:
```
MIRIFLAGS="-Zmiri-many-seeds" cargo miri test # tries the seeds in 0..64
MIRIFLAGS="-Zmiri-many-seeds=0..16" cargo miri test
```
默认的 64 个不同的种子可能会非常慢,因此你通常希望指定一个较小的范围。
### 在 CI 上运行 Miri
在 CI 上运行 Miri 时,请使用以下代码片段来安装带有 Miri 组件的 nightly 工具链:
```
rustup toolchain install nightly --component miri
rustup override set nightly
cargo miri test
```
以下是 GitHub Actions 的作业示例:
```
miri:
name: "Miri"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Miri
run: |
rustup toolchain install nightly --component miri
rustup override set nightly
cargo miri setup
- name: Test with Miri
run: cargo miri test
```
显式的 `cargo miri setup` 有助于保持实际测试步骤输出的整洁。
### 支持的目标
Miri 不支持 Rust 支持的所有目标。然而,好消息是,无论你的宿主操作系统/平台是什么,使用 `--target` 轻松运行*任何*目标的代码都是很容易的!
以下目标在 CI 上进行了测试,因此应该始终有效(达到下文记录的程度):
- Rust 的所有 [Tier 1 目标](https://doc.rust-lang.org/rustc/platform-support.html)均受 Miri
支持。它们都在 Miri 的 CI 上进行了检查,其中一些(每个操作系统至少一个)甚至在每个 Rust PR 上都进行了检查,因此发布的 Miri 应始终在这些目标上正常工作。
- `s390x-unknown-linux-gnu` 作为我们“首选的大端目标”受到支持。
- 对于操作系统为 `linux`、`macos` 或 `windows` 的所有其他目标,Miri 通常应该可以工作,但我们不作任何承诺,也不对这些目标运行测试。
- 我们对某些其他操作系统有非官方支持(不由 Miri 团队本身维护)。
- `solaris` / `illumos`:由 @devnexen 维护。支持整个测试套件。
- `freebsd`:由 @YohDeadfall 和 @LorrensP-2158466 维护。支持整个测试套件。
- `android`:**招募维护者**。支持整个测试套件。
- 对于其他操作系统上的目标,Miri 可能会在甚至还没到达 `main` 函数之前就失败。
然而,即使是我们确实支持的目标,访问平台 API(如文件系统)的支持程度也因目标而异:通常,Linux 目标具有最好的支持,macOS 目标通常不相上下。Windows 的支持相对较弱。
### 并行运行测试
尽管它实现了 Rust 线程,但 Miri 本身是一个单线程解释器(它的工作原理就像单核 CPU 上的多线程操作系统)。
这意味着在运行 `cargo miri test` 时,由于固有的解释器减速和并行性的丧失,你可能会看到运行整个测试套件所需的时间急剧增加。
你可以通过运行 `cargo miri nextest run -jN` 来恢复测试套件的并行性(请注意,你需要安装 [`cargo-nextest`](https://nexte.st))。
这是因为 `cargo-nextest` 收集所有测试的列表,然后为每个测试启动一个单独的 `cargo miri run`。有关 nextest 的更多信息,请参阅 [`cargo-nextest` Miri 文档](https://nexte.st/book/miri.html)。
注意:这种单测试单进程模型意味着 `cargo miri test` 能够检测到两个测试在共享资源上发生竞争时的数据竞争,但 `cargo miri nextest run` 不会检测到这种竞争。
注意:`cargo-nextest` 不支持文档测试,请参阅 https://github.com/nextest-rs/nextest/issues/16
### 直接调用 `miri` 驱动程序
调用 Miri 的推荐方法是通过 `cargo miri`。直接调用底层的 `miri` 驱动程序不受支持,这就是为什么该二进制文件甚至没有安装到 PATH 中的原因。但是,如果你需要在许多小测试上运行 Miri 并想像调用 `rustc` 一样直接调用它,这仍然可以通过一些额外的努力来实现:
```
# 一次性设置
cargo +nightly miri setup
SYSROOT=$(cargo +nightly miri setup --print-sysroot)
# 按文件
~/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/miri --sysroot "$SYSROOT" file.rs
```
### 常见问题
使用上述说明时,你可能会遇到许多令人困惑的编译器错误。
#### "注意:请在 `RUST_BACKTRACE=1` 环境变量下运行以显示 backtrace"
当尝试让 Miri 显示回溯时,你可能会看到此信息。默认情况下,Miri 不会向程序暴露任何环境,因此运行 `RUST_BACKTRACE=1 cargo miri test` 不会按照你的预期工作。
要获取回溯,你需要[使用 `-Zmiri-disable-isolation`][miri-flags]禁用隔离:
```
RUST_BACKTRACE=1 MIRIFLAGS="-Zmiri-disable-isolation" cargo miri test
```
#### "发现由不兼容版本的 rustc 编译的 crate `std`"
你可能正在使用与构建 Miri 使用的自定义 libstd 不同版本的编译器运行 `cargo miri`,而 Miri 未能检测到这一点。请尝试运行 `cargo miri clean`。
## Miri `-Z` 标志和环境变量
Miri 添加了自己的一组 `-Z` 标志,这些标志通常通过 `MIRIFLAGS` 环境变量设置。我们首先记录最相关和最常用的标志:
* `-Zmiri-backtrace=<0|1|full>` 配置 Miri 如何打印回溯:`1` 是默认值,以修剪形式打印回溯;`full` 打印未经修剪的回溯,而 `0` 完全禁用回溯。
* `-Zmiri-deterministic-concurrency` 使 Miri 与并发相关的行为完全确定。严格来说,当启用隔离(默认模式)时,Miri 总是完全确定的,但这种确定性是通过具有固定种子的 RNG 实现的。因此,看似无害的程序更改,或者只是为不同的目标架构运行程序,都可能导致后续出现完全不同的程序行为。此标志禁止将 RNG 用于与并发相关的决策。因此,Miri 无法找到仅在特定情况下才会出现的 bug,但 Miri 的行为在不同版本和目标之间也会更加稳定。这等效于 `-Zmiri-fixed-schedule -Zmiri-compare-exchange-weak-failure-rate=0.0 -Zmiri-address-reuse-cross-thread-rate=0.0 -Zmiri-disable-weak-memory-emulation`。
* `-Zmiri-disable-isolation` 禁用宿主隔离。因此,程序可以访问宿主资源,如环境变量、文件系统和随机数。
这将覆盖之前设置的 `-Zmiri-isolation-error`。
* `-Zmiri-disable-leak-backtraces` 禁用内存泄漏的回溯报告。默认情况下,在创建每个分配时都会捕获其回溯,以防万一发生泄漏。这会带来一些内存开销来存储几乎从不使用的数据。此标志由 `-Zmiri-ignore-leaks` 隐含。
* `-Zmiri-env-forward=` 将 `var` 环境变量转发给被解释的程序。可以多次使用以转发多个变量。如果转发变量的值保持不变,执行仍将是确定性的。如果设置了 `-Zmiri-disable-isolation`,则此选项无效。
* `-Zmiri-env-set==` 将被解释程序中的 `var` 环境变量设置为 `value`。
它可用于传递环境变量而无需更改宿主环境。它可以多次使用以设置多个变量。如果设置了 `-Zmiri-disable-isolation` 或 `-Zmiri-env-forward`,则使用此选项设置的值将优先于来自宿主环境的值。
* `-Zmiri-ignore-leaks` 禁用内存泄漏检查器,并且还允许在主线程退出时存在一些剩余线程。
* `-Zmiri-isolation-error=` 配置 Miri 在启用隔离时对需要访问宿主的操作的响应。`abort`、`hide`、`warn` 和 `warn-nobacktrace` 是支持的操作。默认值为 `abort`,它会停止机器。某些(但不是全部)操作还支持继续执行,并向程序返回“权限被拒绝”错误。`warn` 每次发生时都会打印完整的回溯;`warn-nobacktrace` 输出较少,并且每个操作最多显示一次。`hide` 完全隐藏警告。
这将覆盖之前设置的 `-Zmiri-disable-isolation`。
* `-Zmiri-many-seeds=[]..` 使用 Miri RNG 的不同种子多次运行程序。使用不同的种子,Miri 将做出不同的选择来解决非确定性问题,例如并发线程的调度顺序,或分配给分配的确切地址。
这对于查找仅在并发线程的特定交错下或以其他方式依赖于非确定性的情况下才会出现的 bug 非常有用。如果省略了 `` 部分,则默认为 `0`。
可以不带值使用;在这种情况下,范围默认为 `0..64`。
* `-Zmiri-many-seeds-keep-going` 告诉 Miri 真正尝试给定范围内的所有种子,即使已经发现了失败的种子。这对于确定失败种子的比例非常有用。
* `-Zmiri-max-extra-rounding-error` 告诉 Miri 始终将最大误差应用于没有保证精度的浮点操作。误差的符号仍然是不确定的。
* `-Zmiri-no-extra-rounding-error` 阻止 Miri 向没有保证精度的浮点操作添加额外的舍入误差。
* `-Zmiri-no-short-fd-operations` 阻止 Miri 人为地强制 `read`/`write` 操作仅处理其缓冲区的一部分。请注意,每当 Miri 使用宿主操作来实现 `read`/`write`(例如,对于文件支持的文件描述符)时,宿主系统仍可能引入短读/写。
* `-Zmiri-num-cpus` 声明要由 miri 报告的可用 CPU 数量。默认情况下,可用 CPU 数量为 `1`。请注意,此标志完全不影响 miri 处理线程的方式。
* `-Zmiri-permissive-provenance` 禁用对整型到指针转换和 [`ptr::with_exposed_provenance`](https://doc.rust-lang.org/nightly/std/ptr/fn.with_exposed_provenance.html) 的警告。
这必然会遗漏一些 bug,因为这些操作无法在清理工具中高效准确地实现,但它只会遗漏涉及受这些操作影响的内存/指针的 bug。
* `-Zmiri-report-progress` 使 Miri 不时打印当前的堆栈跟踪,以便你可以判断当程序一直运行时它在做什么。你可以通过 `-Zmiri-report-progress=` 自定义报告打印的频率,该命令每 N 个基本块打印一次报告。
* `-Zmiri-seed=` 配置 Miri 用于解决非确定性的 RNG 的种子。此 RNG 用于选择分配的基地址、确定抢占和 `compare_exchange_weak` 的失败,以及控制弱内存模拟的存储缓冲。当启用隔离(默认)时,它还用于模拟系统熵。默认种子为 0。你可以通过使用不同种子多次运行 Miri 来增加测试覆盖率。
* `-Zmiri-strict-provenance` 在 Miri 中启用[严格来源](https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance)检查。这意味着将整型转换为指针将停止执行,因为无法确定指针的来源。
* `-Zmiri-symbolic-alignment-check` 使对齐检查更加严格。默认情况下,对齐检查是通过将指针转换为整型,并确保它是堆齐倍数来进行的。这可能导致程序纯粹偶然地通过对齐检查的情况,因为事物“碰巧”被充分对齐了——这次执行没有 UB,但在其他执行中可能会有 UB。为了避免这种情况,符号对齐检查仅考虑相关分配的请求对齐方式以及在该分配中的偏移量。这避免了遗漏此类 bug,但当代码进行手动整型算术以确保对齐时,它也会产生一些误报。(标准库的 `align_to` 方法在两种模式下均可正常工作;在符号对齐下,只有当分配保证足够的对齐时,它才会填充中间切片。)
* `-Zmiri-user-relevant-crates=,,...` 扩展了 Miri 认为是“与用户相关”的 crate 列表。这会影响回溯的呈现(对于与用户相关的 crate,Miri 不仅显示函数名称,还显示实际代码),并且它会影响为数据竞争和别名冲突收集的跨度(Miri 将显示与用户相关的 crate 中最顶部的非 `#[track_caller]` 帧的跨度)。使用 `cargo miri` 时,本地工作区中的 crate 始终被认为是与用户相关的。
其余标志仅供高级使用,并且更有可能更改或被移除。
其中一些是**非健壮的**,这意味着它们可能导致 Miri 无法检测到程序中的未定义行为实例。
* `-Zmiri-address-reuse-rate=` 更改已释放的*非栈*分配被添加到地址重用池中的概率,以及新的*非栈*分配从池中取出的概率。栈分配永远不会被添加到池中或从中取出。默认值为 `0.5`。
* `-Zmiri-address-reuse-cross-thread-rate=` 更改尝试重用先前释放的内存块的分配也将考虑由*其他线程*释放的块的概率。默认值为 `0.1`,这意味着默认情况下,在进行地址重用尝试的 90% 的情况下,只会考虑来自同一线程的地址。重用来自另一个线程的地址会在这些线程之间引入同步,这可能会掩盖数据竞争和弱内存 bug。
* `-Zmiri-compare-exchange-weak-failure-rate=` 更改 `compare_exchange_weak` 操作的失败率。默认值为 `0.8`(即 5 次弱操作中有 4 次会失败)。你可以将其更改为 `0.0` 到 `1.0` 之间的任何值,其中 `1.0` 意味着它将始终失败,而 `0.0` 意味着它将永远不会失败。请注意,将其设置为 `1.0` 可能会导致挂起,因为这意味着使用 `compare_exchange_weak` 的程序无法取得进展。
* `-Zmiri-deterministic-floats` 使 Miri 的浮点行为完全确定。这意味着操作将始终返回首选的 NaN,不精确的操作将不会应用任何随机误差,并且 `min`/`max` 和“可能融合的”乘加都表现确定。请注意,Miri 仍对某些操作使用宿主浮点数,因此行为仍可能因宿主目标和设置而异。请参阅 `-Zmiri-no-extra-rounding-error` 以获取专门仅禁用随机误差的标志。
* `-Zmiri-disable-alignment-check` 禁用指针对齐检查,因此你可以专注于其他故障,但这意味着 Miri 可能会遗漏程序中的 bug。
使用此标志是**非健壮的**。
* `-Zmiri-disable-data-race-detector` 禁用数据竞争检查。使用此标志是**非健壮的**。这隐含了 `-Zmiri-disable-weak-memory-emulation`。
* `-Zmiri-disable-stacked-borrows` 禁用检查实验性的别名规则以跟踪借用([Stacked Borrows] 和 [Tree Borrows])。
这可以使 Miri 运行得更快,但这也意味着不会检测到别名违规。使用此标志是**非健壮的**(但受影响的健壮性规则是实验性的)。后面的标志优先:可以通过 `-Zmiri-tree-borrows` 重新激活借用跟踪。
* `-Zmiri-disable-validation` 禁用强制执行有效性不变量,默认情况下这些不变量是强制执行的。这仅对类型化副本禁用这些检查;在任何其他操作中使用无效值仍将导致错误。这也禁用了别名模型。这主要用于首先专注于其他故障(例如越界访问)。设置此标志意味着 Miri 可能会遗漏程序中的 bug。但是,这也有助于使 Miri 运行得更快。使用此标志是**非健壮的**。
* `-Zmiri-disable-weak-memory-emulation` 禁用对某些 C++11 弱内存效果的模拟。
* `-Zmiri-fixed-schedule` 禁用抢占(类似于 `-Zmiri-preemption-rate=0.0`),并进一步禁用下一个要选择的线程的随机化,而是固定为循环调度。但是请注意,Miri 并发行为的其他方面仍然是随机的;请使用 `-Zmiri-deterministic-concurrency` 全部禁用它们。
* `-Zmiri-force-intrinsic-fallback` 强制对所有具有备用主体的内建函数使用“备用”主体。这对于测试备用主体非常有用,但在其他情况下不应使用。它是**非健壮的**,因为备用主体可能没有检查所有 UB。
* `-Zmiri-native-lib=` 是一个实验性标志,用于提供通过 FFI 从解释器内部调用原生函数的支持。该标志仅在 Unix 系统上受支持。该文件未提供的函数仍通过通常的 Miri 垫片执行。如果指定了目录的路径,则该目录中的所有文件将以非递归方式包含在内。可以多次传递此标志以指定多个文件和/或目录。
**警告**:如果指定了无效/不正确的 `.so` 文件,这可能会导致 Miri 本身出现未定义行为!当然,Miri 通常无法对原生代码采取的操作进行任何检查。
请注意,Miri 有自己的文件描述符处理方式,因此如果你想替换某些处理文件描述符的函数,你必须替换所有这些函数,否则这两种文件描述符将被混淆。
这是**正在进行的工作**;目前,仅支持整型和指针参数及返回值,并且原生代码分配的内存无法从 Rust 访问(仅支持反过来访问)。原生代码不得派生在调用返回 Rust 后仍在后台运行并访问 Rust 分配的内存的线程。
最后,该标志在某种意义上是**非健壮的**,即 Miri 停止跟踪诸如初始化和来源等与原生代码共享的内存上的细节,因此很容易编写出具有 Miri 遗漏的 UB 的代码。
* `-Zmiri-native-lib-enable-tracing` 为调用原生代码启用正在开发(WIP)的详细跟踪模式。请注意,此标志仅在 Linux 系统上有意义;其他 Unix 系统(目前)不支持跟踪模式。
* `-Zmiri-measureme=` 为被解释的程序启用 `measureme` 性能分析。
这可用于找出程序的哪些部分在 Miri 下执行缓慢。
性能分析数据将写入名为 `` 的目录内的文件中,可以使用存储库 https://github.com/rust-lang/measureme 中的工具进行处理。
* `-Zmiri-mute-stdout-stderr` 静默忽略所有对 stdout 和 stderr 的写入,
但向程序报告它确实已写入。当你对实际程序的输出不感兴趣,而只想查看 Miri 的错误和警告时,这非常有用。
* `-Zmiri-recursive-validation` 是一个*高度实验性*的标志,它使有效性检查在引用之下递归*一层*。内存中的值被视为位于 `MaybeDangling` 中,即嵌套引用甚至不必是可解引用的。
* `-Zmiri-preemption-rate` 配置基本块结束时活动线程被抢占的概率。默认值为 `0.01`(即 1%)。将此设置为 `0` 将禁用抢占。请注意,即使没有抢占,调度仍然是不确定的:如果线程阻塞或让步,则随机选择下一个线程。
* `-Zmiri-provenance-gc=` 配置指针来源垃圾收集器运行的频率。默认设置是每 `10000` 个基本块搜索并删除一次不可达的来源。将此设置为 `0` 将禁用垃圾收集器,这会导致某些程序的内存使用量激增和/或超线性运行时间。
* `-Zmiri-track-alloc-accesses` 不仅显示被跟踪分配的分配和释放事件,还显示读取和写入。
* `-Zmiri-track-alloc-id=,,...` 在给定分配被分配或释放时显示回溯。这有助于调试内存泄漏和释放后使用 bug。多次指定此参数不会覆盖先前的值,而是将其值附加到列表中。多次列出 ID 没有效果。你还可以在运行时使用 `miri_track_alloc` 添加 ID。
* `-Zmiri-track-pointer-tag=,,...` 在创建给定指针标签以及(如果曾经)将其从借用栈中弹出(这是标签变为无效且其将来任何使用都会报错的地方)时显示回溯。这有助于你找出发生 UB 的原因以及代码中何处适合寻找它。多次指定此参数不会覆盖先前的值,而是将其值附加到列表中。多次列出标签没有效果。
* `-Zmiri-track-weak-memory-loads` 在弱内存模拟从加载中返回过时值时显示回溯。这有助于诊断在 `-Zmiri-disable-weak-memory-emulation` 下消失的问题。
*
`-Zmiri-tree-borrows` 用 [Tree Borrows] 规则替换 [Stacked Borrows]。
Tree Borrows 比 Stacked Borrows 更具实验性。尽管从捕获当前版本的编译器可能利用的所有别名违规的意义上讲,Tree Borrows 仍然是健壮的,但 Rust 最终的最终别名模型可能比 Tree Borrows 更严格。换言之,如果你使用 Tree Borrows,即使你的代码今天被接受,它将来也可能会被声明为 UB。使用 Stacked Borrows 时,这种情况的可能性要小得多。
* `-Zmiri-tree-borrows-implicit-writes` 为所有 `&mut` 函数参数启用隐式写入。
这使 Tree Borrows 不那么宽松。
* `-Zmiri-tree-borrows-no-precise-interior-mut` 使 Tree Borrows
在引用级别而不是默认情况下的字节级别跟踪内部可变数据。因此,使用此标志时,Tree Borrows 将更加宽松。
* `-Zmiri-force-page-size=` 覆盖体系结构的默认页面大小,以 1k 的倍数表示。
大多数目标的默认值为 `4`。此值应始终为 2 的幂且非零。
此外,Miri 识别一些环境变量:
* `MIRIFLAGS` 定义要传递给 Miri 的额外标志。
* `MIRI_LIB_SRC` 定义 Miri 期望其将构建并用于解释的标准库源代码的目录。此目录必须指向 `rust-lang/rust` 代码库检出中的 `library` 子目录。
* `MIRI_SYSROOT` 指示要使用的 sysroot。使用 `cargo miri test`/`cargo miri run` 时,这将跳过自动设置——仅当不想使用自动创建的 sysroot 时才设置此项。在调用 `cargo miri setup` 时,这指示 sysroot 将被放置的位置。
* `MIRI_NO_STD` 确保目标的 sysroot 在没有 libstd 的情况下构建。这允许测试和运行 no_std 程序。这*通常不应使用*;Miri 有一个基于目标名称检测 no-std 目标的启发式方法。在支持 libstd 的目标上设置此项可能会导致令人困惑的结果。
## Miri `extern` 函数
Miri 提供了一些程序可以导入以访问 Miri 特定功能的 `extern` 函数。它们在 [/tests/utils/miri\_extern.rs](/tests/utils/miri_extern.rs) 中声明。
## no-std 二进制文件的入口点
不使用标准库的二进制文件需要声明一个这样的函数,以便 Miri 知道它应该从哪里开始执行:
```
#[cfg(miri)]
#[unsafe(no_mangle)]
fn miri_start(argc: isize, argv: *const *const u8) -> isize {
// Call the actual start function that your project implements, based on your target's conventions.
}
```
## 贡献和获取帮助
如果你想为 Miri 做贡献,太好了!请查看我们的[贡献指南](CONTRIBUTING.md)。
如需有关运行 Miri 的帮助,你可以在 GitHub 上提出问题,或使用 [Rust Zulip 上的 Miri 频道][zulip]。
## 历史
该项目始于 2015 年,作为 [萨斯喀彻温大学][usask] @solson 本科研究课程的一部分。该项目有可用的[幻灯片]和[报告]。2016 年,@oli-obk 加入以准备让 Miri 最终在 Rust 编译器本身(基本上用于 `const` 和 `static` 的东西)中用作 const 求值器,取代了直接在 AST 上工作的旧求值器。2017 年,@RalfJung 在 Mozilla 实习,开始将 Miri 开发为用于检测未定义行为的工具,并利用 Miri 探索 Rust 中未定义行为的各种可能定义的后果。@oli-obk 将 Miri 引擎移入编译器的工作终于在 2018 年初完成。与此同时,同年晚些时候,@RalfJung 进行了第二次实习,进一步开发 Miri,支持检查基本类型不变量并验证引用是否根据其别名限制使用。
## Miri 发现的 bug
Miri 已经在 Rust 标准库及其他地方发现了许多 bug,我们在此收集了其中的一部分。
如果 Miri 帮助你在代码中发现了微妙的 UB bug,我们非常希望你提交 PR 将其添加到列表中!
发现的确切切 bug:
* [`Debug for vec_deque::Iter` 访问未初始化的内存](https://github.com/rust-lang/rust/issues/53566)
* [`Vec::into_iter` 执行未对齐的 ZST 读取](https://github.com/rust-lang/rust/pull/53804)
* [`From<&[T]> for Rc` 创建了未充分对齐的引用](https://github.com/rust-lang/rust/issues/54908)
* [`BTreeMap` 创建了指向过小分配的共享引用](https://github.com/rust-lang/rust/issues/54957)
* [`Vec::append` 创建了悬垂引用](https://github.com/rust-lang/rust/pull/61082)
* [Futures 将共享引用转换为可变引用](https://github.com/rust-lang/rust/pull/56319)
* [`str` 将共享引用转换为可变引用](https://github.com/rust-lang/rust/pull/58200)
* [`rand` 执行未对齐的读取](https://github.com/rust-random/rand/issues/779)
* [Unix 分配器以无效方式调用 `posix_memalign`](https://github.com/rust-lang/rust/issues/62251)
* [`getrandom` 以无效方式调用 `getrandom` 系统调用](https://github.com/rust-random/getrandom/pull/73)
* [`Vec`](https://github.com/rust-lang/rust/issues/69770) 和 [`BTreeMap`](https://github.com/rust-lang/rust/issues/69769) 在某些(异常)条件下泄漏内存
* [`beef` 泄漏内存](https://github.com/maciejhirsz/beef/issues/12)
* [`EbrCell` 错误地使用了未初始化的内存](https://github.com/Firstyear/concread/commit/b15be53b6ec076acb295a5c0483cdb4bf9be838f#diff-6282b2fc8e98bd089a1f0c86f648157cR229)
* [TiKV 执行未对齐的指针访问](https://github.com/tikv/tikv/issues/7613)
* [`servo_arc` 创建了悬垂的共享引用](https://github.com/servo/servo/issues/26357)
* [TiKV 构建越界指针(以及重叠的可变引用)](https://github.com/tikv/tikv/pull/7751)
* [`encoding_rs` 执行越界指针算术](https://github.com/hsivonen/encoding_rs/pull/53)
* [TiKV 错误地使用 `Vec::from_raw_parts`](https://github.com/tikv/agatedb/pull/24)
* [`AtomicPtr`](https://github.com/rust-lang/rust/pull/84052) 和 [`Box::from_raw_in`](https://github.com/rust-lang/rust/pull/84053) 的不正确文档测试
* [`ThinVec` 中对齐不足](https://github.com/Gankra/thin-vec/pull/27)
* [`crossbeam-epoch` 对部分初始化的 `MaybeUninit` 调用 `assume_init`](https://github.com/crossbeam-rs/crossbeam/pull/779)
* [`integer-encoding` 解引用未对齐的指针](https://github.com/dermesser/integer-encoding-rs/pull/23)
* [`rkyv` 从过度对齐的分配构造 `Box<[u8]>`](https://github.com/rkyv/rkyv/commit/a9417193a34757e12e24263178be8b2eebb72456)
* [`arc-swap` 中的数据竞争](https://github.com/vorner/arc-swap/issues/76)
* [`thread::scope` 中的数据竞争](https://github.com/rust-lang/rust/issues/98498)
* [`regex` 错误处理未对齐的 `Vec` 缓冲区](https://www.reddit.com/r/rust/comments/vq3mmu/comment/ienc7t0?context=3)
* [在 `once_cell` 中错误使用 `compare_exchange_weak`](https://github.com/matklad/once_cell/issues/186)
* [在 `vec::IntoIter` 中使用未对齐的指针进行释放](https://github.com/rust-lang/rust/pull/106084)
* [在原地 `Iterator::collect` 的新特化中使用错误布局解除分配](https://github.com/rust-lang/rust/pull/118460)
* [在 `portable-atomic-util` 中对高度对齐类型的不正确偏移量计算](https://github.com/taiki-e/portable-atomic/pull/138)
* [`std::mpsc` 通道中偶发的内存泄漏](https://github.com/rust-lang/rust/issues/121582)(原始代码在 [crossbeam](https://github.com/crossbeam-rs/crossbeam/pull/1084) 中)
* [Windows 线程局部存储中弱内存引起的内存泄漏](https://github.com/rust-lang/rust/pull/124281)
* [新 `RwLock::downgrade` 实现中的一个 bug](https://rust-lang.zulipchat.com/#narrow/channel/269128-miri/topic/Miri.20error.20library.20test)(在进入 Rust 代码库之前被 Miri 捕获)
* [Mockall 在模拟 `std::io::Read::read` 时读取未初始化的内存,即使所有期望都已满足](https://github.com/asomers/mockall/issues/647)(通过运行 Tokio 的测试套件被 Miri 捕获)
* [`ReentrantLock` 未正确处理不同线程的 TLS 存储地址重用](https://github.com/rust-lang/rust/pull/141248)
* [线程停用/激活示例代码中罕见的死锁](https://github.com/rust-lang/rust/issues/145816)
* [`winit` 在 Windows 上以错误的 ABI 注册全局构造函数](https://github.com/rust-windowing/winit/issues/4435)
* [`VecDeque::splice` 混淆了物理和逻辑索引](https://github.com/rust-lang/rust/issues/151758)
* [`oneshot` 通道中的数据竞争](https://github.com/faern/oneshot/issues/69)
* [serde-yaml-bw 中的内存泄漏](https://github.com/bourumir-wyngs/serde-yaml-bw/issues/197)
发现的可能为 bug 的 [Stacked Borrows] 违规(但 Stacked Borrows 目前只是一个实验):
* [`VecDeque::drain` 创建重叠的可变引用](https://github.com/rust-lang/rust/pull/56161)
* 各种 `BTreeMap` 问题
* [`BTreeMap` 迭代器创建与共享引用重叠的可变引用](https://github.com/rust-lang/rust/pull/58431)
* [`BTreeMap::iter_mut` 创建重叠的可变引用](https://github.com/rust-lang/rust/issues/73915)
* [`BTreeMap` 节点插入在其有效内存区域外使用原始指针](https://github.com/rust-lang/rust/issues/78477)
* [`LinkedList` 游标插入创建重叠的可变引用](https://github.com/rust-lang/rust/pull/60072)
* [`Vec::push` 使指向向量的现有引用失效](https://github.com/rust-lang/rust/issues/60847)
* [`align_to_mut` 违反可变引用的唯一性](https://github.com/rust-lang/rust/issues/68549)
* [`sized-chunks` 创建别名的可变引用](https://github.com/bodil/sized-chunks/issues/8)
* [`String::push_str` 使指向字符串的现有引用失效](https://github.com/rust-lang/rust/issues/70301)
* [`ryu` 在其有效内存区域外使用原始指针](https://github.com/dtolnay/ryu/issues/24)
* [ink! 创建重叠的可变引用](https://github.com/rust-lang/miri/issues/1364)
* [TiKV 创建重叠的可变引用和原始指针](https://github.com/tikv/tikv/pull/7709)
* [Windows `Env` 迭代器在其有效内存区域外使用原始指针](https://github.com/rust-lang/rust/pull/70479)
* [`VecDeque::iter_mut` 创建重叠的可变引用](https://github.com/rust-lang/rust/issues/74029)
* [涉及原始指针的各种标准库别名问题](https://github.com/rust-lang/rust/pull/78602)
* [`<[T]>::copy_within` 在使借用失效后使用它](https://github.com/rust-lang/rust/pull/85610)
## 采用 Miri 的学术论文
* [Stacked Borrows: An Aliasing Model for Rust](https://plv.mpi-sws.org/rustbelt/stacked-borrows/)
* [Using Lightweight Formal Methods to Validate a Key-Value Storage Node in Amazon S3](https://www.amazon.science/publications/using-lightweight-formal-methods-to-validate-a-key-value-storage-node-in-amazon-s3)
* [SyRust: Automatic Testing of Rust Libraries with Semantic-Aware Program Synthesis](https://dl.acm.org/doi/10.1145/3453483.3454084)
* [Crabtree: Rust API Test Synthesis Guided by Coverage and Type](https://dl.acm.org/doi/10.1145/3689733)
* [Rustlantis: Randomized Differential Testing of the Rust Compiler](https://dl.acm.org/doi/10.1145/3689780)
* [A Study of Undefined Behavior Across Foreign Function Boundaries in Rust Libraries](https://arxiv.org/abs/2404.11671)
* [Tree Borrows](https://plf.inf.ethz.ch/research/pldi25-tree-borrows.html)
* [Miri: Practical Undefined Behavior Detection for Rust](https://plf.inf.ethz.ch/research/popl26-miri.html) **(本文介绍了 Miri 本身)**
## 许可证
根据以下任一许可证授权
* Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) 或
http://.apache.org/licenses/LICENSE-2.0)
* MIT license ([LICENSE-MIT](LICENSE-MIT) 或
http://opensource.org/licenses/MIT)
由你选择。
### 贡献
除非你明确声明,否则你故意提交以包含在本作品中的任何贡献均应按上述方式双重许可,不附带任何额外的条款或条件。
标签:DNS解析, Miri, Rust, 中间表示, 云安全监控, 代码安全测试, 内存安全, 内存对齐, 内存泄漏, 别名规则, 可视化界面, 威胁情报, 并发安全, 开发者工具, 开源项目, 弱内存模型, 数据竞争, 未定义行为检测, 模糊测试辅助, 测试工具, 系统编程, 网络流量审计, 解释器, 越界访问, 跨平台模拟, 软件质量保证, 通知系统, 释放后使用, 静态分析