Skip to content

Commit df60c4a

Browse files
authored
Rollup merge of rust-lang#148020 - bjorn3:oom_backtrace, r=Mark-Simulacrum
Show backtrace on allocation failures when possible And if an allocation while printing the backtrace fails, don't try to print another backtrace as that will never succeed. Split out of rust-lang#147725 to allow landing this independently of a decision whether or not to remove `-Zoom=panic`.
2 parents 7a01345 + 91ed7a6 commit df60c4a

File tree

6 files changed

+119
-23
lines changed

6 files changed

+119
-23
lines changed

library/std/src/alloc.rs

Lines changed: 70 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
#![stable(feature = "alloc_module", since = "1.28.0")]
5858

5959
use core::ptr::NonNull;
60-
use core::sync::atomic::{Atomic, AtomicPtr, Ordering};
60+
use core::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
6161
use core::{hint, mem, ptr};
6262

6363
#[stable(feature = "alloc_module", since = "1.28.0")]
@@ -287,7 +287,7 @@ unsafe impl Allocator for System {
287287
}
288288
}
289289

290-
static HOOK: Atomic<*mut ()> = AtomicPtr::new(ptr::null_mut());
290+
static HOOK: AtomicPtr<()> = AtomicPtr::new(ptr::null_mut());
291291

292292
/// Registers a custom allocation error hook, replacing any that was previously registered.
293293
///
@@ -344,7 +344,12 @@ pub fn take_alloc_error_hook() -> fn(Layout) {
344344
if hook.is_null() { default_alloc_error_hook } else { unsafe { mem::transmute(hook) } }
345345
}
346346

