导致段错误的 Rust 代码示例是什么?

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

我用 Google 搜索了 Rust 中的一些段错误示例,但现在没有崩溃。 Rust 现在能够防止所有段错误吗?有没有一个简单的演示会导致段错误?

rust segmentation-fault
6个回答
12
投票

如果允许使用

unsafe
代码,则:

fn main() {
    unsafe { std::ptr::null_mut::<i32>().write(42) };
}

结果:

   Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 1.37s
     Running `target/debug/playground`
timeout: the monitored command dumped core
/playground/tools/entrypoint.sh: line 11:     7 Segmentation fault      timeout --signal=KILL ${timeout} "$@"

如在游乐场所见。


任何会触发段错误的情况都需要在某个时刻调用未定义的行为。编译器可以优化代码或以其他方式利用未定义行为永远不应该发生的事实,因此很难保证某些代码会出现段错误。编译器完全有权利使上述程序运行而不触发段错误。

举个例子,上面的代码在 release 模式下编译时会导致“非法指令”。


如果不允许

unsafe
代码,请参阅 Rust 如何保证内存安全并防止段错误? 了解 Rust 如何保证只要不违反其内存安全不变量就不会发生这种情况(这只能发生在
unsafe
代码)。

如果可以避免,就不要使用不安全的代码。


10
投票

严格来说,总是有可能欺骗程序认为它存在分段错误,因为这是操作系统发送的信号:

use libc::kill;
use std::process;

fn main() {
    unsafe {
        // First SIGSEGV will be consumed by Rust runtime
        // (see https://users.rust-lang.org/t/is-sigsegv-handled-by-rust-runtime/45680)...
        kill(process::id() as i32, libc::SIGSEGV);
        // ...but the second will crash the program, as expected
        kill(process::id() as i32, libc::SIGSEGV);
    }
}

游乐场

这并不是你问题的真正答案,因为这不是“真正的”分段错误,但从字面上看这个问题 - Rust 程序仍然可以以“分段错误”错误结束,这里有一个可靠地触发它的情况。


3
投票

如果您更普遍地寻找会转储核心的东西,而不是专门导致段错误,则还有另一种选择,即导致编译器发出

UD2
指令或等效指令。有一些事情可以产生这种效果:

  • 一个没有任何副作用的空循环由于LLVM优化而成为UB

    fn main() {
         (|| loop {})()
    }
    

    游乐场

    这不再产生 UB。

  • 尝试创建从不(

    !
    )类型

    #![feature(never_type)]
    union Erroneous {
         a: (),
         b: !,
    }
    
    fn main() {
         unsafe { Erroneous { a: () }.b }
    }
    

    游乐场

  • 或者也尝试使用(在本例中匹配)没有变体的枚举:

    #[derive(Clone, Copy)]
    enum Uninhabited {}
    
    union Erroneous {
         a: (),
         b: Uninhabited,
    }
    
    fn main() {
         match unsafe { Erroneous { a: () }.b } {
             // Nothing to match on.
         }
    }
    

    游乐场

  • 最后,你可以作弊并强制它直接生成 UD2:

    #![feature(asm)]
    fn main() {
         unsafe {
             asm! {
                 "ud2"
             }
         };
    }
    

    游乐场

    或使用

    llvm_asm!
    代替
    asm!

    #![feature(llvm_asm)]
    fn main() {
         unsafe {
             llvm_asm! {
                 "ud2"
             }
         };
    }
    

    游乐场


1
投票

在安全的 Rust 中,你可以通过操作系统设施故意搞乱你自己进程的内存。

use std::io::Write;
use std::io::Seek;

fn main() {
    let x = 42;
    let y = &x;

    // Delete the next few lines and everything is good.
    let mut f = std::fs::OpenOptions::new()
        .write(true)
        .open("/proc/self/mem").expect("welp");
    // Turn y into a nullptr
    f.seek(std::io::SeekFrom::Start(&y as *const _ as u64)).expect("oof");
    f.write(&0usize.to_ne_bytes()).expect("darn");

    println!("{y}");
}

游乐场

虽然这有一些“duuuuh”因素,但通用技术已经引起了一些波澜。甚至考虑过这是否不合理,但最终大多被拒绝。 (有人认为 Rust 不能防止机器异常,无论是位翻转还是操作系统弄乱进程内存。)


另一个不安全的 SIGSEGV 是由堆栈溢出引起的,正如 @guest_703 提到的(但没有解释)。另一个例子:

fn main() {
  main();
}

游乐场

请注意

  • 这不被 rustc 视为可观察的行为,并且可能会在发布模式下进行优化。
  • 虽然这是安全 Rust 中的 SIGSEGV,但它并不是不健全/违反内存安全,因为它永远不会导致 UB/对已分配内存的无效访问。
  • stacker
    crate 可用于避免堆栈溢出。

0
投票

NULL 指针在 C 和 Rust 中都会导致段错误。

union Foo<'a>{
    a:i32,
    s:&'a str,
}
fn main() {
    let mut a = Foo{s:"fghgf"};
    a.a = 0;
    unsafe {
        print!("{:?}", a.s);
    }
}

-1
投票
fn main() {
    let mut arr = [5; 1000000000000];
    arr[0] = 3;
    eprintln!("{:?}", &arr[0..1])
}

还有游乐场

产生堆栈溢出 -

Exited with signal 6 (SIGABRT)

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