From cd80e0af341260b428c61d678af7d714792fca91 Mon Sep 17 00:00:00 2001 From: Klim Tsoutsman Date: Mon, 26 Dec 2022 14:04:21 +1100 Subject: [PATCH] Move `can_unwind` into `unwind` Signed-off-by: Klim Tsoutsman --- kernel/task_cancel/src/lib.rs | 70 +++--------------------------- kernel/unwind/src/lib.rs | 82 ++++++++++++++++++++++++++++++++++- 2 files changed, 88 insertions(+), 64 deletions(-) diff --git a/kernel/task_cancel/src/lib.rs b/kernel/task_cancel/src/lib.rs index 02ed3eeaa7..112007ffae 100644 --- a/kernel/task_cancel/src/lib.rs +++ b/kernel/task_cancel/src/lib.rs @@ -4,10 +4,7 @@ #![feature(abi_x86_interrupt, try_blocks)] #![no_std] -use core::{default::Default, iter::Iterator, sync::atomic::Ordering}; -use gimli::{BaseAddresses, EhFrame, NativeEndian, UninitializedUnwindContext, UnwindSection}; -use memory_structs::VirtualAddress; -use mod_mgmt::SectionType; +use core::sync::atomic::Ordering; use task::{KillReason, TaskRef}; use x86_64::structures::idt::InterruptStackFrame; @@ -15,7 +12,7 @@ pub fn cancel_task(task: TaskRef) { task.cancel_requested.store(true, Ordering::Relaxed); } -pub fn set_trap_flag(stack_frame: &mut InterruptStackFrame) { +fn set_trap_flag(stack_frame: &mut InterruptStackFrame) { unsafe { stack_frame.as_mut() }.update(|stack_frame| stack_frame.cpu_flags |= 0x100); } @@ -25,63 +22,8 @@ pub extern "x86-interrupt" fn interrupt_handler(mut stack_frame: InterruptStackF log::info!("instruction pointer: {instruction_pointer:0x?}"); - let can_unwind = Option::<_>::is_some( - &try { - let task = task::get_my_current_task().expect("couldn't get current task"); - - // TODO: Search external unwind info. - - let krate = task.namespace.get_crate_containing_address( - VirtualAddress::new_canonical(instruction_pointer as usize), - false, - )?; - - let krate = krate.lock_as_ref(); - let text_address = krate.text_pages.as_ref()?.1.start.value(); - let eh_frame_section = krate - .sections - .values() - .find(|s| s.typ == SectionType::EhFrame)?; - let eh_frame_address = eh_frame_section.virt_addr.value(); - - let base_addresses = BaseAddresses::default() - .set_text(text_address as u64) - .set_eh_frame(eh_frame_address as u64); - log::info!("{base_addresses:0x?}"); - - let pages = eh_frame_section.mapped_pages.lock(); - let bytes = pages - .as_slice(eh_frame_section.mapped_pages_offset, eh_frame_section.size) - .ok()?; - let eh_frame = EhFrame::new(bytes, NativeEndian); - - let frame_description_entry = eh_frame - .fde_for_address( - &base_addresses, - instruction_pointer, - EhFrame::cie_from_offset, - ) - .ok()?; - - let mut unwind_context = UninitializedUnwindContext::new(); - let _ = frame_description_entry - .unwind_info_for_address( - &eh_frame, - &base_addresses, - &mut unwind_context, - instruction_pointer, - ) - .ok()?; - - log::info!("covering"); - - Some(()) - }, - ); - - log::info!("stack frame: {:0x?}", stack_frame.stack_pointer); - - if can_unwind { + if unwind::can_unwind(instruction_pointer) { + log::info!("unwinding a cancelled task"); unwind::start_remote_unwinding( KillReason::Requested, 0, @@ -90,7 +32,9 @@ pub extern "x86-interrupt" fn interrupt_handler(mut stack_frame: InterruptStackF ) .expect("failed to unwind"); } else { - // FIXME: What happens if the APIC interrupt triggers here? + // The trap flag is reset after every debug interrupt. Since we can't unwind at + // this instruction, we reset the flag to check again at the next instruction. set_trap_flag(&mut stack_frame); + // FIXME: What happens if a LAPIC timer interrupt triggers here? } } diff --git a/kernel/unwind/src/lib.rs b/kernel/unwind/src/lib.rs index 530d793ca2..ed7027fb11 100644 --- a/kernel/unwind/src/lib.rs +++ b/kernel/unwind/src/lib.rs @@ -413,7 +413,7 @@ impl FallibleIterator for StackFrameIter { let size_of_exception_stack_frame: i64 = 5 * 8; #[cfg(not(downtime_eval))] trace!("StackFrameIter: next stack frame is an exception handler: adding {:#X} to cfa, new cfa: {:#X}", size_of_exception_stack_frame, cfa); - + this_frame_is_exception_handler = true; Some(size_of_error_code + size_of_exception_stack_frame) } else { @@ -456,6 +456,86 @@ unsafe fn deref_ptr(ptr: Pointer) -> u64 { pub trait FuncWithRegisters = FnMut(Registers) -> Result<(), &'static str>; type FuncWithRegistersRefMut<'a> = &'a mut dyn FuncWithRegisters; +/// Returns whether unwinding can begin from the given instruction pointer. +/// +/// This happens when one of the following two criteria is met: +/// +/// 1. The pointer is covered by the unwind tables, but does not have an LSDA. +/// This occurs when the function does not have any associated drop handlers, +/// either because it has nothing to drop or Rust has determined that the +/// function will never be unwound. +/// +/// 2. The pointer is covered by the unwind tables, has an LSDA, and is covered +/// by the LSDA's call site table. This occurs when the function has associated +/// drop handlers and the instruction pointer is at an instruction that could +/// cause an unwinding (usually a call site to a function that can cause an +/// unwinding). +pub fn can_unwind(instruction_pointer: u64) -> bool { + can_unwind_inner(instruction_pointer).is_some() +} + +fn can_unwind_inner(instruction_pointer: u64) -> Option<()> { + let task = task::get_my_current_task()?; + let instruction_address = VirtualAddress::new(instruction_pointer as usize)?; + let krate = task + .namespace + .get_crate_containing_address(instruction_address, false)?; + + let (eh_frame, base_addresses) = get_eh_frame_info(&krate) + .map(|(eh_frame_section, base_addresses)| { + (EhFrameReference::Section(eh_frame_section), base_addresses) + }) + .or_else(|| { + external_unwind_info::get_unwind_info(instruction_address).map(|unwind_info| { + let base_addresses = BaseAddresses::default() + .set_eh_frame(unwind_info.unwind_info.start.value() as u64) + .set_text(unwind_info.text_section.start.value() as u64); + (EhFrameReference::External(unwind_info), base_addresses) + }) + })?; + + let unwind_row = UnwindRowReference { + caller: instruction_pointer, + eh_frame_sec: eh_frame, + base_addrs: base_addresses, + }; + let StackFrame { + lsda: lsda_address, + initial_address, + call_site_address, + .. + } = unwind_row + .with_unwind_info(|fde, _| { + Ok(StackFrame { + personality: None, + lsda: fde.lsda().map(|address| unsafe { deref_ptr(address) }), + initial_address: fde.initial_address(), + call_site_address: instruction_pointer, + }) + }) + .ok()?; + + let lsda_address = match lsda_address { + Some(address) => VirtualAddress::new_canonical(address as usize), + None => return Some(()), + }; + + let (lsda_section, _) = task + .namespace + .get_section_containing_address(lsda_address, true)?; + let start = + lsda_section.mapped_pages_offset + (lsda_address.value() - lsda_section.virt_addr.value()); + let length = lsda_section.virt_addr.value() + lsda_section.size - lsda_address.value(); + + let lsda_pages = lsda_section.mapped_pages.lock(); + let lsda_slice = lsda_pages.as_slice::(start, length).ok()?; + let table = lsda::GccExceptTableArea::new(lsda_slice, NativeEndian, initial_address); + + table + .call_site_table_entry_for_address(call_site_address) + .ok() + .map(|_| ()) +} /// This function saves the current CPU register values onto the stack (to preserve them) /// and then invokes the given closure with those registers as the argument.