347+
#[optimize(size)]
347348
fn default_alloc_error_hook(layout: Layout) {
349+
if cfg!(panic = "immediate-abort") {
350+
return;
351+
}
352+
348353
unsafe extern "Rust" {
349354
// This symbol is emitted by rustc next to __rust_alloc_error_handler.
350355
// Its value depends on the -Zoom={panic,abort} compiler option.
@@ -354,28 +359,79 @@ fn default_alloc_error_hook(layout: Layout) {
354359

355360
if unsafe { __rust_alloc_error_handler_should_panic_v2() != 0 } {
356361
panic!("memory allocation of {} bytes failed", layout.size());
362+
}
363+
364+
// This is the default path taken on OOM, and the only path taken on stable with std.
365+
// Crucially, it does *not* call any user-defined code, and therefore users do not have to
366+
// worry about allocation failure causing reentrancy issues. That makes it different from
367+
// the default `__rdl_alloc_error_handler` defined in alloc (i.e., the default alloc error
368+
// handler that is called when there is no `#[alloc_error_handler]`), which triggers a
369+
// regular panic and thus can invoke a user-defined panic hook, executing arbitrary
370+
// user-defined code.
371+
372+
static PREV_ALLOC_FAILURE: AtomicBool = AtomicBool::new(false);
373+
if PREV_ALLOC_FAILURE.swap(true, Ordering::Relaxed) {
374+
// Don't try to print a backtrace if a previous alloc error happened. This likely means
375+
// there is not enough memory to print a backtrace, although it could also mean that two
376+
// threads concurrently run out of memory.
377+
rtprintpanic!(
378+
"memory allocation of {} bytes failed\nskipping backtrace printing to avoid potential recursion\n",
379+
layout.size()
380+
);
381+
return;
357382
} else {
358-
// This is the default path taken on OOM, and the only path taken on stable with std.
359-
// Crucially, it does *not* call any user-defined code, and therefore users do not have to
360-
// worry about allocation failure causing reentrancy issues. That makes it different from
361-
// the default `__rdl_alloc_error_handler` defined in alloc (i.e., the default alloc error
362-
// handler that is called when there is no `#[alloc_error_handler]`), which triggers a
363-
// regular panic and thus can invoke a user-defined panic hook, executing arbitrary
364-
// user-defined code.
365383
rtprintpanic!("memory allocation of {} bytes failed\n", layout.size());
366384
}
385+
386+
let Some(mut out) = crate::sys::stdio::panic_output() else {
387+
return;
388+
};
389+
390+
// Use a lock to prevent mixed output in multithreading context.
391+
// Some platforms also require it when printing a backtrace, like `SymFromAddr` on Windows.
392+
// Make sure to not take this lock until after checking PREV_ALLOC_FAILURE to avoid deadlocks
393+
// when there is too little memory to print a backtrace.
394+
let mut lock = crate::sys::backtrace::lock();
395+
396+
match crate::panic::get_backtrace_style() {
397+
Some(crate::panic::BacktraceStyle::Short) => {
398+
drop(lock.print(&mut out, crate::backtrace_rs::PrintFmt::Short))
399+
}
400+
Some(crate::panic::BacktraceStyle::Full) => {
401+
drop(lock.print(&mut out, crate::backtrace_rs::PrintFmt::Full))
402+
}
403+
Some(crate::panic::BacktraceStyle::Off) => {
404+
use crate::io::Write;
405+
let _ = writeln!(
406+
out,
407+
"note: run with `RUST_BACKTRACE=1` environment variable to display a \
408+
backtrace"
409+
);
410+
if cfg!(miri) {
411+
let _ = writeln!(
412+
out,
413+
"note: in Miri, you may have to set `MIRIFLAGS=-Zmiri-env-forward=RUST_BACKTRACE` \
414+
for the environment variable to have an effect"
415+
);
416+
}
417+
}
418+
// If backtraces aren't supported or are forced-off, do nothing.
419+
None => {}
420+
}
367421
}
368422

369423
#[cfg(not(test))]
370424
#[doc(hidden)]
371425
#[alloc_error_handler]
372426
#[unstable(feature = "alloc_internals", issue = "none")]
373427
pub fn rust_oom(layout: Layout) -> ! {
374-
let hook = HOOK.load(Ordering::Acquire);
375-
let hook: fn(Layout) =
376-
if hook.is_null() { default_alloc_error_hook } else { unsafe { mem::transmute(hook) } };
377-
hook(layout);
378-
crate::process::abort()
428+
crate::sys::backtrace::__rust_end_short_backtrace(|| {
429+
let hook = HOOK.load(Ordering::Acquire);
430+
let hook: fn(Layout) =
431+
if hook.is_null() { default_alloc_error_hook } else { unsafe { mem::transmute(hook) } };
432+
hook(layout);
433+
crate::process::abort()
434+
})
379435
}
380436

381437
#[cfg(not(test))]

library/std/src/panicking.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,6 @@ fn default_hook(info: &PanicHookInfo<'_>) {
293293
static FIRST_PANIC: Atomic<bool> = AtomicBool::new(true);
294294

295295
match backtrace {
296-
// SAFETY: we took out a lock just a second ago.
297296
Some(BacktraceStyle::Short) => {
298297
drop(lock.print(err, crate::backtrace_rs::PrintFmt::Short))
299298
}

library/std/src/sys/backtrace.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ pub(crate) fn lock<'a>() -> BacktraceLock<'a> {
2020

2121
impl BacktraceLock<'_> {
2222
/// Prints the current backtrace.
23-
///
24-
/// NOTE: this function is not Sync. The caller must hold a mutex lock, or there must be only one thread in the program.
2523
pub(crate) fn print(&mut self, w: &mut dyn Write, format: PrintFmt) -> io::Result<()> {
2624
// There are issues currently linking libbacktrace into tests, and in
2725
// general during std's own unit tests we're not testing this path. In
@@ -36,13 +34,17 @@ impl BacktraceLock<'_> {
3634
}
3735
impl fmt::Display for DisplayBacktrace {
3836
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
37+
// SAFETY: the backtrace lock is held
3938
unsafe { _print_fmt(fmt, self.format) }
4039
}
4140
}
4241
write!(w, "{}", DisplayBacktrace { format })
4342
}
4443
}
4544

45+
/// # Safety
46+
///
47+
/// This function is not Sync. The caller must hold a mutex lock, or there must be only one thread in the program.
4648
unsafe fn _print_fmt(fmt: &mut fmt::Formatter<'_>, print_fmt: PrintFmt) -> fmt::Result {
4749
// Always 'fail' to get the cwd when running under Miri -
4850
// this allows Miri to display backtraces in isolation mode

src/tools/miri/tests/fail/alloc/alloc_error_handler.stderr

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
memory allocation of 4 bytes failed
2+
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
3+
note: in Miri, you may have to set `MIRIFLAGS=-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect
24
error: abnormal termination: the program aborted execution
35
--> RUSTLIB/std/src/alloc.rs:LL:CC
46
|
5-
LL | crate::process::abort()
6-
| ^^^^^^^^^^^^^^^^^^^^^^^ abnormal termination occurred here
7+
LL | crate::process::abort()
8+
| ^^^^^^^^^^^^^^^^^^^^^^^ abnormal termination occurred here
79
|
810
= note: BACKTRACE:
11+
= note: inside closure at RUSTLIB/std/src/alloc.rs:LL:CC
12+
= note: inside `std::sys::backtrace::__rust_end_short_backtrace::<{closure@std::alloc::rust_oom::{closure#0}}, !>` at RUSTLIB/std/src/sys/backtrace.rs:LL:CC
913
= note: inside `std::alloc::rust_oom` at RUSTLIB/std/src/alloc.rs:LL:CC
1014
= note: inside `std::alloc::_::__rust_alloc_error_handler` at RUSTLIB/std/src/alloc.rs:LL:CC
1115
= note: inside `std::alloc::handle_alloc_error::rt_error` at RUSTLIB/alloc/src/alloc.rs:LL:CC
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//@ run-pass
2+
//@ ignore-android FIXME #17520
3+
//@ needs-subprocess
4+
//@ ignore-openbsd no support for libbacktrace without filename
5+
//@ ignore-fuchsia Backtraces not symbolized
6+
//@ compile-flags:-g
7+
//@ compile-flags:-Cstrip=none
8+
9+
use std::alloc::{Layout, handle_alloc_error};
10+
use std::process::Command;
11+
use std::{env, str};
12+
13+
fn main() {
14+
if env::args().len() > 1 {
15+
handle_alloc_error(Layout::new::<[u8; 42]>())
16+
}
17+
18+
let me = env::current_exe().unwrap();
19+
let output = Command::new(&me).env("RUST_BACKTRACE", "1").arg("next").output().unwrap();
20+
assert!(!output.status.success(), "{:?} is a success", output.status);
21+
22+
let mut stderr = str::from_utf8(&output.stderr).unwrap();
23+
24+
// When running inside QEMU user-mode emulation, there will be an extra message printed by QEMU
25+
// in the stderr whenever a core dump happens. Remove it before the check.
26+
stderr = stderr
27+
.strip_suffix("qemu: uncaught target signal 6 (Aborted) - core dumped\n")
28+
.unwrap_or(stderr);
29+
30+
assert!(stderr.contains("memory allocation of 42 bytes failed"), "{}", stderr);
31+
assert!(stderr.contains("alloc_error_backtrace::main"), "{}", stderr);
32+
}

tests/ui/alloc-error/default-alloc-error-hook.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,16 @@
22
//@ needs-subprocess
33

44
use std::alloc::{Layout, handle_alloc_error};
5-
use std::env;
65
use std::process::Command;
7-
use std::str;
6+
use std::{env, str};
87

98
fn main() {
109
if env::args().len() > 1 {
1110
handle_alloc_error(Layout::new::<[u8; 42]>())
1211
}
1312

1413
let me = env::current_exe().unwrap();
15-
let output = Command::new(&me).arg("next").output().unwrap();
14+
let output = Command::new(&me).env("RUST_BACKTRACE", "0").arg("next").output().unwrap();
1615
assert!(!output.status.success(), "{:?} is a success", output.status);
1716

1817
let mut stderr = str::from_utf8(&output.stderr).unwrap();
@@ -23,5 +22,9 @@ fn main() {
2322
.strip_suffix("qemu: uncaught target signal 6 (Aborted) - core dumped\n")
2423
.unwrap_or(stderr);
2524

26-
assert_eq!(stderr, "memory allocation of 42 bytes failed\n");
25+
assert_eq!(
26+
stderr,
27+
"memory allocation of 42 bytes failed\n\
28+
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace\n"
29+
);
2730
}

0 commit comments

Comments
 (0)