bigywater.tech
Rust · 163

Miri 检查未定义行为

一段 unsafe 代码在普通运行里没报错,是不是就没有问题?这篇把几种典型的未定义行为各写一个最小程序,先用普通编译运行,再用 Miri 解释执行,对比它们的结果、报错内容,以及 Miri 的代价与限制。

7
本篇里普通运行不报错、而 Miri 报错的例子数(其中 1 个是调试构建自带的对齐检查也能抓住)
≈33602×
100 万次循环:Miri 对比原生 -O 的耗时倍数
2
别名违规在 Stacked Borrows 与 Tree Borrows 两个模型下都被判为 UB

释放后使用

实测 · 本机执行输出
  源码:
      1 | fn main() {
      2 |     let v = vec![1, 2, 3];
      3 |     let p = v.as_ptr();
      4 |     drop(v);
      5 |     println!("{}", unsafe { *p });
      6 | }
  普通编译运行(brew rustc 1.98.1 稳定版):退出码 0
    2
  cargo miri run:退出码 1
    error: Undefined Behavior: memory access failed: alloc216 has been freed, so this pointer is dangling
     --> src/main.rs:5:29
      |
    5 |     println!("{}", unsafe { *p });
      |                             ^^ Undefined Behavior occurred here
      |
      = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
      = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
    help: alloc216 was allocated here:
     --> src/main.rs:2:13
      |
    2 |     let v = vec![1, 2, 3];
      |             ^^^^^^^^^^^^^
    help: alloc216 was deallocated here:
     --> src/main.rs:4:5
      |
    4 |     drop(v);
      |     ^^^^^^^
    
    note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
    
    error: aborting due to 1 previous error

Miri 是 Rust 的 MIR 解释器,按 Rust 的抽象机规则一条条执行,所以能在「碰巧能跑」的地方发现问题。内存模型相关内容见unsafe 的边界与MaybeUninit。

越界读取

实测 · 本机执行输出
  源码:
      1 | fn main() {
      2 |     let v = vec![10u32, 20, 30];
      3 |     let x = unsafe { *v.as_ptr().add(3) };
      4 |     println!("{}", x);
      5 | }
  普通编译运行(brew rustc 1.98.1 稳定版):退出码 0
    0
  cargo miri run:退出码 1
    error: Undefined Behavior: memory access failed: attempting to access 4 bytes, but got alloc216+0xc which is at or beyond the end of the allocation of size 12 bytes
     --> src/main.rs:3:22
      |
    3 |     let x = unsafe { *v.as_ptr().add(3) };
      |                      ^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here
      |
      = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
      = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
    help: alloc216 was allocated here:
     --> src/main.rs:2:13
      |
    2 |     let v = vec![10u32, 20, 30];
      |             ^^^^^^^^^^^^^^^^^^^
    
    note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
    
    error: aborting due to 1 previous error

未初始化与非法值

实测 · 本机执行输出
  源码:
      1 | use std::mem::MaybeUninit;
      2 | fn main() {
      3 |     let x: u32 = unsafe { MaybeUninit::<u32>::uninit().assume_init() };
      4 |     println!("{}", x);
      5 | }
  普通编译运行(brew rustc 1.98.1 稳定版):退出码 0
    72632596
  cargo miri run:退出码 1
    warning: the type `u32` does not permit being left uninitialized
     --> src/main.rs:3:27
      |
    3 |     let x: u32 = unsafe { MaybeUninit::<u32>::uninit().assume_init() };
      |                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      |                           |
      |                           this code causes undefined behavior when executed
      |                           help: use `MaybeUninit<T>` instead, and only call `assume_init` after initialization is done
      |
      = note: integers must be initialized
      = note: `#[warn(invalid_value)]` on by default
    
    error: Undefined Behavior: constructing invalid value of type u32: encountered uninitialized memory, but expected an integer
     --> src/main.rs:3:27
      |
    3 |     let x: u32 = unsafe { MaybeUninit::<u32>::uninit().assume_init() };
      |                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here
      |
      = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
      = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
    
    note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
    …(共 24 行)
  源码:
      1 | fn main() {
      2 |     let b: bool = unsafe { std::mem::transmute::<u8, bool>(2) };
      3 |     println!("{}", b);
      4 | }
  普通编译运行(brew rustc 1.98.1 稳定版):退出码 0
    false
  cargo miri run:退出码 1
    error: Undefined Behavior: constructing invalid value of type bool: encountered 0x02, but expected a boolean
     --> src/main.rs:2:28
      |
    2 |     let b: bool = unsafe { std::mem::transmute::<u8, bool>(2) };
      |                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here
      |
      = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
      = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
    
    note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
    
    error: aborting due to 1 previous error

未对齐引用

