Skip to content

Commit c159b2b

Browse files
committed
Show backtrace on allocation failures when possible
1 parent 812cd88 commit c159b2b

File tree

4 files changed

+110
-14
lines changed

4 files changed

+110
-14
lines changed

library/std/src/alloc.rs

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

358413
#[cfg(not(test))]
359414
#[doc(hidden)]
360415
#[alloc_error_handler]
361416
#[unstable(feature = "alloc_internals", issue = "none")]
362417
pub fn rust_oom(layout: Layout) -> ! {
363-
let hook = HOOK.load(Ordering::Acquire);
364-
let hook: fn(Layout) =
365-
if hook.is_null() { default_alloc_error_hook } else { unsafe { mem::transmute(hook) } };
366-
hook(layout);
367-
crate::process::abort()
418+
crate::sys::backtrace::__rust_end_short_backtrace(|| {
419+
let hook = HOOK.load(Ordering::Acquire);
420+
let hook: fn(Layout) =
421+
if hook.is_null() { default_alloc_error_hook } else { unsafe { mem::transmute(hook) } };
422+
hook(layout);
423+
crate::process::abort()
424+
})
368425
}
369426

370427
#[cfg(not(test))]

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)