Skip to content

Commit

Permalink
Support diverted panic upon stack overflow.
Browse files Browse the repository at this point in the history
  • Loading branch information
zyma98 committed Aug 10, 2024
1 parent 56c39ba commit 3d1832a
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 37 deletions.
2 changes: 1 addition & 1 deletion src/boot/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ unsafe extern "C" fn Reset() -> ! {
kern_stk_len = const { config::CONTIGUOUS_STACK_BOTTOM - 0x2000_0000 },
kern_stk_boundary = const config::CONTIGUOUS_STACK_BOUNDARY,
stklet_boundary_mem_addr = const config::TLS_MEM_ADDR,
deferred_unwind = sym unwind::unwind::deferred_unwind,
deferred_unwind = sym unwind::forced::deferred_unwind,
memclr = sym memclr,
memcpy = sym memcpy,
memset = sym memset,
Expand Down
42 changes: 35 additions & 7 deletions src/task/segmented_stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ use crate::{
},
schedule,
unrecoverable::{self, Lethal},
unwind,
};
use core::{
alloc::Layout,
Expand Down Expand Up @@ -229,11 +230,14 @@ pub(crate) fn more_stack(tf: &mut TrapFrame, ctxt: &mut TaskSVCCtxt, reason: Mor
let cur_meta_ptr = bound_to_stklet_meta(bound as usize);
let cur_meta = unsafe { &mut *cur_meta_ptr };

// Whether we should abort the new stacklet allocation.
let mut abort = false;

schedule::with_current_task(|cur_task| {
cur_task
// Alleviate the hot split problem if the task contains a hot-split
// alleviation block. All tasks with dynamic stack extension
// enabled has it.
// enabled have it.
.with_hsab(|hsab| {
svc_more_stack_anti_hot_split(
tf,
Expand All @@ -243,17 +247,41 @@ pub(crate) fn more_stack(tf: &mut TrapFrame, ctxt: &mut TaskSVCCtxt, reason: Mor
)
})
// Otherwise, the task does not enable dynamic stack extension.
// Divert the function call to a panic if no drop handler function
// is currently active. Pend a panic if any drop handler function
// is active. The pending panic will be invoked after all drop
// handler functions of the task return.
.unwrap_or_else(|| {
if reason == MoreStackReason::Drop || ctxt.tls.nested_drop_cnt > 0 {
ctxt.tls.panic_pending = 1;
// If the task wants to start unwinding or is under unwinding,
// proceed to allocate a new stacklet even if the task does not
// enable dynamic stack extension.
if reason == MoreStackReason::Unwind || cur_task.is_unwinding() {
}
// If the overflowing function is a drop handler or if any
// active parent function is a drop handler, pend a panic. The
// pending panic will be invoked after all drop handlers have
// finished execution. This is done by the complier inserting
// an epilogue to all drop handlers. See the `unwind::forced`
// module for details. We proceed to allocate a new stacklet
// even if the task does not enabled dynamic stack extension,
// because we must not unwind from inside a drop handler.
else if reason == MoreStackReason::Drop || ctxt.tls.nested_drop_cnt > 0 {
ctxt.tls.unwind_pending = 1;
// If the overflowing function is not a drop handler and no
// drop handler is active, divert the function call to the
// stack unwinding entry to forcefully unwind the task.
} else if reason == MoreStackReason::Normal {
if !cur_task.is_unwinding() {
tf.gp_regs.pc = unwind::forced::diverted_unwind as u32;

// Do not allocate a new stacklet if the task is going
// to be unwound.
abort = true;
}
}
});
});

if abort {
return;
}

// Total chunk size to request from malloc.
// The overhead includes the trap frame, its padding, and the metadata block.
let total_size = stk_frame_size as usize
Expand Down
4 changes: 2 additions & 2 deletions src/task/task_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ pub(crate) struct TaskLocalStorage {
/// which case we just set the panic pending flag. The modified compiler
/// toolchain generates an epilogue that checks this flag if the
/// [`nested_drop_cnt`](Self::nested_drop_cnt) is zero and diverts to panic
/// if the flag is set to true.
pub(crate) panic_pending: u32,
/// if the flag is set to true. See [`crate::unwind::forced`] for details.
pub(crate) unwind_pending: u32,
}

#[repr(C)]
Expand Down
82 changes: 82 additions & 0 deletions src/unwind/forced.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//! Forced unwinding is used to forcefully terminate a task while reclaiming
//! its allocated resources. The only current use of forced unwinding is to
//! terminate a task whose stack is going to overflow but does not enable
//! dynamic stack extension. In this case, the segmented stack runtime
//! [`crate::task::more_stack`] will divert the original function call to
//! [`diverted_unwind`] instead, which further starts the unwinding process.
//!
//! A notable caveat to forced unwinding is that it is an undefined behavior
//! if we initiate unwinding from inside a drop handler function. Thus, if an
//! overflowing function is a drop handler or if any active parent function in
//! the call stack is a drop handler, we must defer the forced unwinding to
//! some later time.
//!
//! A deferred unwinding has two steps:
//! 1. Upon detecting that a forced unwinding must be deferred, the segmented
//! stack runtime sets a deferred unwinding pending flag in the task local
//! storage (TLS) area. The flag is currently placed at the fixed address
//! `0x2000_0008`. The segmented stack runtime allocates a new stacklet to
//! let the task proceed with executing the function instead of immediately
//! diverting it to unwinding, even if the task does not enable dynamic
//! stack extension.
//! 2. When the last (outmost) drop handler is returning, it checks if the
//! deferred unwinding pending flag is set. If so, instead of returning,
//! it will call [`deferred_unwind`].
//!
//! The compiler generates a special function prologue and epilogue for drop
//! handler functions to enable deferred forced unwinding.
//!
//! The special prologue contains two components:
//! 1. A segmented stack prologue placed at the very beginning of the function
//! that detects upcoming stack overflows. When an overflow is impending,
//! the prologue invokes SVC using a different SVC number than other normal
//! functions, and thus the segmented stack runtime can notice that the
//! overflowing function is a drop handler.
//! 2. A counter increment prologue that placed at the beginning of the body of
//! the drop logic, i.e., `CNT += 1`. The counter is currently placed at a
//! fixed address in the task local storage (TLS) area. The address of the
//! counter is `0x2000_0004`. The counter is named
//! [nested drop count](crate::task::TaskLocalStorage::nested_drop_cnt)
//! because it represents the nesting level of active drop handlers.
//!
//! The epilogue contains two components:
//! 1. A counter decrement epilogue that placed at the ending of the body of
//! the drop logic, i.e., `CNT -= 1`.
//! 2. A flag checking epilogue that checks the deferred unwinding pending flag
//! if the nested drop count is zero after the decrement. If the flag is set,
//! call [`deferred_unwind`].
use super::unwind;
use crate::config;
use core::arch::asm;

/// Just jump to the entry point to start unwinding.
#[naked]
pub(crate) extern "C" fn diverted_unwind() {
unsafe {
asm!(
"b {start_unwind_entry}",
start_unwind_entry = sym unwind::start_unwind_entry,
options(noreturn)
)
}
}

/// See [module](crate::unwind::forced) level documentation for when this
/// function will be called.
#[naked]
pub(crate) extern "C" fn deferred_unwind() {
unsafe {
asm!(
// Clear the deferred unwinding pending flag.
"mov r0, #0",
"ldr r1, ={tls_mem_addr}",
"str r0, [r1, #8]",
// Jump to the entry point to start unwinding.
"b {start_unwind_entry}",
tls_mem_addr = const config::TLS_MEM_ADDR,
start_unwind_entry = sym unwind::start_unwind_entry,
options(noreturn)
)
}
}
6 changes: 4 additions & 2 deletions src/unwind/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
#[cfg(feature = "unwind")]
pub mod unw_catch;
pub(crate) mod forced;
#[cfg(feature = "unwind")]
pub(crate) mod unw_catch;
#[cfg(feature = "unwind")]
mod unw_lsda;
#[cfg(feature = "unwind")]
mod unw_table;
#[cfg(feature = "unwind")]
pub mod unwind;
pub(crate) mod unwind;

#[cfg(not(feature = "unwind"))]
mod panic;
31 changes: 6 additions & 25 deletions src/unwind/unwind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ impl ARMGPReg {
13 => Self::SP,
14 => Self::LR,
15 => Self::PC,
_ => loop {},
_ => unrecoverable::die(),
}
}

Expand Down Expand Up @@ -296,7 +296,7 @@ impl ARMDPFPReg {
13 => Self::D13,
14 => Self::D14,
15 => Self::D15,
_ => loop {},
_ => unrecoverable::die(),
}
}
}
Expand Down Expand Up @@ -429,7 +429,7 @@ impl UnwindState<'static> {

// If the reserved static storage is already in-use, we halt here.
if res.is_err() {
loop {}
unrecoverable::die();
}

unsafe { &mut STATIC_UNWIND_STATE as *mut _ }
Expand Down Expand Up @@ -461,7 +461,7 @@ impl UnwindState<'static> {
extern "C" fn create_unwind_state(init_ctxt: &UnwindInitContext) -> *mut Self {
// If we have a double panic in the same task or ISR, halt.
if is_unwinding() {
loop {}
unrecoverable::die();
}

// Mark that we are now unwinding.
Expand Down Expand Up @@ -1130,28 +1130,9 @@ unsafe extern "C" fn _Unwind_Resume(unw_state_ptr: *mut UnwindState) -> ! {
#[panic_handler]
unsafe fn panic(_info: &PanicInfo) -> ! {
start_unwind_entry();
loop {}
}

/// Clear the panic pending flag in the task local storage region.
#[naked]
extern "C" fn clear_panic_pending_flag() {
unsafe {
asm!(
"mov r0, #0",
"ldr r1, ={tls_mem_addr}",
"str r0, [r1, #8]",
"bx lr",
tls_mem_addr = const config::TLS_MEM_ADDR,
options(noreturn)
)
}
}

/// TODO: Comment it.
pub(crate) extern "C" fn deferred_unwind() {
clear_panic_pending_flag();
panic!()
// Should not reach here.
unrecoverable::die()
}

/* Below are unused personality routines. They are marked unsafe because */
Expand Down

0 comments on commit 3d1832a

Please sign in to comment.