diff --git a/artiq/firmware/Cargo.lock b/artiq/firmware/Cargo.lock index 77d262538..e5d303a00 100644 --- a/artiq/firmware/Cargo.lock +++ b/artiq/firmware/Cargo.lock @@ -253,6 +253,7 @@ dependencies = [ "byteorder", "cslice", "dyld", + "eh", "failure", "failure_derive", "io", diff --git a/artiq/firmware/ksupport/api.rs b/artiq/firmware/ksupport/api.rs index 7b219562c..c085aa08b 100644 --- a/artiq/firmware/ksupport/api.rs +++ b/artiq/firmware/ksupport/api.rs @@ -117,7 +117,8 @@ static mut API: &'static [(&'static str, *const ())] = &[ api!(_Unwind_Resume = ::unwind::_Unwind_Resume), api!(__artiq_personality = ::eh_artiq::personality), api!(__artiq_raise = ::eh_artiq::raise), - api!(__artiq_reraise = ::eh_artiq::reraise), + api!(__artiq_resume = ::eh_artiq::resume), + api!(__artiq_end_catch = ::eh_artiq::end_catch), /* proxified syscalls */ api!(core_log), diff --git a/artiq/firmware/ksupport/eh_artiq.rs b/artiq/firmware/ksupport/eh_artiq.rs index 82be0aa4e..30e173dc0 100644 --- a/artiq/firmware/ksupport/eh_artiq.rs +++ b/artiq/firmware/ksupport/eh_artiq.rs @@ -10,12 +10,15 @@ // except according to those terms. #![allow(non_camel_case_types)] -use core::{ptr, mem}; -use cslice::CSlice; +use core::mem; +use cslice::AsCSlice; use unwind as uw; use libc::{c_int, c_void}; -use eh::dwarf::{self, EHAction, EHContext}; +use eh::{self, dwarf::{self, EHAction, EHContext}}; + +pub type Exception<'a> = eh::eh_artiq::Exception<'a>; +pub type StackPointerBacktrace = eh::eh_artiq::StackPointerBacktrace; type _Unwind_Stop_Fn = extern "C" fn(version: c_int, actions: uw::_Unwind_Action, @@ -30,40 +33,74 @@ extern { stop_parameter: *mut c_void) -> uw::_Unwind_Reason_Code; } -#[repr(C)] -#[derive(Clone, Copy)] -pub struct Exception<'a> { - pub name: CSlice<'a, u8>, - pub file: CSlice<'a, u8>, - pub line: u32, - pub column: u32, - pub function: CSlice<'a, u8>, - pub message: CSlice<'a, u8>, - pub param: [i64; 3] -} +pub static mut PAYLOAD_ADDRESS: usize = 0; const EXCEPTION_CLASS: uw::_Unwind_Exception_Class = 0x4d_4c_42_53_41_52_54_51; /* 'MLBSARTQ' */ +const MAX_INFLIGHT_EXCEPTIONS: usize = 10; const MAX_BACKTRACE_SIZE: usize = 128; -#[repr(C)] -struct ExceptionInfo { - uw_exception: uw::_Unwind_Exception, - exception: Option>, - handled: bool, - backtrace: [usize; MAX_BACKTRACE_SIZE], - backtrace_size: usize +struct ExceptionBuffer { + // we need n _Unwind_Exception, because each will have their own private data + uw_exceptions: [uw::_Unwind_Exception; MAX_INFLIGHT_EXCEPTIONS], + exceptions: [Option>; MAX_INFLIGHT_EXCEPTIONS + 1], + exception_stack: [isize; MAX_INFLIGHT_EXCEPTIONS + 1], + // nested exceptions will share the backtrace buffer, treated as a tree + // backtrace contains a tuple of IP and SP + backtrace: [(usize, usize); MAX_BACKTRACE_SIZE], + backtrace_size: usize, + // stack pointers are stored to reconstruct backtrace for each exception + stack_pointers: [StackPointerBacktrace; MAX_INFLIGHT_EXCEPTIONS + 1], + // current allocated nested exceptions + exception_count: usize, +} + +static mut EXCEPTION_BUFFER: ExceptionBuffer = ExceptionBuffer { + uw_exceptions: [uw::_Unwind_Exception { + exception_class: EXCEPTION_CLASS, + exception_cleanup: cleanup, + private: [0; uw::unwinder_private_data_size], + }; MAX_INFLIGHT_EXCEPTIONS], + exceptions: [None; MAX_INFLIGHT_EXCEPTIONS + 1], + exception_stack: [-1; MAX_INFLIGHT_EXCEPTIONS + 1], + backtrace: [(0, 0); MAX_BACKTRACE_SIZE], + backtrace_size: 0, + stack_pointers: [StackPointerBacktrace { + stack_pointer: 0, + initial_backtrace_size: 0, + current_backtrace_size: 0 + }; MAX_INFLIGHT_EXCEPTIONS + 1], + exception_count: 0 +}; + +pub unsafe extern fn reset_exception_buffer(payload_addr: usize) { + EXCEPTION_BUFFER.uw_exceptions = [uw::_Unwind_Exception { + exception_class: EXCEPTION_CLASS, + exception_cleanup: cleanup, + private: [0; uw::unwinder_private_data_size], + }; MAX_INFLIGHT_EXCEPTIONS]; + EXCEPTION_BUFFER.exceptions = [None; MAX_INFLIGHT_EXCEPTIONS + 1]; + EXCEPTION_BUFFER.exception_stack = [-1; MAX_INFLIGHT_EXCEPTIONS + 1]; + EXCEPTION_BUFFER.backtrace_size = 0; + EXCEPTION_BUFFER.exception_count = 0; + PAYLOAD_ADDRESS = payload_addr; } #[cfg(target_arch = "x86_64")] const UNWIND_DATA_REG: (i32, i32) = (0, 1); // RAX, RDX +#[cfg(target_arch = "x86_64")] +// actually this is not the SP, but frame pointer +// but it serves its purpose, and getting SP will somehow cause segfault... +const UNW_FP_REG: c_int = 12; #[cfg(any(target_arch = "riscv32"))] const UNWIND_DATA_REG: (i32, i32) = (10, 11); // X10, X11 +#[cfg(any(target_arch = "riscv32"))] +const UNW_FP_REG: c_int = 2; #[export_name="__artiq_personality"] pub extern fn personality(version: c_int, - actions: uw::_Unwind_Action, + _actions: uw::_Unwind_Action, uw_exception_class: uw::_Unwind_Exception_Class, uw_exception: *mut uw::_Unwind_Exception, context: *mut uw::_Unwind_Context) @@ -85,133 +122,236 @@ pub extern fn personality(version: c_int, get_data_start: &|| uw::_Unwind_GetDataRelBase(context), }; - let exception_info = &mut *(uw_exception as *mut ExceptionInfo); - let exception = &exception_info.exception.unwrap(); + let index = EXCEPTION_BUFFER.exception_stack[EXCEPTION_BUFFER.exception_count - 1]; + assert!(index != -1); + let exception = EXCEPTION_BUFFER.exceptions[index as usize].as_ref().unwrap(); + let id = exception.id; - let name_ptr = exception.name.as_ptr(); - let len = exception.name.len(); - let eh_action = match dwarf::find_eh_action(lsda, &eh_context, name_ptr, len) { + let eh_action = match dwarf::find_eh_action(lsda, &eh_context, id) { Ok(action) => action, Err(_) => return uw::_URC_FATAL_PHASE1_ERROR, }; - if actions as u32 & uw::_UA_SEARCH_PHASE as u32 != 0 { - match eh_action { - EHAction::None | - EHAction::Cleanup(_) => return uw::_URC_CONTINUE_UNWIND, - EHAction::Catch(_) => return uw::_URC_HANDLER_FOUND, - EHAction::Terminate => return uw::_URC_FATAL_PHASE1_ERROR, - } - } else { - match eh_action { - EHAction::None => return uw::_URC_CONTINUE_UNWIND, - EHAction::Cleanup(lpad) | - EHAction::Catch(lpad) => { - if actions as u32 & uw::_UA_HANDLER_FRAME as u32 != 0 { - exception_info.handled = true - } - - // Pass a pair of the unwinder exception and ARTIQ exception - // (which immediately follows). - uw::_Unwind_SetGR(context, UNWIND_DATA_REG.0, - uw_exception as uw::_Unwind_Word); - uw::_Unwind_SetGR(context, UNWIND_DATA_REG.1, - exception as *const _ as uw::_Unwind_Word); - uw::_Unwind_SetIP(context, lpad); - return uw::_URC_INSTALL_CONTEXT; - } - EHAction::Terminate => return uw::_URC_FATAL_PHASE2_ERROR, + match eh_action { + EHAction::None => return uw::_URC_CONTINUE_UNWIND, + EHAction::Cleanup(lpad) | + EHAction::Catch(lpad) => { + // Pass a pair of the unwinder exception and ARTIQ exception + // (which immediately follows). + uw::_Unwind_SetGR(context, UNWIND_DATA_REG.0, + uw_exception as uw::_Unwind_Word); + uw::_Unwind_SetGR(context, UNWIND_DATA_REG.1, + exception as *const _ as uw::_Unwind_Word); + uw::_Unwind_SetIP(context, lpad); + return uw::_URC_INSTALL_CONTEXT; } + EHAction::Terminate => return uw::_URC_FATAL_PHASE2_ERROR, } } } +#[export_name="__artiq_raise"] +#[unwind(allowed)] +pub unsafe extern fn raise(exception: *const Exception) -> ! { + let count = EXCEPTION_BUFFER.exception_count; + let stack = &mut EXCEPTION_BUFFER.exception_stack; + let diff = exception as isize - EXCEPTION_BUFFER.exceptions.as_ptr() as isize; + if 0 <= diff && diff <= (mem::size_of::>() * MAX_INFLIGHT_EXCEPTIONS) as isize { + let index = diff / (mem::size_of::>() as isize); + let mut found = false; + for i in 0..=MAX_INFLIGHT_EXCEPTIONS + 1 { + if found { + if stack[i] == -1 { + stack[i - 1] = index; + assert!(i == count); + break; + } else { + stack[i - 1] = stack[i]; + } + } else { + if stack[i] == index { + found = true; + } + } + } + assert!(found); + let _result = _Unwind_ForcedUnwind(&mut EXCEPTION_BUFFER.uw_exceptions[stack[count - 1] as usize], + stop_fn, core::ptr::null_mut()); + } else { + if count < MAX_INFLIGHT_EXCEPTIONS { + let exception = &*exception; + for (i, slot) in EXCEPTION_BUFFER.exceptions.iter_mut().enumerate() { + // we should always be able to find a slot + if slot.is_none() { + *slot = Some( + *mem::transmute::<*const Exception, *const Exception<'static>> + (exception)); + EXCEPTION_BUFFER.exception_stack[count] = i as isize; + EXCEPTION_BUFFER.uw_exceptions[i].private = + [0; uw::unwinder_private_data_size]; + EXCEPTION_BUFFER.stack_pointers[i] = StackPointerBacktrace { + stack_pointer: 0, + initial_backtrace_size: EXCEPTION_BUFFER.backtrace_size, + current_backtrace_size: 0, + }; + EXCEPTION_BUFFER.exception_count += 1; + let _result = _Unwind_ForcedUnwind(&mut EXCEPTION_BUFFER.uw_exceptions[i], + stop_fn, core::ptr::null_mut()); + } + } + } else { + // TODO: better reporting? + let exception = Exception { + id: get_exception_id("RuntimeError"), + file: file!().as_c_slice(), + line: line!(), + column: column!(), + // https://github.com/rust-lang/rfcs/pull/1719 + function: "__artiq_raise".as_c_slice(), + message: "too many nested exceptions".as_c_slice(), + param: [0, 0, 0] + }; + EXCEPTION_BUFFER.exceptions[MAX_INFLIGHT_EXCEPTIONS] = Some(mem::transmute(exception)); + EXCEPTION_BUFFER.stack_pointers[MAX_INFLIGHT_EXCEPTIONS] = Default::default(); + EXCEPTION_BUFFER.exception_count += 1; + uncaught_exception() + } + } + unreachable!(); +} + + +#[export_name="__artiq_resume"] +#[unwind(allowed)] +pub unsafe extern fn resume() -> ! { + assert!(EXCEPTION_BUFFER.exception_count != 0); + let i = EXCEPTION_BUFFER.exception_stack[EXCEPTION_BUFFER.exception_count - 1]; + assert!(i != -1); + let _result = _Unwind_ForcedUnwind(&mut EXCEPTION_BUFFER.uw_exceptions[i as usize], + stop_fn, core::ptr::null_mut()); + unreachable!() +} + +#[export_name="__artiq_end_catch"] +#[unwind(allowed)] +pub unsafe extern fn end_catch() { + let mut count = EXCEPTION_BUFFER.exception_count; + assert!(count != 0); + // we remove all exceptions with SP <= current exception SP + // i.e. the outer exception escapes the finally block + let index = EXCEPTION_BUFFER.exception_stack[count - 1] as usize; + EXCEPTION_BUFFER.exception_stack[count - 1] = -1; + EXCEPTION_BUFFER.exceptions[index] = None; + let outer_sp = EXCEPTION_BUFFER.stack_pointers + [index].stack_pointer; + count -= 1; + for i in (0..count).rev() { + let index = EXCEPTION_BUFFER.exception_stack[i]; + assert!(index != -1); + let index = index as usize; + let sp = EXCEPTION_BUFFER.stack_pointers[index].stack_pointer; + if sp >= outer_sp { + break; + } + EXCEPTION_BUFFER.exceptions[index] = None; + EXCEPTION_BUFFER.exception_stack[i] = -1; + count -= 1; + } + EXCEPTION_BUFFER.exception_count = count; + EXCEPTION_BUFFER.backtrace_size = if count > 0 { + let index = EXCEPTION_BUFFER.exception_stack[count - 1]; + assert!(index != -1); + EXCEPTION_BUFFER.stack_pointers[index as usize].current_backtrace_size + } else { + 0 + }; +} + extern fn cleanup(_unwind_code: uw::_Unwind_Reason_Code, - uw_exception: *mut uw::_Unwind_Exception) { - unsafe { - let exception_info = &mut *(uw_exception as *mut ExceptionInfo); + _uw_exception: *mut uw::_Unwind_Exception) { + unimplemented!() +} - exception_info.exception = None; +fn uncaught_exception() -> ! { + unsafe { + // dump way to reorder the stack + for i in 0..EXCEPTION_BUFFER.exception_count { + if EXCEPTION_BUFFER.exception_stack[i] != i as isize { + // find the correct index + let index = EXCEPTION_BUFFER.exception_stack + .iter() + .position(|v| *v == i as isize).unwrap(); + let a = EXCEPTION_BUFFER.exception_stack[index]; + let b = EXCEPTION_BUFFER.exception_stack[i]; + assert!(a != -1 && b != -1); + core::mem::swap(&mut EXCEPTION_BUFFER.exception_stack[index], + &mut EXCEPTION_BUFFER.exception_stack[i]); + core::mem::swap(&mut EXCEPTION_BUFFER.exceptions[a as usize], + &mut EXCEPTION_BUFFER.exceptions[b as usize]); + core::mem::swap(&mut EXCEPTION_BUFFER.stack_pointers[a as usize], + &mut EXCEPTION_BUFFER.stack_pointers[b as usize]); + } + } + } + unsafe { + ::terminate( + EXCEPTION_BUFFER.exceptions[..EXCEPTION_BUFFER.exception_count].as_ref(), + EXCEPTION_BUFFER.stack_pointers[..EXCEPTION_BUFFER.exception_count].as_ref(), + EXCEPTION_BUFFER.backtrace[..EXCEPTION_BUFFER.backtrace_size].as_mut()) } } -extern fn uncaught_exception(_version: c_int, - actions: uw::_Unwind_Action, - _uw_exception_class: uw::_Unwind_Exception_Class, - uw_exception: *mut uw::_Unwind_Exception, - context: *mut uw::_Unwind_Context, - _stop_parameter: *mut c_void) - -> uw::_Unwind_Reason_Code { + +// stop function which would be executed when we unwind each frame +extern fn stop_fn(_version: c_int, + actions: uw::_Unwind_Action, + _uw_exception_class: uw::_Unwind_Exception_Class, + _uw_exception: *mut uw::_Unwind_Exception, + context: *mut uw::_Unwind_Context, + _stop_parameter: *mut c_void) -> uw::_Unwind_Reason_Code { unsafe { - let exception_info = &mut *(uw_exception as *mut ExceptionInfo); - - if exception_info.backtrace_size < exception_info.backtrace.len() { + let backtrace_size = EXCEPTION_BUFFER.backtrace_size; + if backtrace_size < MAX_BACKTRACE_SIZE { let ip = uw::_Unwind_GetIP(context); - exception_info.backtrace[exception_info.backtrace_size] = ip; - exception_info.backtrace_size += 1; + let fp = uw::_Unwind_GetGR(context, UNW_FP_REG); + if PAYLOAD_ADDRESS == 0 || ip > PAYLOAD_ADDRESS { + let ip = ip - PAYLOAD_ADDRESS; + EXCEPTION_BUFFER.backtrace[backtrace_size] = (ip, fp); + EXCEPTION_BUFFER.backtrace_size += 1; + let last_index = EXCEPTION_BUFFER.exception_stack[EXCEPTION_BUFFER.exception_count - 1]; + assert!(last_index != -1); + let sp_info = &mut EXCEPTION_BUFFER.stack_pointers[last_index as usize]; + sp_info.stack_pointer = fp; + sp_info.current_backtrace_size = backtrace_size + 1; + } } - if actions as u32 & uw::_UA_END_OF_STACK as u32 != 0 { - ::terminate(&exception_info.exception.unwrap(), - exception_info.backtrace[..exception_info.backtrace_size].as_mut()) + uncaught_exception() } else { uw::_URC_NO_REASON } } } -// We can unfortunately not use mem::zeroed in a static, so Option<> is used as a workaround. -// See https://github.com/rust-lang/rust/issues/39498. -static mut INFLIGHT: ExceptionInfo = ExceptionInfo { - uw_exception: uw::_Unwind_Exception { - exception_class: EXCEPTION_CLASS, - exception_cleanup: cleanup, - private: [0; uw::unwinder_private_data_size], - }, - exception: None, - handled: true, - backtrace: [0; MAX_BACKTRACE_SIZE], - backtrace_size: 0 -}; +static EXCEPTION_ID_LOOKUP: [(&str, u32); 10] = [ + ("RuntimeError", 0), + ("RTIOUnderflow", 1), + ("RTIOOverflow", 2), + ("RTIODestinationUnreachable", 3), + ("DMAError", 4), + ("I2CError", 5), + ("CacheError", 6), + ("SPIError", 7), + ("ZeroDivisionError", 8), + ("IndexError", 9) +]; -#[export_name="__artiq_raise"] -#[unwind(allowed)] -pub unsafe extern fn raise(exception: *const Exception) -> ! { - // Zing! The Exception<'a> to Exception<'static> transmute is not really sound in case - // the exception is ever captured. Fortunately, they currently aren't, and we save - // on the hassle of having to allocate exceptions somewhere except on stack. - INFLIGHT.exception = Some(mem::transmute::>(*exception)); - INFLIGHT.handled = false; - - let result = uw::_Unwind_RaiseException(&mut INFLIGHT.uw_exception); - assert!(result == uw::_URC_END_OF_STACK); - - INFLIGHT.backtrace_size = 0; - let _result = _Unwind_ForcedUnwind(&mut INFLIGHT.uw_exception, - uncaught_exception, ptr::null_mut()); - unreachable!() -} - -#[export_name="__artiq_reraise"] -#[unwind(allowed)] -pub unsafe extern fn reraise() -> ! { - use cslice::AsCSlice; - - if INFLIGHT.handled { - match INFLIGHT.exception { - Some(ref exception) => raise(exception), - None => raise(&Exception { - name: "0:artiq.coredevice.exceptions.RuntimeError".as_c_slice(), - file: file!().as_c_slice(), - line: line!(), - column: column!(), - // https://github.com/rust-lang/rfcs/pull/1719 - function: "__artiq_reraise".as_c_slice(), - message: "No active exception to reraise".as_c_slice(), - param: [0, 0, 0] - }) +pub fn get_exception_id(name: &str) -> u32 { + for (n, id) in EXCEPTION_ID_LOOKUP.iter() { + if *n == name { + return *id } - } else { - uw::_Unwind_Resume(&mut INFLIGHT.uw_exception) } + unimplemented!("unallocated internal exception id") } + diff --git a/artiq/firmware/ksupport/lib.rs b/artiq/firmware/ksupport/lib.rs index d915d7e81..1a1528494 100644 --- a/artiq/firmware/ksupport/lib.rs +++ b/artiq/firmware/ksupport/lib.rs @@ -1,5 +1,5 @@ #![feature(lang_items, llvm_asm, panic_unwind, libc, unwind_attributes, - panic_info_message, nll)] + panic_info_message, nll, const_in_array_repeat_expressions)] #![no_std] extern crate libc; @@ -80,8 +80,9 @@ macro_rules! println { macro_rules! raise { ($name:expr, $message:expr, $param0:expr, $param1:expr, $param2:expr) => ({ use cslice::AsCSlice; + let name_id = $crate::eh_artiq::get_exception_id($name); let exn = $crate::eh_artiq::Exception { - name: concat!("0:artiq.coredevice.exceptions.", $name).as_c_slice(), + id: name_id, file: file!().as_c_slice(), line: line!(), column: column!(), @@ -164,12 +165,12 @@ extern fn rpc_recv(slot: *mut ()) -> usize { &Err(ref exception) => unsafe { eh_artiq::raise(&eh_artiq::Exception { - name: exception.name.as_bytes().as_c_slice(), - file: exception.file.as_bytes().as_c_slice(), + id: exception.id, + file: exception.file, line: exception.line, column: exception.column, - function: exception.function.as_bytes().as_c_slice(), - message: exception.message.as_bytes().as_c_slice(), + function: exception.function, + message: exception.message, param: exception.param }) } @@ -177,27 +178,13 @@ extern fn rpc_recv(slot: *mut ()) -> usize { }) } -fn terminate(exception: &eh_artiq::Exception, backtrace: &mut [usize]) -> ! { - let mut cursor = 0; - for index in 0..backtrace.len() { - if backtrace[index] > kernel_proto::KERNELCPU_PAYLOAD_ADDRESS { - backtrace[cursor] = backtrace[index] - kernel_proto::KERNELCPU_PAYLOAD_ADDRESS; - cursor += 1; - } - } - let backtrace = &mut backtrace.as_mut()[0..cursor]; - +fn terminate(exceptions: &'static [Option>], + stack_pointers: &'static [eh_artiq::StackPointerBacktrace], + backtrace: &mut [(usize, usize)]) -> ! { send(&RunException { - exception: kernel_proto::Exception { - name: str::from_utf8(exception.name.as_ref()).unwrap(), - file: str::from_utf8(exception.file.as_ref()).unwrap(), - line: exception.line, - column: exception.column, - function: str::from_utf8(exception.function.as_ref()).unwrap(), - message: str::from_utf8(exception.message.as_ref()).unwrap(), - param: exception.param, - }, - backtrace: backtrace + exceptions, + stack_pointers, + backtrace }); loop {} } @@ -472,6 +459,7 @@ unsafe fn attribute_writeback(typeinfo: *const ()) { #[no_mangle] pub unsafe fn main() { + eh_artiq::reset_exception_buffer(KERNELCPU_PAYLOAD_ADDRESS); let image = slice::from_raw_parts_mut(kernel_proto::KERNELCPU_PAYLOAD_ADDRESS as *mut u8, kernel_proto::KERNELCPU_LAST_ADDRESS - kernel_proto::KERNELCPU_PAYLOAD_ADDRESS); diff --git a/artiq/firmware/libeh/dwarf.rs b/artiq/firmware/libeh/dwarf.rs index f70290c46..d2e1f633e 100644 --- a/artiq/firmware/libeh/dwarf.rs +++ b/artiq/firmware/libeh/dwarf.rs @@ -202,8 +202,7 @@ unsafe fn get_ttype_entry( pub unsafe fn find_eh_action( lsda: *const u8, context: &EHContext<'_>, - name: *const u8, - len: usize, + id: u32, ) -> Result { if lsda.is_null() { return Ok(EHAction::None); @@ -275,19 +274,9 @@ pub unsafe fn find_eh_action( return Ok(EHAction::Catch(lpad)); } // this seems to be target dependent - let clause_ptr = *(catch_type as *const CSlice); - let clause_name_ptr = (clause_ptr).as_ptr(); - let clause_name_len = (clause_ptr).len(); - if clause_name_len == len { - if (clause_name_ptr == core::ptr::null() || - clause_name_ptr == name || - // somehow their name pointers might differ, but the content is the - // same - core::slice::from_raw_parts(clause_name_ptr, clause_name_len) == - core::slice::from_raw_parts(name, len)) - { - return Ok(EHAction::Catch(lpad)); - } + let clause_id = *(catch_type as *const u32); + if clause_id == id { + return Ok(EHAction::Catch(lpad)); } } else if ar_filter < 0 { // FIXME: how to handle this? diff --git a/artiq/firmware/libeh/eh_artiq.rs b/artiq/firmware/libeh/eh_artiq.rs new file mode 100644 index 000000000..72e9e8d02 --- /dev/null +++ b/artiq/firmware/libeh/eh_artiq.rs @@ -0,0 +1,47 @@ +// ARTIQ Exception struct declaration +use cslice::CSlice; + +// Note: CSlice within an exception may not be actual cslice, they may be strings that exist only +// in the host. If the length == usize:MAX, the pointer is actually a string key in the host. +#[repr(C)] +#[derive(Copy, Clone)] +pub struct Exception<'a> { + pub id: u32, + pub file: CSlice<'a, u8>, + pub line: u32, + pub column: u32, + pub function: CSlice<'a, u8>, + pub message: CSlice<'a, u8>, + pub param: [i64; 3] +} + +fn str_err(_: core::str::Utf8Error) -> core::fmt::Error { + core::fmt::Error +} + +fn exception_str<'a>(s: &'a CSlice<'a, u8>) -> Result<&'a str, core::str::Utf8Error> { + if s.len() == usize::MAX { + Ok("") + } else { + core::str::from_utf8(s.as_ref()) + } +} + +impl<'a> core::fmt::Debug for Exception<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Exception {} from {} in {}:{}:{}, message: {}", + self.id, + exception_str(&self.function).map_err(str_err)?, + exception_str(&self.file).map_err(str_err)?, + self.line, self.column, + exception_str(&self.message).map_err(str_err)?) + } +} + +#[derive(Copy, Clone, Debug, Default)] +pub struct StackPointerBacktrace { + pub stack_pointer: usize, + pub initial_backtrace_size: usize, + pub current_backtrace_size: usize, +} + diff --git a/artiq/firmware/libeh/eh_rust.rs b/artiq/firmware/libeh/eh_rust.rs index 43e17eeac..886d00aa2 100644 --- a/artiq/firmware/libeh/eh_rust.rs +++ b/artiq/firmware/libeh/eh_rust.rs @@ -87,5 +87,5 @@ unsafe fn find_eh_action(context: *mut uw::_Unwind_Context) -> Result { - pub name: &'a str, - pub file: &'a str, - pub line: u32, - pub column: u32, - pub function: &'a str, - pub message: &'a str, - pub param: [i64; 3] -} - #[derive(Debug)] pub enum Message<'a> { LoadRequest(&'a [u8]), @@ -47,8 +36,9 @@ pub enum Message<'a> { RunFinished, RunException { - exception: Exception<'a>, - backtrace: &'a [usize] + exceptions: &'a [Option>], + stack_pointers: &'a [eh::eh_artiq::StackPointerBacktrace], + backtrace: &'a [(usize, usize)] }, RunAborted, @@ -59,7 +49,7 @@ pub enum Message<'a> { data: *const *const () }, RpcRecvRequest(*mut ()), - RpcRecvReply(Result>), + RpcRecvReply(Result>), RpcFlush, CacheGetRequest { key: &'a str }, diff --git a/artiq/firmware/libproto_artiq/lib.rs b/artiq/firmware/libproto_artiq/lib.rs index d343d7d61..cb344616b 100644 --- a/artiq/firmware/libproto_artiq/lib.rs +++ b/artiq/firmware/libproto_artiq/lib.rs @@ -13,6 +13,7 @@ extern crate log; extern crate byteorder; extern crate io; extern crate dyld; +extern crate eh; // Internal protocols. pub mod kernel_proto; diff --git a/artiq/firmware/libproto_artiq/session_proto.rs b/artiq/firmware/libproto_artiq/session_proto.rs index 0475a4489..523331416 100644 --- a/artiq/firmware/libproto_artiq/session_proto.rs +++ b/artiq/firmware/libproto_artiq/session_proto.rs @@ -1,5 +1,7 @@ use core::str::Utf8Error; -use alloc::{vec::Vec, string::String}; +use alloc::vec::Vec; +use eh::eh_artiq::{Exception, StackPointerBacktrace}; +use cslice::CSlice; use io::{Read, ProtoRead, Write, ProtoWrite, Error as IoError, ReadStringError}; @@ -70,13 +72,13 @@ pub enum Request { RpcReply { tag: Vec }, RpcException { - name: String, - message: String, + id: u32, + message: u32, param: [i64; 3], - file: String, + file: u32, line: u32, column: u32, - function: String, + function: u32, }, } @@ -95,14 +97,9 @@ pub enum Reply<'a> { }, KernelStartupFailed, KernelException { - name: &'a str, - message: &'a str, - param: [i64; 3], - file: &'a str, - line: u32, - column: u32, - function: &'a str, - backtrace: &'a [usize], + exceptions: &'a [Option>], + stack_pointers: &'a [StackPointerBacktrace], + backtrace: &'a [(usize, usize)], async_errors: u8 }, @@ -126,15 +123,15 @@ impl Request { tag: reader.read_bytes()? }, 8 => Request::RpcException { - name: reader.read_string()?, - message: reader.read_string()?, + id: reader.read_u32()?, + message: reader.read_u32()?, param: [reader.read_u64()? as i64, reader.read_u64()? as i64, reader.read_u64()? as i64], - file: reader.read_string()?, + file: reader.read_u32()?, line: reader.read_u32()?, column: reader.read_u32()?, - function: reader.read_string()? + function: reader.read_u32()? }, ty => return Err(Error::UnknownPacket(ty)) @@ -142,6 +139,18 @@ impl Request { } } +fn write_exception_string<'a, W>(writer: &mut W, s: &CSlice<'a, u8>) -> Result<(), IoError> + where W: Write + ?Sized +{ + if s.len() == usize::MAX { + writer.write_u32(u32::MAX)?; + writer.write_u32(s.as_ptr() as u32)?; + } else { + writer.write_string(core::str::from_utf8(s.as_ref()).unwrap())?; + } + Ok(()) +} + impl<'a> Reply<'a> { pub fn write_to(&self, writer: &mut W) -> Result<(), IoError> where W: Write + ?Sized @@ -171,22 +180,36 @@ impl<'a> Reply<'a> { writer.write_u8(8)?; }, Reply::KernelException { - name, message, param, file, line, column, function, backtrace, + exceptions, + stack_pointers, + backtrace, async_errors } => { writer.write_u8(9)?; - writer.write_string(name)?; - writer.write_string(message)?; - writer.write_u64(param[0] as u64)?; - writer.write_u64(param[1] as u64)?; - writer.write_u64(param[2] as u64)?; - writer.write_string(file)?; - writer.write_u32(line)?; - writer.write_u32(column)?; - writer.write_string(function)?; + writer.write_u32(exceptions.len() as u32)?; + for exception in exceptions.iter() { + let exception = exception.as_ref().unwrap(); + writer.write_u32(exception.id as u32)?; + write_exception_string(writer, &exception.message)?; + writer.write_u64(exception.param[0] as u64)?; + writer.write_u64(exception.param[1] as u64)?; + writer.write_u64(exception.param[2] as u64)?; + write_exception_string(writer, &exception.file)?; + writer.write_u32(exception.line)?; + writer.write_u32(exception.column)?; + write_exception_string(writer, &exception.function)?; + } + + for sp in stack_pointers.iter() { + writer.write_u32(sp.stack_pointer as u32)?; + writer.write_u32(sp.initial_backtrace_size as u32)?; + writer.write_u32(sp.current_backtrace_size as u32)?; + } + writer.write_u32(backtrace.len() as u32)?; - for &addr in backtrace { - writer.write_u32(addr as u32)? + for &(addr, sp) in backtrace { + writer.write_u32(addr as u32)?; + writer.write_u32(sp as u32)?; } writer.write_u8(async_errors)?; }, diff --git a/artiq/firmware/runtime/session.rs b/artiq/firmware/runtime/session.rs index 260a1b385..bf374eb5d 100644 --- a/artiq/firmware/runtime/session.rs +++ b/artiq/firmware/runtime/session.rs @@ -1,6 +1,7 @@ use core::{mem, str, cell::{Cell, RefCell}, fmt::Write as FmtWrite}; use alloc::{vec::Vec, string::String}; use byteorder::{ByteOrder, NativeEndian}; +use cslice::CSlice; use io::{Read, Write, Error as IoError}; use board_misoc::{ident, cache, config}; @@ -291,7 +292,7 @@ fn process_host_message(io: &Io, } host::Request::RpcException { - name, message, param, file, line, column, function + id, message, param, file, line, column, function } => { if session.kernel_state != KernelState::RpcWait { unexpected!("unsolicited RPC reply") @@ -305,16 +306,18 @@ fn process_host_message(io: &Io, } })?; - let exn = kern::Exception { - name: name.as_ref(), - message: message.as_ref(), - param: param, - file: file.as_ref(), - line: line, - column: column, - function: function.as_ref() - }; - kern_send(io, &kern::RpcRecvReply(Err(exn)))?; + unsafe { + let exn = eh::eh_artiq::Exception { + id: id, + message: CSlice::new(message as *const u8, usize::MAX), + param: param, + file: CSlice::new(file as *const u8, usize::MAX), + line: line, + column: column, + function: CSlice::new(function as *const u8, usize::MAX), + }; + kern_send(io, &kern::RpcRecvReply(Err(exn)))?; + } session.kernel_state = KernelState::Running } @@ -438,7 +441,8 @@ fn process_kern_message(io: &Io, aux_mutex: &Mutex, } } &kern::RunException { - exception: kern::Exception { name, message, param, file, line, column, function }, + exceptions, + stack_pointers, backtrace } => { unsafe { kernel::stop() } @@ -448,19 +452,15 @@ fn process_kern_message(io: &Io, aux_mutex: &Mutex, match stream { None => { error!("exception in flash kernel"); - error!("{}: {} {:?}", name, message, param); - error!("at {}:{}:{} in {}", file, line, column, function); + for exception in exceptions { + error!("{:?}", exception.unwrap()); + } return Ok(true) }, Some(ref mut stream) => { host_write(stream, host::Reply::KernelException { - name: name, - message: message, - param: param, - file: file, - line: line, - column: column, - function: function, + exceptions: exceptions, + stack_pointers: stack_pointers, backtrace: backtrace, async_errors: unsafe { get_async_errors() } }).map_err(|e| e.into()) diff --git a/artiq/test/libartiq_support/lib.rs b/artiq/test/libartiq_support/lib.rs index 050ca31f2..4216c85ec 100644 --- a/artiq/test/libartiq_support/lib.rs +++ b/artiq/test/libartiq_support/lib.rs @@ -1,4 +1,4 @@ -#![feature(libc, panic_unwind, unwind_attributes, rustc_private, int_bits_const)] +#![feature(libc, panic_unwind, unwind_attributes, rustc_private, int_bits_const, const_in_array_repeat_expressions)] #![crate_name = "artiq_support"] #![crate_type = "cdylib"] @@ -58,23 +58,31 @@ mod cslice { pub mod eh { #[path = "../../firmware/libeh/dwarf.rs"] pub mod dwarf; + #[path = "../../firmware/libeh/eh_artiq.rs"] + pub mod eh_artiq; } #[path = "../../firmware/ksupport/eh_artiq.rs"] pub mod eh_artiq; use std::{str, process}; -fn terminate(exception: &eh_artiq::Exception, mut _backtrace: &mut [usize]) -> ! { - println!("Uncaught {}: {} ({}, {}, {})", - str::from_utf8(exception.name.as_ref()).unwrap(), - str::from_utf8(exception.message.as_ref()).unwrap(), - exception.param[0], - exception.param[1], - exception.param[2]); - println!("at {}:{}:{}", - str::from_utf8(exception.file.as_ref()).unwrap(), - exception.line, - exception.column); +fn terminate(exceptions: &'static [Option>], + _stack_pointers: &'static [eh_artiq::StackPointerBacktrace], + _backtrace: &'static mut [(usize, usize)]) -> ! { + println!("{}", exceptions.len()); + for exception in exceptions.iter() { + let exception = exception.as_ref().unwrap(); + println!("Uncaught {}: {} ({}, {}, {})", + exception.id, + str::from_utf8(exception.message.as_ref()).unwrap(), + exception.param[0], + exception.param[1], + exception.param[2]); + println!("at {}:{}:{}", + str::from_utf8(exception.file.as_ref()).unwrap(), + exception.line, + exception.column); + } process::exit(1); } diff --git a/flake.nix b/flake.nix index 8918092b0..78c9dc823 100644 --- a/flake.nix +++ b/flake.nix @@ -257,7 +257,7 @@ cargoDeps = rustPlatform.fetchCargoTarball { name = "artiq-firmware-cargo-deps"; src = "${self}/artiq/firmware"; - sha256 = "sha256-Lf6M4M/jdRiO5MsWSoqtOSNfRIhbze+qvg4kaiiBWW4="; + sha256 = "sha256-YyycMsDzR+JRcMZJd6A/CRi2J9nKmaWY/KXUnAQaZ00="; }; nativeBuildInputs = [ (pkgs.python3.withPackages(ps: [ migen misoc artiq ]))