From 5f0fc0c00148c6128ee9354ba7360b386a59bef5 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Mon, 31 Jul 2017 23:23:20 -0600 Subject: [PATCH 1/3] Add unwinding strategy for panics --- ctr-std/src/collections/hash/table.rs | 6 +- ctr-std/src/lib.rs | 21 +- ctr-std/src/panic.rs | 11 +- ctr-std/src/panicking.rs | 616 ++++++++++++++++++++++---- ctr-std/src/rt.rs | 5 +- ctr-std/src/sys_common/mod.rs | 4 + examples/.cargo/config | 3 + examples/3ds.json | 1 - examples/Cargo.toml | 4 + examples/Xargo.toml | 2 + 10 files changed, 557 insertions(+), 116 deletions(-) diff --git a/ctr-std/src/collections/hash/table.rs b/ctr-std/src/collections/hash/table.rs index 06f4f76..3844690 100644 --- a/ctr-std/src/collections/hash/table.rs +++ b/ctr-std/src/collections/hash/table.rs @@ -44,7 +44,7 @@ impl TaggedHashUintPtr { #[inline] unsafe fn new(ptr: *mut HashUint) -> Self { debug_assert!(ptr as usize & 1 == 0 || ptr as usize == EMPTY as usize); - TaggedHashUintPtr(Unique::new(ptr)) + TaggedHashUintPtr(Unique::new_unchecked(ptr)) } #[inline] @@ -56,7 +56,7 @@ impl TaggedHashUintPtr { } else { usize_ptr &= !1; } - self.0 = Unique::new(usize_ptr as *mut HashUint) + self.0 = Unique::new_unchecked(usize_ptr as *mut HashUint) } } @@ -877,7 +877,7 @@ impl RawTable { elems_left: elems_left, marker: marker::PhantomData, }, - table: unsafe { Shared::new(self) }, + table: Shared::from(self), marker: marker::PhantomData, } } diff --git a/ctr-std/src/lib.rs b/ctr-std/src/lib.rs index 99b77a2..28ab259 100644 --- a/ctr-std/src/lib.rs +++ b/ctr-std/src/lib.rs @@ -26,6 +26,7 @@ #![feature(lang_items)] #![feature(macro_reexport)] #![feature(needs_drop)] +#![feature(needs_panic_runtime)] #![feature(oom)] #![feature(on_unimplemented)] #![feature(optin_builtin_traits)] @@ -45,10 +46,13 @@ #![feature(unicode)] #![feature(unique)] #![feature(untagged_unions)] +#![feature(unwind_attributes)] #![feature(zero_one)] #![allow(non_camel_case_types, dead_code, unused_features)] #![no_std] +#![needs_panic_runtime] + #![cfg_attr(not(stage0), default_lib_allocator)] #![stable(feature = "rust1", since = "1.0.0")] @@ -197,20 +201,3 @@ pub mod rt; pub mod __rand { pub use rand::{thread_rng, ThreadRng, Rng}; } - -// NOTE: These two are "undefined" symbols that LLVM emits but that -// we never actually use -#[doc(hidden)] - -#[stable(feature = "3ds", since = "1.0.0")] -#[no_mangle] -pub unsafe extern "C" fn __aeabi_unwind_cpp_pr0() { - intrinsics::unreachable() -} - -#[stable(feature = "3ds", since = "1.0.0")] -#[doc(hidden)] -#[no_mangle] -pub unsafe extern "C" fn __aeabi_unwind_cpp_pr1() { - intrinsics::unreachable() -} diff --git a/ctr-std/src/panic.rs b/ctr-std/src/panic.rs index 2e037cd..58356bc 100644 --- a/ctr-std/src/panic.rs +++ b/ctr-std/src/panic.rs @@ -22,8 +22,8 @@ use rc::Rc; use sync::{Arc, Mutex, RwLock, atomic}; use thread::Result; -//#[stable(feature = "panic_hooks", since = "1.10.0")] -//pub use panicking::{take_hook, set_hook, PanicInfo, Location}; +#[stable(feature = "panic_hooks", since = "1.10.0")] +pub use panicking::{take_hook, set_hook, PanicInfo, Location}; /// A marker trait which represents "panic safe" types in Rust. /// @@ -112,7 +112,7 @@ pub trait UnwindSafe {} /// This is a "helper marker trait" used to provide impl blocks for the /// `UnwindSafe` trait, for more information see that documentation. #[stable(feature = "catch_unwind", since = "1.9.0")] -#[rustc_on_unimplemented = "the type {Self} contains interior mutability \ +#[rustc_on_unimplemented = "the type {Self} may contain interior mutability \ and a reference may not be safely transferrable \ across a catch_unwind boundary"] pub trait RefUnwindSafe {} @@ -388,7 +388,6 @@ pub fn catch_unwind R + UnwindSafe, R>(f: F) -> Result { /// } /// ``` #[stable(feature = "resume_unwind", since = "1.9.0")] -// we always abort so I'm pretty sure there's no reason to ever call this -pub fn resume_unwind(_payload: Box) -> ! { - unimplemented!() +pub fn resume_unwind(payload: Box) -> ! { + panicking::update_count_then_panic(payload) } diff --git a/ctr-std/src/panicking.rs b/ctr-std/src/panicking.rs index 26914ce..c2e0832 100644 --- a/ctr-std/src/panicking.rs +++ b/ctr-std/src/panicking.rs @@ -10,16 +10,26 @@ //! Implementation of various bits and pieces of the `panic!` macro and //! associated runtime pieces. +//! +//! Specifically, this module contains the implementation of: +//! +//! * Panic hooks +//! * Executing a panic up to doing the actual implementation +//! * Shims around "try" use io::prelude::*; use any::Any; use cell::RefCell; -use fmt::{self, Display}; +use fmt; +use intrinsics; use mem; use ptr; use raw; +use sys_common::rwlock::RWLock; use sys_common::thread_info; +use sys_common::util; +use thread; thread_local! { pub static LOCAL_STDERR: RefCell>> = { @@ -27,97 +37,416 @@ thread_local! { } } -///The compiler wants this to be here. Otherwise it won't be happy. And we like happy compilers. -#[lang = "eh_personality"] -pub extern fn eh_personality() {} +#[allow(improper_ctypes)] +extern { + fn __rust_maybe_catch_panic(f: fn(*mut u8), + data: *mut u8, + data_ptr: *mut usize, + vtable_ptr: *mut usize) -> u32; + #[unwind] + fn __rust_start_panic(data: usize, vtable: usize) -> u32; +} -/// Determines whether the current thread is unwinding because of panic. -// Which it won't be, since we still don't have stack unwinding. -pub fn panicking() -> bool { - false +#[derive(Copy, Clone)] +enum Hook { + Default, + Custom(*mut (Fn(&PanicInfo) + 'static + Sync + Send)), } -/// Entry point of panic from the libcore crate. -#[lang = "panic_fmt"] -pub extern fn rust_begin_panic(msg: fmt::Arguments, - file: &'static str, - line: u32, - col: u32) -> ! { - begin_panic_fmt(&msg, &(file, line, col)) +static HOOK_LOCK: RWLock = RWLock::new(); +static mut HOOK: Hook = Hook::Default; + +/// Registers a custom panic hook, replacing any that was previously registered. +/// +/// The panic hook is invoked when a thread panics, but before the panic runtime +/// is invoked. As such, the hook will run with both the aborting and unwinding +/// runtimes. The default hook prints a message to standard error and generates +/// a backtrace if requested, but this behavior can be customized with the +/// `set_hook` and `take_hook` functions. +/// +/// The hook is provided with a `PanicInfo` struct which contains information +/// about the origin of the panic, including the payload passed to `panic!` and +/// the source code location from which the panic originated. +/// +/// The panic hook is a global resource. +/// +/// # Panics +/// +/// Panics if called from a panicking thread. +/// +/// # Examples +/// +/// The following will print "Custom panic hook": +/// +/// ```should_panic +/// use std::panic; +/// +/// panic::set_hook(Box::new(|_| { +/// println!("Custom panic hook"); +/// })); +/// +/// panic!("Normal panic"); +/// ``` +#[stable(feature = "panic_hooks", since = "1.10.0")] +pub fn set_hook(hook: Box) { + if thread::panicking() { + panic!("cannot modify the panic hook from a panicking thread"); + } + + unsafe { + HOOK_LOCK.write(); + let old_hook = HOOK; + HOOK = Hook::Custom(Box::into_raw(hook)); + HOOK_LOCK.write_unlock(); + + if let Hook::Custom(ptr) = old_hook { + Box::from_raw(ptr); + } + } } -/// The entry point for panicking with a formatted message. +/// Unregisters the current panic hook, returning it. /// -/// This is designed to reduce the amount of code required at the call -/// site as much as possible (so that `panic!()` has as low an impact -/// on (e.g.) the inlining of other functions as possible), by moving -/// the actual formatting into this shared place. -#[unstable(feature = "libstd_sys_internals", - reason = "used by the panic! macro", - issue = "0")] -#[inline(never)] #[cold] -pub fn begin_panic_fmt(msg: &fmt::Arguments, file_line_col: &(&'static str, u32, u32)) -> ! { - use fmt::Write; +/// If no custom hook is registered, the default hook will be returned. +/// +/// # Panics +/// +/// Panics if called from a panicking thread. +/// +/// # Examples +/// +/// The following will print "Normal panic": +/// +/// ```should_panic +/// use std::panic; +/// +/// panic::set_hook(Box::new(|_| { +/// println!("Custom panic hook"); +/// })); +/// +/// let _ = panic::take_hook(); +/// +/// panic!("Normal panic"); +/// ``` +#[stable(feature = "panic_hooks", since = "1.10.0")] +pub fn take_hook() -> Box { + if thread::panicking() { + panic!("cannot modify the panic hook from a panicking thread"); + } - let mut s = String::new(); - let _ = s.write_fmt(*msg); - begin_panic(s, file_line_col); + unsafe { + HOOK_LOCK.write(); + let hook = HOOK; + HOOK = Hook::Default; + HOOK_LOCK.write_unlock(); + + match hook { + Hook::Default => Box::new(default_hook), + Hook::Custom(ptr) => Box::from_raw(ptr), + } + } +} + +/// A struct providing information about a panic. +/// +/// `PanicInfo` structure is passed to a panic hook set by the [`set_hook`] +/// function. +/// +/// [`set_hook`]: ../../std/panic/fn.set_hook.html +/// +/// # Examples +/// +/// ```should_panic +/// use std::panic; +/// +/// panic::set_hook(Box::new(|panic_info| { +/// println!("panic occured: {:?}", panic_info.payload().downcast_ref::<&str>().unwrap()); +/// })); +/// +/// panic!("Normal panic"); +/// ``` +#[stable(feature = "panic_hooks", since = "1.10.0")] +#[derive(Debug)] +pub struct PanicInfo<'a> { + payload: &'a (Any + Send), + location: Location<'a>, +} + +impl<'a> PanicInfo<'a> { + /// Returns the payload associated with the panic. + /// + /// This will commonly, but not always, be a `&'static str` or [`String`]. + /// + /// [`String`]: ../../std/string/struct.String.html + /// + /// # Examples + /// + /// ```should_panic + /// use std::panic; + /// + /// panic::set_hook(Box::new(|panic_info| { + /// println!("panic occured: {:?}", panic_info.payload().downcast_ref::<&str>().unwrap()); + /// })); + /// + /// panic!("Normal panic"); + /// ``` + #[stable(feature = "panic_hooks", since = "1.10.0")] + pub fn payload(&self) -> &(Any + Send) { + self.payload + } + + /// Returns information about the location from which the panic originated, + /// if available. + /// + /// This method will currently always return [`Some`], but this may change + /// in future versions. + /// + /// [`Some`]: ../../std/option/enum.Option.html#variant.Some + /// + /// # Examples + /// + /// ```should_panic + /// use std::panic; + /// + /// panic::set_hook(Box::new(|panic_info| { + /// if let Some(location) = panic_info.location() { + /// println!("panic occured in file '{}' at line {}", location.file(), location.line()); + /// } else { + /// println!("panic occured but can't get location information..."); + /// } + /// })); + /// + /// panic!("Normal panic"); + /// ``` + #[stable(feature = "panic_hooks", since = "1.10.0")] + pub fn location(&self) -> Option<&Location> { + Some(&self.location) + } +} + +/// A struct containing information about the location of a panic. +/// +/// This structure is created by the [`location`] method of [`PanicInfo`]. +/// +/// [`location`]: ../../std/panic/struct.PanicInfo.html#method.location +/// [`PanicInfo`]: ../../std/panic/struct.PanicInfo.html +/// +/// # Examples +/// +/// ```should_panic +/// use std::panic; +/// +/// panic::set_hook(Box::new(|panic_info| { +/// if let Some(location) = panic_info.location() { +/// println!("panic occured in file '{}' at line {}", location.file(), location.line()); +/// } else { +/// println!("panic occured but can't get location information..."); +/// } +/// })); +/// +/// panic!("Normal panic"); +/// ``` +#[derive(Debug)] +#[stable(feature = "panic_hooks", since = "1.10.0")] +pub struct Location<'a> { + file: &'a str, + line: u32, + col: u32, +} + +impl<'a> Location<'a> { + /// Returns the name of the source file from which the panic originated. + /// + /// # Examples + /// + /// ```should_panic + /// use std::panic; + /// + /// panic::set_hook(Box::new(|panic_info| { + /// if let Some(location) = panic_info.location() { + /// println!("panic occured in file '{}'", location.file()); + /// } else { + /// println!("panic occured but can't get location information..."); + /// } + /// })); + /// + /// panic!("Normal panic"); + /// ``` + #[stable(feature = "panic_hooks", since = "1.10.0")] + pub fn file(&self) -> &str { + self.file + } + + /// Returns the line number from which the panic originated. + /// + /// # Examples + /// + /// ```should_panic + /// use std::panic; + /// + /// panic::set_hook(Box::new(|panic_info| { + /// if let Some(location) = panic_info.location() { + /// println!("panic occured at line {}", location.line()); + /// } else { + /// println!("panic occured but can't get location information..."); + /// } + /// })); + /// + /// panic!("Normal panic"); + /// ``` + #[stable(feature = "panic_hooks", since = "1.10.0")] + pub fn line(&self) -> u32 { + self.line + } + + /// Returns the column from which the panic originated. + /// + /// # Examples + /// + /// ```should_panic + /// #![feature(panic_col)] + /// use std::panic; + /// + /// panic::set_hook(Box::new(|panic_info| { + /// if let Some(location) = panic_info.location() { + /// println!("panic occured at column {}", location.column()); + /// } else { + /// println!("panic occured but can't get location information..."); + /// } + /// })); + /// + /// panic!("Normal panic"); + /// ``` + #[unstable(feature = "panic_col", reason = "recently added", issue = "42939")] + pub fn column(&self) -> u32 { + self.col + } } -// Citra doesn't support the Error applet yet #[cfg(feature = "citra")] -#[unstable(feature = "libstd_sys_internals", - reason = "used by the panic! macro", - issue = "0")] -#[inline(never)] #[cold] -pub fn begin_panic(msg: M, file_line_col: &(&'static str, u32, u32)) -> ! { - let msg = Box::new(msg); - let (file, line, col) = *file_line_col; +fn default_hook(info: &PanicInfo) { + #[cfg(feature = "backtrace")] + use sys_common::backtrace; + + // If this is a double panic, make sure that we print a backtrace + // for this panic. Otherwise only print it if logging is enabled. + #[cfg(feature = "backtrace")] + let log_backtrace = { + let panics = update_panic_count(0); + + if panics >= 2 { + Some(backtrace::PrintFormat::Full) + } else { + backtrace::log_enabled() + } + }; + + let file = info.location.file; + let line = info.location.line; + let col = info.location.col; + + let msg = match info.payload.downcast_ref::<&'static str>() { + Some(s) => *s, + None => match info.payload.downcast_ref::() { + Some(s) => &s[..], + None => "Box", + } + }; // 3DS-specific code begins here - use libctru::{consoleInit, consoleClear, gfxScreen_t, threadGetCurrent}; + use libctru::{consoleInit, consoleDebugInit, debugDevice, + consoleClear, gfxScreen_t, threadGetCurrent}; + use sys::stdio::Stderr; + + let mut err = Stderr::new().ok(); + let thread = thread_info::current_thread(); + let name; unsafe { - // Set up a new console, overwriting whatever was on the top screen + // Set up a new console, overwriting whatever was on the bottom screen // before we started panicking - let _console = consoleInit(gfxScreen_t::GFX_TOP, ptr::null_mut()); + let _console = consoleInit(gfxScreen_t::GFX_BOTTOM, ptr::null_mut()); consoleClear(); + consoleDebugInit(debugDevice::debugDevice_CONSOLE); // Determine thread name - let thread = thread_info::current_thread(); - let name = thread.as_ref() + name = thread.as_ref() .and_then(|t| t.name()) .unwrap_or(if threadGetCurrent() == ptr::null_mut() {"main"} else {""}); + } + + let write = |err: &mut ::io::Write| { + let _ = writeln!(err, "thread '{}' panicked at '{}', {}:{}:{}", + name, msg, file, line, col); - println!("thread '{}' panicked at '{}', {}:{}:{}", - name, msg, file, line, col); + #[cfg(feature = "backtrace")] + { + use sync::atomic::{AtomicBool, Ordering}; - // Citra seems to ignore calls to svcExitProcess, and libc::abort() - // causes it to lock up and fill the console with endless debug statements. - // So instead of terminating the program, we just let it spin in the following - // loop. This means that any background threads might continue to run after - // this thread panics, but there's not a lot we can do about that currently. - loop { } + static FIRST_PANIC: AtomicBool = AtomicBool::new(true); + + if let Some(format) = log_backtrace { + let _ = backtrace::print(err, format); + } else if FIRST_PANIC.compare_and_swap(true, false, Ordering::SeqCst) { + let _ = writeln!(err, "note: Run with `RUST_BACKTRACE=1` for a backtrace."); + } + } + }; + + let prev = LOCAL_STDERR.with(|s| s.borrow_mut().take()); + match (prev, err.as_mut()) { + (Some(mut stderr), _) => { + write(&mut *stderr); + let mut s = Some(stderr); + LOCAL_STDERR.with(|slot| { + *slot.borrow_mut() = s.take(); + }); + } + (None, Some(ref mut err)) => { write(err) } + _ => {} } + // Citra will crash and die horribly if we allow it to go further than this, + // So we just trap control flow in this loop instead. You'll still see the panic + // message, and Citra won't die quite as horribly. It's a win-win! Well, mostly. + loop { } } #[cfg(not(feature = "citra"))] -#[unstable(feature = "libstd_sys_internals", - reason = "used by the panic! macro", - issue = "0")] -#[inline(never)] #[cold] -pub fn begin_panic(msg: M, file_line_col: &(&'static str, u32, u32)) -> ! { - let msg = Box::new(msg); - let (file, line, col) = *file_line_col; +fn default_hook(info: &PanicInfo) { + #[cfg(feature = "backtrace")] + use sys_common::backtrace; + + // If this is a double panic, make sure that we print a backtrace + // for this panic. Otherwise only print it if logging is enabled. + #[cfg(feature = "backtrace")] + let log_backtrace = { + let panics = update_panic_count(0); + + if panics >= 2 { + Some(backtrace::PrintFormat::Full) + } else { + backtrace::log_enabled() + } + }; + + let file = info.location.file; + let line = info.location.line; + let col = info.location.col; + + let msg = match info.payload.downcast_ref::<&'static str>() { + Some(s) => *s, + None => match info.payload.downcast_ref::() { + Some(s) => &s[..], + None => "Box", + } + }; // 3DS-specific code begins here - use libctru::{errorInit, errorText, errorDisp, - APT_HardwareResetAsync, svcExitProcess, threadGetCurrent, + use libctru::{errorInit, errorText, errorDisp, threadGetCurrent, errorConf, errorType, CFG_Language}; use libc; unsafe { - // Determine thread name let thread = thread_info::current_thread(); let name = thread.as_ref() .and_then(|t| t.name()) @@ -126,6 +455,7 @@ pub fn begin_panic(msg: M, file_line_col: &(&'static st // Setup error payload let error_text = format!("thread '{}' panicked at '{}', {}:{}:{}", name, msg, file, line, col); + let mut error_conf: errorConf = mem::uninitialized(); errorInit(&mut error_conf, errorType::ERROR_TEXT_WORD_WRAP, @@ -134,16 +464,26 @@ pub fn begin_panic(msg: M, file_line_col: &(&'static st // Display error errorDisp(&mut error_conf); + } +} - // Now that we're all done printing, it's time to exit the program. - // We don't have stack unwinding yet, so let's just trigger a reboot - APT_HardwareResetAsync(); +#[cfg(not(test))] +#[doc(hidden)] +#[unstable(feature = "update_panic_count", issue = "0")] +pub fn update_panic_count(amt: isize) -> usize { + use cell::Cell; + thread_local! { static PANIC_COUNT: Cell = Cell::new(0) } - // If rebooting fails for some reason, we extra-forcibly end the program - svcExitProcess() - } + PANIC_COUNT.with(|c| { + let next = (c.get() as isize + amt) as usize; + c.set(next); + return next + }) } +#[cfg(test)] +pub use realstd::rt::update_panic_count; + /// Invoke a closure, capturing the cause of an unwinding panic if one occurs. pub unsafe fn try R>(f: F) -> Result> { #[allow(unions_with_drop_fields)] @@ -209,29 +549,129 @@ pub unsafe fn try R>(f: F) -> Result> { } } -#[cfg(not(test))] -#[doc(hidden)] -#[unstable(feature = "update_panic_count", issue = "0")] -pub fn update_panic_count(amt: isize) -> usize { - use cell::Cell; - thread_local! { static PANIC_COUNT: Cell = Cell::new(0) } +/// Determines whether the current thread is unwinding because of panic. +pub fn panicking() -> bool { + update_panic_count(0) != 0 +} - PANIC_COUNT.with(|c| { - let next = (c.get() as isize + amt) as usize; - c.set(next); - return next - }) +/// Entry point of panic from the libcore crate. +#[lang = "panic_fmt"] +#[unwind] +pub extern fn rust_begin_panic(msg: fmt::Arguments, + file: &'static str, + line: u32, + col: u32) -> ! { + begin_panic_fmt(&msg, &(file, line, col)) } -// *Implementation borrowed from the libpanic_abort crate* -// -// Rust's "try" function, but if we're aborting on panics we just call the -// function as there's nothing else we need to do here. -#[allow(improper_ctypes)] -extern fn __rust_maybe_catch_panic(f: fn(*mut u8), - data: *mut u8, - _data_ptr: *mut usize, - _vtable_ptr: *mut usize) -> u32 { - f(data); - 0 +/// The entry point for panicking with a formatted message. +/// +/// This is designed to reduce the amount of code required at the call +/// site as much as possible (so that `panic!()` has as low an impact +/// on (e.g.) the inlining of other functions as possible), by moving +/// the actual formatting into this shared place. +#[unstable(feature = "libstd_sys_internals", + reason = "used by the panic! macro", + issue = "0")] +#[inline(never)] #[cold] +pub fn begin_panic_fmt(msg: &fmt::Arguments, + file_line_col: &(&'static str, u32, u32)) -> ! { + use fmt::Write; + + // We do two allocations here, unfortunately. But (a) they're + // required with the current scheme, and (b) we don't handle + // panic + OOM properly anyway (see comment in begin_panic + // below). + + let mut s = String::new(); + let _ = s.write_fmt(*msg); + begin_panic(s, file_line_col) +} + +/// This is the entry point of panicking for panic!() and assert!(). +#[unstable(feature = "libstd_sys_internals", + reason = "used by the panic! macro", + issue = "0")] +#[inline(never)] #[cold] // avoid code bloat at the call sites as much as possible +pub fn begin_panic(msg: M, file_line_col: &(&'static str, u32, u32)) -> ! { + // Note that this should be the only allocation performed in this code path. + // Currently this means that panic!() on OOM will invoke this code path, + // but then again we're not really ready for panic on OOM anyway. If + // we do start doing this, then we should propagate this allocation to + // be performed in the parent of this thread instead of the thread that's + // panicking. + + rust_panic_with_hook(Box::new(msg), file_line_col) +} + +/// Executes the primary logic for a panic, including checking for recursive +/// panics and panic hooks. +/// +/// This is the entry point or panics from libcore, formatted panics, and +/// `Box` panics. Here we'll verify that we're not panicking recursively, +/// run panic hooks, and then delegate to the actual implementation of panics. +#[inline(never)] +#[cold] +fn rust_panic_with_hook(msg: Box, + file_line_col: &(&'static str, u32, u32)) -> ! { + let (file, line, col) = *file_line_col; + + let panics = update_panic_count(1); + + // If this is the third nested call (e.g. panics == 2, this is 0-indexed), + // the panic hook probably triggered the last panic, otherwise the + // double-panic check would have aborted the process. In this case abort the + // process real quickly as we don't want to try calling it again as it'll + // probably just panic again. + if panics > 2 { + util::dumb_print(format_args!("thread panicked while processing \ + panic. aborting.\n")); + unsafe { intrinsics::abort() } + } + + unsafe { + let info = PanicInfo { + payload: &*msg, + location: Location { + file, + line, + col, + }, + }; + HOOK_LOCK.read(); + match HOOK { + Hook::Default => default_hook(&info), + Hook::Custom(ptr) => (*ptr)(&info), + } + HOOK_LOCK.read_unlock(); + } + + if panics > 1 { + // If a thread panics while it's already unwinding then we + // have limited options. Currently our preference is to + // just abort. In the future we may consider resuming + // unwinding or otherwise exiting the thread cleanly. + util::dumb_print(format_args!("thread panicked while panicking. \ + aborting.\n")); + unsafe { intrinsics::abort() } + } + + rust_panic(msg) +} + +/// Shim around rust_panic. Called by resume_unwind. +pub fn update_count_then_panic(msg: Box) -> ! { + update_panic_count(1); + rust_panic(msg) +} + +/// A private no-mangle function on which to slap yer breakpoints. +#[no_mangle] +#[allow(private_no_mangle_fns)] // yes we get it, but we like breakpoints +pub fn rust_panic(msg: Box) -> ! { + let code = unsafe { + let obj = mem::transmute::<_, raw::TraitObject>(msg); + __rust_start_panic(obj.data as usize, obj.vtable as usize) + }; + rtabort!("failed to initiate panic, error {}", code) } diff --git a/ctr-std/src/rt.rs b/ctr-std/src/rt.rs index 178b5a0..ddfb707 100644 --- a/ctr-std/src/rt.rs +++ b/ctr-std/src/rt.rs @@ -22,6 +22,7 @@ issue = "0")] #![doc(hidden)] +use panic; use mem; // Reexport some of our utilities which are expected by other crates. @@ -31,6 +32,8 @@ pub use panicking::{begin_panic, begin_panic_fmt}; #[lang = "start"] #[allow(unused_variables)] fn lang_start(main: *const u8, argc: isize, argv: *const *const u8) -> isize { - unsafe { mem::transmute::<_, fn()>(main)(); } + let _ = unsafe { + panic::catch_unwind(mem::transmute::<_, fn()>(main)) + }; 0 } diff --git a/ctr-std/src/sys_common/mod.rs b/ctr-std/src/sys_common/mod.rs index 6f47466..4a7d79f 100644 --- a/ctr-std/src/sys_common/mod.rs +++ b/ctr-std/src/sys_common/mod.rs @@ -77,6 +77,10 @@ pub fn at_exit(f: F) -> Result<(), ()> { if at_exit_imp::push(Box::new(f)) {Ok(())} else {Err(())} } +macro_rules! rtabort { + ($($t:tt)*) => (::sys_common::util::abort(format_args!($($t)*))) +} + // Computes (value*numer)/denom without overflow, as long as both // (numer*denom) and the overall result fit into i64 (which is the case // for our time conversions). diff --git a/examples/.cargo/config b/examples/.cargo/config index d2c7139..a81612c 100644 --- a/examples/.cargo/config +++ b/examples/.cargo/config @@ -10,6 +10,9 @@ rustflags = [ "-Clink-arg=-mtune=mpcore", "-Clink-arg=-mfpu=vfp", "-Clink-arg=-mtp=soft", + "-Clink-arg=-z", + "-Clink-arg=muldefs", + "-Clink-arg=-lgcc", "-Clink-arg=-lsysbase", "-Clink-arg=-lc" ] diff --git a/examples/3ds.json b/examples/3ds.json index bec3423..6aea864 100644 --- a/examples/3ds.json +++ b/examples/3ds.json @@ -14,6 +14,5 @@ "relocation-model": "static", "executables": true, "exe-suffix": ".elf", - "panic-strategy": "abort", "linker-flavor": "gcc" } diff --git a/examples/Cargo.toml b/examples/Cargo.toml index e01118a..412b5b2 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -5,5 +5,9 @@ version = "0.1.0" [dependencies] ctru-rs = { path = "../ctru-rs" } + +[profile.dev] +lto = true + [profile.release] lto = true diff --git a/examples/Xargo.toml b/examples/Xargo.toml index 7cc0423..a91284d 100644 --- a/examples/Xargo.toml +++ b/examples/Xargo.toml @@ -1,5 +1,7 @@ [dependencies.collections] [dependencies.rand] +[dependencies.panic_abort] +[dependencies.panic_unwind] [dependencies.std] path = "../ctr-std" From a563a07c78406d94f36c32e0516f88539a62b52f Mon Sep 17 00:00:00 2001 From: Fenrir Date: Tue, 1 Aug 2017 00:29:36 -0600 Subject: [PATCH 2/3] Update pinned nightly to 2017-07-31 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4646938..037cd56 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: rust rust: - - nightly-2017-07-18 + - nightly-2017-07-31 - nightly matrix: From b2ba0fa9af69b7ff12b68e1c8b1f2c923c455e2e Mon Sep 17 00:00:00 2001 From: Fenrir Date: Tue, 1 Aug 2017 15:24:05 -0600 Subject: [PATCH 3/3] Properly set up main thread --- ctr-std/src/panicking.rs | 33 +++++++++++---------------------- ctr-std/src/rt.rs | 19 ++++++++++++++++--- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/ctr-std/src/panicking.rs b/ctr-std/src/panicking.rs index c2e0832..7047334 100644 --- a/ctr-std/src/panicking.rs +++ b/ctr-std/src/panicking.rs @@ -354,25 +354,17 @@ fn default_hook(info: &PanicInfo) { }; // 3DS-specific code begins here - use libctru::{consoleInit, consoleDebugInit, debugDevice, - consoleClear, gfxScreen_t, threadGetCurrent}; + use libctru::{consoleDebugInit, debugDevice}; use sys::stdio::Stderr; let mut err = Stderr::new().ok(); let thread = thread_info::current_thread(); - let name; + let name = thread.as_ref() + .and_then(|t| t.name()) + .unwrap_or(""); unsafe { - // Set up a new console, overwriting whatever was on the bottom screen - // before we started panicking - let _console = consoleInit(gfxScreen_t::GFX_BOTTOM, ptr::null_mut()); - consoleClear(); consoleDebugInit(debugDevice::debugDevice_CONSOLE); - - // Determine thread name - name = thread.as_ref() - .and_then(|t| t.name()) - .unwrap_or(if threadGetCurrent() == ptr::null_mut() {"main"} else {""}); } let write = |err: &mut ::io::Write| { @@ -405,10 +397,6 @@ fn default_hook(info: &PanicInfo) { (None, Some(ref mut err)) => { write(err) } _ => {} } - // Citra will crash and die horribly if we allow it to go further than this, - // So we just trap control flow in this loop instead. You'll still see the panic - // message, and Citra won't die quite as horribly. It's a win-win! Well, mostly. - loop { } } #[cfg(not(feature = "citra"))] @@ -441,17 +429,18 @@ fn default_hook(info: &PanicInfo) { } }; + // 3DS-specific code begins here - use libctru::{errorInit, errorText, errorDisp, threadGetCurrent, + use libctru::{errorInit, errorText, errorDisp, errorConf, errorType, CFG_Language}; use libc; - unsafe { - let thread = thread_info::current_thread(); - let name = thread.as_ref() - .and_then(|t| t.name()) - .unwrap_or(if threadGetCurrent() == ptr::null_mut() {"main"} else {""}); + let thread = thread_info::current_thread(); + let name = thread.as_ref() + .and_then(|t| t.name()) + .unwrap_or(""); + unsafe { // Setup error payload let error_text = format!("thread '{}' panicked at '{}', {}:{}:{}", name, msg, file, line, col); diff --git a/ctr-std/src/rt.rs b/ctr-std/src/rt.rs index ddfb707..735509b 100644 --- a/ctr-std/src/rt.rs +++ b/ctr-std/src/rt.rs @@ -23,6 +23,8 @@ #![doc(hidden)] use panic; +use sys_common::thread_info; +use thread::Thread; use mem; // Reexport some of our utilities which are expected by other crates. @@ -32,8 +34,19 @@ pub use panicking::{begin_panic, begin_panic_fmt}; #[lang = "start"] #[allow(unused_variables)] fn lang_start(main: *const u8, argc: isize, argv: *const *const u8) -> isize { - let _ = unsafe { - panic::catch_unwind(mem::transmute::<_, fn()>(main)) + let failed = unsafe { + let thread = Thread::new(Some("main".to_owned())); + + thread_info::set(None, thread); + + let res = panic::catch_unwind(mem::transmute::<_, fn()>(main)); + + res.is_err() }; - 0 + + if failed { + 101 + } else { + 0 + } }