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