实测 · 本机执行输出
  源码:
      1 | fn main() {
      2 |     let buf = [0u8; 8];
      3 |     let p = unsafe { buf.as_ptr().add(1) } as *const u32;
      4 |     let r: &u32 = unsafe { &*p };
      5 |     println!("{}", r);
      6 | }
  普通编译运行(brew rustc 1.98.1 稳定版):退出码 -6
    thread 'main' (7597485) panicked at <实验目录>/src/u163_align.rs:4:28:
    misaligned pointer dereference: address must be a multiple of 0x4 but is 0x16d3f6861
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
    thread caused non-unwinding panic. aborting.
  cargo miri run:退出码 1
    error: Undefined Behavior: constructing invalid value of type &u32: encountered an unaligned reference (required 4 byte alignment but found 1)
     --> src/main.rs:4:28
      |
    4 |     let r: &u32 = unsafe { &*p };
      |                            ^^^ Undefined Behavior occurred here
      |
      = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
      = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
    
    note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
    
    error: aborting due to 1 previous error

别名规则

实测 · 本机执行输出
  源码:
      1 | fn main() {
      2 |     let mut x = 10;
      3 |     let raw = &mut x as *mut i32;
      4 |     let r1 = unsafe { &mut *raw };
      5 |     let r2 = unsafe { &mut *raw };
      6 |     *r2 = 20;
      7 |     *r1 = 30;
      8 |     println!("{}", x);
      9 | }
  普通编译运行(brew rustc 1.98.1 稳定版):退出码 0
    30
  cargo miri run:退出码 1
    error: Undefined Behavior: attempting a write access using <553> at alloc197[0x0], but that tag does not exist in the borrow stack for this location
     --> src/main.rs:7:5
      |
    7 |     *r1 = 30;
      |     ^^^^^^^^ this error occurs as part of an access at alloc197[0x0..0x4]
      |
      = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
      = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
    help: <553> was created by a Unique retag at offsets [0x0..0x4]
     --> src/main.rs:4:23
      |
    4 |     let r1 = unsafe { &mut *raw };
      |                       ^^^^^^^^^
    help: <553> was later invalidated at offsets [0x0..0x4] by a Unique retag
     --> src/main.rs:5:23
      |
    5 |     let r2 = unsafe { &mut *raw };
      |                       ^^^^^^^^^
    
    note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
    
    error: aborting due to 1 previous error
  —— 同一段代码,换成 Tree Borrows 模型:
  MIRIFLAGS=-Zmiri-tree-borrows:退出码 1
    error: Undefined Behavior: write access through <530> at alloc197[0x0] is forbidden
     --> src/main.rs:7:5
      |
    7 |     *r1 = 30;
      |     ^^^^^^^^ Undefined Behavior occurred here
      |
      = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
      = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
      = help: the accessed tag <530> has state Disabled which forbids this child write access
    help: the accessed tag <530> was created here, in the initial state Reserved
     --> src/main.rs:4:23
      |
    4 |     let r1 = unsafe { &mut *raw };
      |                       ^^^^^^^^^
    help: the accessed tag <530> later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4]
     --> src/main.rs:6:5
      |
    6 |     *r2 = 20;
      |     ^^^^^^^^
      = help: this transition corresponds to a loss of read and write permissions
    
    note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
    …(共 24 行)

数据竞争

实测 · 本机执行输出
  源码:
      1 | use std::thread;
      2 | struct P(*mut u32);
      3 | unsafe impl Send for P {}
      4 | fn main() {
      5 |     let mut x = 0u32;
      6 |     let p = P(&mut x as *mut u32);
      7 |     let h = thread::spawn(move || {
      8 |         let p = p;
      9 |         unsafe { *p.0 = 1 };
     10 |     });
     11 |     unsafe { *(&mut x as *mut u32) = 2 };
     12 |     h.join().unwrap();
     13 |     println!("x = {}", x);
     14 | }
  普通编译运行(brew rustc 1.98.1 稳定版):退出码 0
    x = 1
  cargo miri run:退出码 1
    error: Undefined Behavior: Data race detected between (1) non-atomic write on thread `main` and (2) non-atomic write on thread `unnamed-1` at alloc197
      --> src/main.rs:9:18
       |
     9 |         unsafe { *p.0 = 1 };
       |                  ^^^^^^^^ (2) just happened here
       |
    help: and (1) occurred earlier here
      --> src/main.rs:11:14
       |
    11 |     unsafe { *(&mut x as *mut u32) = 2 };
       |              ^^^^^^^^^^^^^^^^^^^^^^^^^
       = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
       = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
       = note: this is on thread `unnamed-1`
    note: the current function got called indirectly due to this code
      --> src/main.rs:7:13
       |
     7 |       let h = thread::spawn(move || {
       |  _____________^
     8 | |         let p = p;
     9 | |         unsafe { *p.0 = 1 };
    10 | |     });
       | |______^
    
    note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
    
    …(共 27 行)

