diff --git a/compiler/rustc_codegen_llvm/src/allocator.rs b/compiler/rustc_codegen_llvm/src/allocator.rs
index bc1d9e1818c2f..e028b2c2dc763 100644
--- a/compiler/rustc_codegen_llvm/src/allocator.rs
+++ b/compiler/rustc_codegen_llvm/src/allocator.rs
@@ -3,11 +3,17 @@ use libc::c_uint;
 use rustc_ast::expand::allocator::{AllocatorKind, AllocatorTy, ALLOCATOR_METHODS};
 use rustc_middle::bug;
 use rustc_middle::ty::TyCtxt;
+use rustc_span::symbol::sym;
 
 use crate::llvm::{self, False, True};
 use crate::ModuleLlvm;
 
-pub(crate) unsafe fn codegen(tcx: TyCtxt<'_>, mods: &mut ModuleLlvm, kind: AllocatorKind) {
+pub(crate) unsafe fn codegen(
+    tcx: TyCtxt<'_>,
+    mods: &mut ModuleLlvm,
+    kind: AllocatorKind,
+    has_alloc_error_handler: bool,
+) {
     let llcx = &*mods.llcx;
     let llmod = mods.llmod();
     let usize = match &tcx.sess.target.target.target_pointer_width[..] {
@@ -82,4 +88,41 @@ pub(crate) unsafe fn codegen(tcx: TyCtxt<'_>, mods: &mut ModuleLlvm, kind: Alloc
         }
         llvm::LLVMDisposeBuilder(llbuilder);
     }
+
+    // rust alloc error handler
+    let args = [usize, usize]; // size, align
+
+    let ty = llvm::LLVMFunctionType(void, args.as_ptr(), args.len() as c_uint, False);
+    let name = format!("__rust_alloc_error_handler");
+    let llfn = llvm::LLVMRustGetOrInsertFunction(llmod, name.as_ptr().cast(), name.len(), ty);
+    // -> ! DIFlagNoReturn
+    llvm::Attribute::NoReturn.apply_llfn(llvm::AttributePlace::Function, llfn);
+
+    if tcx.sess.target.target.options.default_hidden_visibility {
+        llvm::LLVMRustSetVisibility(llfn, llvm::Visibility::Hidden);
+    }
+    if tcx.sess.must_emit_unwind_tables() {
+        attributes::emit_uwtable(llfn, true);
+    }
+
+    let kind = if has_alloc_error_handler { AllocatorKind::Global } else { AllocatorKind::Default };
+    let callee = kind.fn_name(sym::oom);
+    let callee = llvm::LLVMRustGetOrInsertFunction(llmod, callee.as_ptr().cast(), callee.len(), ty);
+    // -> ! DIFlagNoReturn
+    llvm::Attribute::NoReturn.apply_llfn(llvm::AttributePlace::Function, callee);
+    llvm::LLVMRustSetVisibility(callee, llvm::Visibility::Hidden);
+
+    let llbb = llvm::LLVMAppendBasicBlockInContext(llcx, llfn, "entry\0".as_ptr().cast());
+
+    let llbuilder = llvm::LLVMCreateBuilderInContext(llcx);
+    llvm::LLVMPositionBuilderAtEnd(llbuilder, llbb);
+    let args = args
+        .iter()
+        .enumerate()
+        .map(|(i, _)| llvm::LLVMGetParam(llfn, i as c_uint))
+        .collect::<Vec<_>>();
+    let ret = llvm::LLVMRustBuildCall(llbuilder, callee, args.as_ptr(), args.len() as c_uint, None);
+    llvm::LLVMSetTailCall(ret, True);
+    llvm::LLVMBuildRetVoid(llbuilder);
+    llvm::LLVMDisposeBuilder(llbuilder);
 }
diff --git a/compiler/rustc_codegen_llvm/src/lib.rs b/compiler/rustc_codegen_llvm/src/lib.rs
index f14493e604368..1237b39b300ff 100644
--- a/compiler/rustc_codegen_llvm/src/lib.rs
+++ b/compiler/rustc_codegen_llvm/src/lib.rs
@@ -95,8 +95,9 @@ impl ExtraBackendMethods for LlvmCodegenBackend {
         tcx: TyCtxt<'tcx>,
         mods: &mut ModuleLlvm,
         kind: AllocatorKind,
+        has_alloc_error_handler: bool,
     ) {
-        unsafe { allocator::codegen(tcx, mods, kind) }
+        unsafe { allocator::codegen(tcx, mods, kind, has_alloc_error_handler) }
     }
     fn compile_codegen_unit(
         &self,
diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs
index d82fc2c9f63d9..8e6f8e193c0e2 100644
--- a/compiler/rustc_codegen_ssa/src/base.rs
+++ b/compiler/rustc_codegen_ssa/src/base.rs
@@ -538,8 +538,9 @@ pub fn codegen_crate<B: ExtraBackendMethods>(
         let llmod_id =
             cgu_name_builder.build_cgu_name(LOCAL_CRATE, &["crate"], Some("allocator")).to_string();
         let mut modules = backend.new_metadata(tcx, &llmod_id);
-        tcx.sess
-            .time("write_allocator_module", || backend.codegen_allocator(tcx, &mut modules, kind));
+        tcx.sess.time("write_allocator_module", || {
+            backend.codegen_allocator(tcx, &mut modules, kind, tcx.lang_items().oom().is_some())
+        });
 
         Some(ModuleCodegen { name: llmod_id, module_llvm: modules, kind: ModuleKind::Allocator })
     } else {
diff --git a/compiler/rustc_codegen_ssa/src/traits/backend.rs b/compiler/rustc_codegen_ssa/src/traits/backend.rs
index 90520f77e3c04..48c07b0089420 100644
--- a/compiler/rustc_codegen_ssa/src/traits/backend.rs
+++ b/compiler/rustc_codegen_ssa/src/traits/backend.rs
@@ -109,6 +109,7 @@ pub trait ExtraBackendMethods: CodegenBackend + WriteBackendMethods + Sized + Se
         tcx: TyCtxt<'tcx>,
         mods: &mut Self::Module,
         kind: AllocatorKind,
+        has_alloc_error_handler: bool,
     );
     /// This generates the codegen unit and returns it along with
     /// a `u64` giving an estimate of the unit's processing cost.
diff --git a/compiler/rustc_feature/src/active.rs b/compiler/rustc_feature/src/active.rs
index 060efd270dd51..5cd0a56d52414 100644
--- a/compiler/rustc_feature/src/active.rs
+++ b/compiler/rustc_feature/src/active.rs
@@ -593,6 +593,9 @@ declare_features! (
     /// Allows to use the `#[cmse_nonsecure_entry]` attribute.
     (active, cmse_nonsecure_entry, "1.48.0", Some(75835), None),
 
+    /// Allows rustc to inject a default alloc_error_handler
+    (active, default_alloc_error_handler, "1.48.0", Some(66741), None),
+
     // -------------------------------------------------------------------------
     // feature-group-end: actual feature gates
     // -------------------------------------------------------------------------
diff --git a/compiler/rustc_passes/src/weak_lang_items.rs b/compiler/rustc_passes/src/weak_lang_items.rs
index 6bc2110bfb3e6..effb25b022452 100644
--- a/compiler/rustc_passes/src/weak_lang_items.rs
+++ b/compiler/rustc_passes/src/weak_lang_items.rs
@@ -64,7 +64,10 @@ fn verify<'tcx>(tcx: TyCtxt<'tcx>, items: &lang_items::LanguageItems) {
             if item == LangItem::PanicImpl {
                 tcx.sess.err("`#[panic_handler]` function required, but not found");
             } else if item == LangItem::Oom {
-                tcx.sess.err("`#[alloc_error_handler]` function required, but not found");
+                if !tcx.features().default_alloc_error_handler {
+                    tcx.sess.err("`#[alloc_error_handler]` function required, but not found.");
+                    tcx.sess.note_without_error("Use `#![feature(default_alloc_error_handler)]` for a default error handler.");
+                }
             } else {
                 tcx.sess.err(&format!("language item required, but not found: `{}`", name));
             }
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index e3ad31469b237..6309b00f5f514 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -415,6 +415,7 @@ symbols! {
         decl_macro,
         declare_lint_pass,
         decode,
+        default_alloc_error_handler,
         default_lib_allocator,
         default_type_parameter_fallback,
         default_type_params,
diff --git a/library/alloc/src/alloc.rs b/library/alloc/src/alloc.rs
index 75158eefcac28..ce70de6ebdd63 100644
--- a/library/alloc/src/alloc.rs
+++ b/library/alloc/src/alloc.rs
@@ -26,6 +26,8 @@ extern "Rust" {
     fn __rust_realloc(ptr: *mut u8, old_size: usize, align: usize, new_size: usize) -> *mut u8;
     #[rustc_allocator_nounwind]
     fn __rust_alloc_zeroed(size: usize, align: usize) -> *mut u8;
+    #[rustc_allocator_nounwind]
+    fn __rust_alloc_error_handler(size: usize, align: usize) -> !;
 }
 
 /// The global memory allocator.
@@ -334,6 +336,24 @@ pub(crate) unsafe fn box_free<T: ?Sized>(ptr: Unique<T>) {
 /// [`set_alloc_error_hook`]: ../../std/alloc/fn.set_alloc_error_hook.html
 /// [`take_alloc_error_hook`]: ../../std/alloc/fn.take_alloc_error_hook.html
 #[stable(feature = "global_alloc", since = "1.28.0")]
+#[cfg(not(any(test, bootstrap)))]
+#[rustc_allocator_nounwind]
+pub fn handle_alloc_error(layout: Layout) -> ! {
+    unsafe {
+        __rust_alloc_error_handler(layout.size(), layout.align());
+    }
+}
+
+// For alloc test `std::alloc::handle_alloc_error` can be used directly.
+#[cfg(test)]
+pub use std::alloc::handle_alloc_error;
+
+// In stage0 (bootstrap) `__rust_alloc_error_handler`,
+// might not be generated yet, because an old compiler is used,
+// so use the old direct call.
+#[cfg(all(bootstrap, not(test)))]
+#[stable(feature = "global_alloc", since = "1.28.0")]
+#[doc(hidden)]
 #[rustc_allocator_nounwind]
 pub fn handle_alloc_error(layout: Layout) -> ! {
     extern "Rust" {
@@ -342,3 +362,30 @@ pub fn handle_alloc_error(layout: Layout) -> ! {
     }
     unsafe { oom_impl(layout) }
 }
+
+#[cfg(not(any(test, bootstrap)))]
+#[doc(hidden)]
+#[allow(unused_attributes)]
+#[unstable(feature = "alloc_internals", issue = "none")]
+pub mod __default_lib_allocator {
+    use crate::alloc::Layout;
+
+    // called via generated `__rust_alloc_error_handler`
+
+    // if there is no `#[alloc_error_handler]`
+    #[rustc_std_internal_symbol]
+    pub unsafe extern "C" fn __rdl_oom(size: usize, _align: usize) -> ! {
+        panic!("memory allocation of {} bytes failed", size)
+    }
+
+    // if there is a `#[alloc_error_handler]`
+    #[rustc_std_internal_symbol]
+    pub unsafe extern "C" fn __rg_oom(size: usize, align: usize) -> ! {
+        let layout = unsafe { Layout::from_size_align_unchecked(size, align) };
+        extern "Rust" {
+            #[lang = "oom"]
+            fn oom_impl(layout: Layout) -> !;
+        }
+        unsafe { oom_impl(layout) }
+    }
+}
diff --git a/src/test/ui/allocator/auxiliary/helper.rs b/src/test/ui/allocator/auxiliary/helper.rs
index 7f6770c226a0c..008fb3501d90d 100644
--- a/src/test/ui/allocator/auxiliary/helper.rs
+++ b/src/test/ui/allocator/auxiliary/helper.rs
@@ -1,8 +1,10 @@
 // no-prefer-dynamic
 
 #![crate_type = "rlib"]
+#![no_std]
 
-use std::fmt;
+extern crate alloc;
+use alloc::fmt;
 
 pub fn work_with(p: &fmt::Debug) {
     drop(p);
diff --git a/src/test/ui/allocator/no_std-alloc-error-handler-custom.rs b/src/test/ui/allocator/no_std-alloc-error-handler-custom.rs
new file mode 100644
index 0000000000000..f09fafbc98a18
--- /dev/null
+++ b/src/test/ui/allocator/no_std-alloc-error-handler-custom.rs
@@ -0,0 +1,97 @@
+// run-pass
+// ignore-android no libc
+// ignore-cloudabi no libc
+// ignore-emscripten no libc
+// ignore-sgx no libc
+// ignore-wasm32 no libc
+// only-linux
+// compile-flags:-C panic=abort
+// aux-build:helper.rs
+
+#![feature(start, rustc_private, new_uninit, panic_info_message)]
+#![feature(alloc_error_handler)]
+#![no_std]
+
+extern crate alloc;
+extern crate libc;
+
+// ARM targets need these symbols
+#[no_mangle]
+pub fn __aeabi_unwind_cpp_pr0() {}
+
+#[no_mangle]
+pub fn __aeabi_unwind_cpp_pr1() {}
+
+use core::ptr::null_mut;
+use core::alloc::{GlobalAlloc, Layout};
+use alloc::boxed::Box;
+
+extern crate helper;
+
+struct MyAllocator;
+
+#[alloc_error_handler]
+fn my_oom(layout: Layout) -> !
+{
+    use alloc::fmt::write;
+    unsafe {
+        let size = layout.size();
+        let mut s = alloc::string::String::new();
+        write(&mut s, format_args!("My OOM: failed to allocate {} bytes!\n", size)).unwrap();
+        let s = s.as_str();
+        libc::write(libc::STDERR_FILENO, s as *const _ as _, s.len());
+        libc::exit(0)
+    }
+}
+
+unsafe impl GlobalAlloc for MyAllocator {
+    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
+        if layout.size() < 4096 {
+            libc::malloc(layout.size()) as _
+        } else {
+            null_mut()
+        }
+    }
+    unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {}
+}
+
+#[global_allocator]
+static A: MyAllocator = MyAllocator;
+
+#[panic_handler]
+fn panic(panic_info: &core::panic::PanicInfo) -> ! {
+    unsafe {
+        if let Some(s) = panic_info.payload().downcast_ref::<&str>() {
+            const PSTR: &str = "panic occurred: ";
+            const CR: &str = "\n";
+            libc::write(libc::STDERR_FILENO, PSTR as *const _ as _, PSTR.len());
+            libc::write(libc::STDERR_FILENO, s as *const _ as _, s.len());
+            libc::write(libc::STDERR_FILENO, CR as *const _ as _, CR.len());
+        }
+        if let Some(args) = panic_info.message() {
+            let mut s = alloc::string::String::new();
+            alloc::fmt::write(&mut s, *args).unwrap();
+            let s = s.as_str();
+            const PSTR: &str = "panic occurred: ";
+            const CR: &str = "\n";
+            libc::write(libc::STDERR_FILENO, PSTR as *const _ as _, PSTR.len());
+            libc::write(libc::STDERR_FILENO, s as *const _ as _, s.len());
+            libc::write(libc::STDERR_FILENO, CR as *const _ as _, CR.len());
+        } else {
+            const PSTR: &str = "panic occurred\n";
+            libc::write(libc::STDERR_FILENO, PSTR as *const _ as _, PSTR.len());
+        }
+        libc::exit(1)
+    }
+}
+
+#[derive(Debug)]
+struct Page([[u64; 32]; 16]);
+
+#[start]
+pub fn main(_argc: isize, _argv: *const *const u8) -> isize {
+    let zero = Box::<Page>::new_zeroed();
+    let zero = unsafe { zero.assume_init() };
+    helper::work_with(&zero);
+    1
+}
diff --git a/src/test/ui/allocator/no_std-alloc-error-handler-default.rs b/src/test/ui/allocator/no_std-alloc-error-handler-default.rs
new file mode 100644
index 0000000000000..4d68160379d9c
--- /dev/null
+++ b/src/test/ui/allocator/no_std-alloc-error-handler-default.rs
@@ -0,0 +1,84 @@
+// run-pass
+// ignore-android no libc
+// ignore-cloudabi no libc
+// ignore-emscripten no libc
+// ignore-sgx no libc
+// ignore-wasm32 no libc
+// only-linux
+// compile-flags:-C panic=abort
+// aux-build:helper.rs
+// gate-test-default_alloc_error_handler
+
+#![feature(start, rustc_private, new_uninit, panic_info_message)]
+#![feature(default_alloc_error_handler)]
+#![no_std]
+
+extern crate alloc;
+extern crate libc;
+
+// ARM targets need these symbols
+#[no_mangle]
+pub fn __aeabi_unwind_cpp_pr0() {}
+
+#[no_mangle]
+pub fn __aeabi_unwind_cpp_pr1() {}
+
+use alloc::boxed::Box;
+use core::alloc::{GlobalAlloc, Layout};
+use core::ptr::null_mut;
+
+extern crate helper;
+
+struct MyAllocator;
+
+unsafe impl GlobalAlloc for MyAllocator {
+    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
+        if layout.size() < 4096 {
+            libc::malloc(layout.size()) as _
+        } else {
+            null_mut()
+        }
+    }
+    unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {}
+}
+
+#[global_allocator]
+static A: MyAllocator = MyAllocator;
+
+#[panic_handler]
+fn panic(panic_info: &core::panic::PanicInfo) -> ! {
+    unsafe {
+        if let Some(s) = panic_info.payload().downcast_ref::<&str>() {
+            const PSTR: &str = "panic occurred: ";
+            const CR: &str = "\n";
+            libc::write(libc::STDERR_FILENO, PSTR as *const _ as _, PSTR.len());
+            libc::write(libc::STDERR_FILENO, s as *const _ as _, s.len());
+            libc::write(libc::STDERR_FILENO, CR as *const _ as _, CR.len());
+        }
+        if let Some(args) = panic_info.message() {
+            let mut s = alloc::string::String::new();
+            alloc::fmt::write(&mut s, *args).unwrap();
+            let s = s.as_str();
+            const PSTR: &str = "panic occurred: ";
+            const CR: &str = "\n";
+            libc::write(libc::STDERR_FILENO, PSTR as *const _ as _, PSTR.len());
+            libc::write(libc::STDERR_FILENO, s as *const _ as _, s.len());
+            libc::write(libc::STDERR_FILENO, CR as *const _ as _, CR.len());
+        } else {
+            const PSTR: &str = "panic occurred\n";
+            libc::write(libc::STDERR_FILENO, PSTR as *const _ as _, PSTR.len());
+        }
+        libc::exit(0)
+    }
+}
+
+#[derive(Debug)]
+struct Page([[u64; 32]; 16]);
+
+#[start]
+pub fn main(_argc: isize, _argv: *const *const u8) -> isize {
+    let zero = Box::<Page>::new_zeroed();
+    let zero = unsafe { zero.assume_init() };
+    helper::work_with(&zero);
+    1
+}
diff --git a/src/test/ui/missing/missing-alloc_error_handler.stderr b/src/test/ui/missing/missing-alloc_error_handler.stderr
index 5489b2cbbfad8..511d0788b40a7 100644
--- a/src/test/ui/missing/missing-alloc_error_handler.stderr
+++ b/src/test/ui/missing/missing-alloc_error_handler.stderr
@@ -1,4 +1,6 @@
-error: `#[alloc_error_handler]` function required, but not found
+error: `#[alloc_error_handler]` function required, but not found.
+
+note: Use `#![feature(default_alloc_error_handler)]` for a default error handler.
 
 error: aborting due to previous error