数据竞争在原子操作与同步原语上的正确写法见原子操作与内存序。

内存泄漏

实测 · 本机执行输出
  源码:
      1 | fn main() {
      2 |     let b = Box::new([1u8; 16]);
      3 |     std::mem::forget(b);
      4 |     let v = vec![1, 2, 3];
      5 |     std::mem::forget(v);
      6 |     println!("done");
      7 | }
  普通编译运行(brew rustc 1.98.1 稳定版):退出码 0
    done
  cargo miri run:退出码 1
    error: memory leaked: alloc218 (Rust heap, size: 16, align: 1), allocated here:
     --> src/main.rs:2:13
      |
    2 |     let b = Box::new([1u8; 16]);
      |             ^^^^^^^^^^^^^^^^^^^
    
    error: memory leaked: alloc268 (Rust heap, size: 12, align: 4), allocated here:
     --> src/main.rs:4:13
      |
    4 |     let v = vec![1, 2, 3];
      |             ^^^^^^^^^^^^^
    
    note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
    
    note: set `MIRIFLAGS=-Zmiri-ignore-leaks` to disable this check
    
    error: aborting due to 2 previous errors
    
    done

代价

实测 · 本机执行输出
        迭代次数     原生 -O (ms)     Miri 真实 (ms)       倍数                   Miri 默认隔离下程序自己看到的 (ms)
       10000         0.0067            350.2    52200                                   1450.3
      100000         0.1103           3312.5    30023                                  14500.3
     1000000         0.9814          32977.8    33602                                 145000.3

限制

实测 · 本机执行输出
  源码:
      1 | use std::net::TcpListener;
      2 | fn main() {
      3 |     let l = TcpListener::bind("127.0.0.1:0").unwrap();
      4 |     println!("{}", l.local_addr().unwrap());
      5 | }
  普通编译运行(brew rustc 1.98.1 稳定版):退出码 0
    127.0.0.1:54916
  cargo miri run:退出码 1
    error: unsupported operation: `socket` not available when isolation is enabled
      --> <实验目录>/rustup_home/toolchains/nightly-aarch64-apple-darwin/lib/rustlib/src/rust/library/std/src/sys/net/connection/socket/unix.rs:98:39
       |
    98 |                 let fd = cvt(unsafe { libc::socket(family, ty, 0) })?;
       |                                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^ unsupported operation occurred here
       |
       = help: set `MIRIFLAGS=-Zmiri-disable-isolation` to disable isolation;
       = help: or set `MIRIFLAGS=-Zmiri-isolation-error=warn` to make Miri return an error code from isolated operations (if supported for that operation) and continue with a warning
       = note: stack backtrace:
               0: std::sys::net::connection::socket::unix::Socket::new
                   at <实验目录>/rustup_home/toolchains/nightly-aarch64-apple-darwin/lib/rustlib/src/rust/library/std/src/sys/net/connection/socket/unix.rs:98:39: 98:66
               1: std::sys::net::connection::socket::TcpListener::bind::inner
    …(共 27 行)
  —— 同一段 socket 代码,加 -Zmiri-disable-isolation:
  退出码 0
    127.0.0.1:54917
  源码:
      1 | unsafe extern "C" { fn ffs(i: i32) -> i32; }
      2 | fn main() { println!("{}", unsafe { ffs(8) }); }
  普通编译运行(brew rustc 1.98.1 稳定版):退出码 0
    4
  cargo miri run:退出码 1
    error: unsupported operation: can't call foreign function `ffs` on OS `macos`
     --> src/main.rs:2:37
      |
    2 | fn main() { println!("{}", unsafe { ffs(8) }); }
      |                                     ^^^^^^ unsupported operation occurred here
      |
      = help: this means the program tried to do something Miri does not support; it does not indicate a bug in the program
    
    note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
    
    error: aborting due to 1 previous error

下一篇的 Sanitizer 是另一类思路:把检查编译进原生程序,速度快但能发现的问题不同,见Sanitizer。

分步演示

Miri
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。nightly 工具链用 rustup 装在隔离目录(不影响 brew 的 rustc):rustc 1.101.0-nightly (21b707e3f 2026-09-30)、miri 0.1.0 (21b707e3f9 2026-09-30)。cargo miri run(默认的 Stacked Borrows;另用 -Zmiri-tree-borrows)
    • 没有验证:Miri 对更多目标平台(-Zmiri-many-seeds 以外的随机化)的行为、-Zmiri-many-seeds 对线程调度的覆盖效果、在大型真实 crate 测试套件上跑 cargo miri test 的耗时、Miri 对内联汇编与更多 FFI 的支持、Stacked 与 Tree Borrows 在更复杂代码上结论不同的情况
    京ICP备2026067502号-1京公网安备11010502063487号