runtime/eh_artiq: support exception allocation

The backtrace is now nested, and should be used together with the stack
pointer array to construct the full backtrace for each exception.
We now allocate exception objects in a stack, but their names are still
not allocated. This is fine for exceptions raised in the driver or artiq
code, but we will have to implement allocation for names of exceptions
raised in RPC calls. The compiler should also emit code to store the
exception names once they catch it, to prepare for later reraising.
This commit is contained in:
pca006132 2022-01-14 19:24:20 +08:00
parent 8923feceac
commit 6f5ba46e89
7 changed files with 327 additions and 157 deletions

View File

@ -90,8 +90,7 @@ pub unsafe fn find_eh_action(
lsda: *const u8, lsda: *const u8,
context: &EHContext<'_>, context: &EHContext<'_>,
foreign_exception: bool, foreign_exception: bool,
name: *const u8, id: u32,
len: usize,
) -> Result<EHAction, ()> { ) -> Result<EHAction, ()> {
if lsda.is_null() { if lsda.is_null() {
return Ok(EHAction::None); return Ok(EHAction::None);
@ -164,19 +163,11 @@ pub unsafe fn find_eh_action(
ttype_base, ttype_base,
ttype_table, ttype_table,
)?; )?;
let clause_ptr = *(catch_type as *const *const CSlice<u8>); let clause_ptr = *(catch_type as *const *const u32);
if clause_ptr.is_null() { if clause_ptr.is_null() {
return Ok(EHAction::Catch(lpad)); return Ok(EHAction::Catch(lpad));
} }
let clause_name_ptr = (*clause_ptr).as_ptr(); if *clause_ptr == id {
let clause_name_len = (*clause_ptr).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)); return Ok(EHAction::Catch(lpad));
} }
} else if ar_filter < 0 { } else if ar_filter < 0 {

View File

@ -1,8 +1,8 @@
use core::fmt; use core::fmt;
use core::cell::RefCell; use core::cell::RefCell;
use core::str::Utf8Error;
use alloc::{vec, vec::Vec, string::String, collections::BTreeMap, rc::Rc}; use alloc::{vec, vec::Vec, string::String, collections::BTreeMap, rc::Rc};
use log::{info, warn, error}; use log::{info, warn, error};
use cslice::CSlice;
use num_derive::{FromPrimitive, ToPrimitive}; use num_derive::{FromPrimitive, ToPrimitive};
use num_traits::{FromPrimitive, ToPrimitive}; use num_traits::{FromPrimitive, ToPrimitive};
@ -39,7 +39,6 @@ pub enum Error {
UnexpectedPattern, UnexpectedPattern,
UnrecognizedPacket, UnrecognizedPacket,
BufferExhausted, BufferExhausted,
Utf8Error(Utf8Error),
} }
pub type Result<T> = core::result::Result<T, Error>; pub type Result<T> = core::result::Result<T, Error>;
@ -51,7 +50,6 @@ impl fmt::Display for Error {
Error::UnexpectedPattern => write!(f, "unexpected pattern"), Error::UnexpectedPattern => write!(f, "unexpected pattern"),
Error::UnrecognizedPacket => write!(f, "unrecognized packet"), Error::UnrecognizedPacket => write!(f, "unrecognized packet"),
Error::BufferExhausted => write!(f, "buffer exhausted"), Error::BufferExhausted => write!(f, "buffer exhausted"),
Error::Utf8Error(error) => write!(f, "UTF-8 error: {}", error),
} }
} }
} }
@ -122,11 +120,6 @@ async fn read_bytes(stream: &TcpStream, max_length: usize) -> Result<Vec<u8>> {
Ok(buffer) Ok(buffer)
} }
async fn read_string(stream: &TcpStream, max_length: usize) -> Result<String> {
let bytes = read_bytes(stream, max_length).await?;
Ok(String::from_utf8(bytes).map_err(|err| Error::Utf8Error(err.utf8_error()))?)
}
const RETRY_LIMIT: usize = 100; const RETRY_LIMIT: usize = 100;
async fn fast_send(sender: &mut Sender<'_, kernel::Message>, content: kernel::Message) { async fn fast_send(sender: &mut Sender<'_, kernel::Message>, content: kernel::Message) {
@ -156,6 +149,16 @@ async fn fast_recv(receiver: &mut Receiver<'_, kernel::Message>) -> kernel::Mess
receiver.async_recv().await receiver.async_recv().await
} }
async fn write_exception_string(stream: &TcpStream, s: CSlice<'static, u8>) -> Result<()> {
if s.len() == usize::MAX {
write_i32(stream, -1).await?;
write_i32(stream, s.as_ptr() as i32).await?
} else {
write_chunk(stream, s.as_ref()).await?;
};
Ok(())
}
async fn handle_run_kernel(stream: Option<&TcpStream>, control: &Rc<RefCell<kernel::Control>>) -> Result<()> { async fn handle_run_kernel(stream: Option<&TcpStream>, control: &Rc<RefCell<kernel::Control>>) -> Result<()> {
control.borrow_mut().tx.async_send(kernel::Message::StartRequest).await; control.borrow_mut().tx.async_send(kernel::Message::StartRequest).await;
loop { loop {
@ -204,17 +207,17 @@ async fn handle_run_kernel(stream: Option<&TcpStream>, control: &Rc<RefCell<kern
kernel::Message::RpcRecvRequest(_) => (), kernel::Message::RpcRecvRequest(_) => (),
other => panic!("expected (ignored) root value slot from kernel CPU, not {:?}", other), other => panic!("expected (ignored) root value slot from kernel CPU, not {:?}", other),
} }
let name = read_string(stream, 16384).await?; let id = read_i32(stream).await? as u32;
let message = read_string(stream, 16384).await?; let message = read_i32(stream).await? as u32;
let param = [read_i64(stream).await?, let param = [read_i64(stream).await?,
read_i64(stream).await?, read_i64(stream).await?,
read_i64(stream).await?]; read_i64(stream).await?];
let file = read_string(stream, 16384).await?; let file = read_i32(stream).await? as u32;
let line = read_i32(stream).await?; let line = read_i32(stream).await?;
let column = read_i32(stream).await?; let column = read_i32(stream).await?;
let function = read_string(stream, 16384).await?; let function = read_i32(stream).await? as u32;
control.tx.async_send(kernel::Message::RpcRecvReply(Err(kernel::RPCException { control.tx.async_send(kernel::Message::RpcRecvReply(Err(kernel::RPCException {
name, message, param, file, line, column, function id, message, param, file, line, column, function
}))).await; }))).await;
}, },
_ => { _ => {
@ -231,29 +234,39 @@ async fn handle_run_kernel(stream: Option<&TcpStream>, control: &Rc<RefCell<kern
} }
break; break;
}, },
kernel::Message::KernelException(exception, backtrace, async_errors) => { kernel::Message::KernelException(exceptions, stack_pointers, backtrace, async_errors) => {
match stream { match stream {
Some(stream) => { Some(stream) => {
// only send the exception data to host if there is host, // only send the exception data to host if there is host,
// i.e. not idle/startup kernel. // i.e. not idle/startup kernel.
write_header(stream, Reply::KernelException).await?; write_header(stream, Reply::KernelException).await?;
write_chunk(stream, exception.name.as_ref()).await?; write_i32(stream, exceptions.len() as i32).await?;
write_chunk(stream, exception.message.as_ref()).await?; for exception in exceptions.iter() {
write_i64(stream, exception.param[0] as i64).await?; let exception = exception.as_ref().unwrap();
write_i64(stream, exception.param[1] as i64).await?; write_i32(stream, exception.id as i32).await?;
write_i64(stream, exception.param[2] as i64).await?; write_exception_string(stream, exception.message).await?;
write_chunk(stream, exception.file.as_ref()).await?; write_i64(stream, exception.param[0] as i64).await?;
write_i32(stream, exception.line as i32).await?; write_i64(stream, exception.param[1] as i64).await?;
write_i32(stream, exception.column as i32).await?; write_i64(stream, exception.param[2] as i64).await?;
write_chunk(stream, exception.function.as_ref()).await?; write_exception_string(stream, exception.file).await?;
write_i32(stream, exception.line as i32).await?;
write_i32(stream, exception.column as i32).await?;
write_exception_string(stream, exception.function).await?;
}
for sp in stack_pointers.iter() {
write_i32(stream, sp.stack_pointer as i32).await?;
write_i32(stream, sp.initial_backtrace_size as i32).await?;
write_i32(stream, sp.current_backtrace_size as i32).await?;
}
write_i32(stream, backtrace.len() as i32).await?; write_i32(stream, backtrace.len() as i32).await?;
for &addr in backtrace { for &(addr, sp) in backtrace {
write_i32(stream, addr as i32).await?; write_i32(stream, addr as i32).await?;
write_i32(stream, sp as i32).await?;
} }
write_i8(stream, async_errors as i8).await?; write_i8(stream, async_errors as i8).await?;
}, },
None => { None => {
error!("Uncaught kernel exception: {:?}", exception); error!("Uncaught kernel exceptions: {:?}", exceptions);
} }
} }
break; break;

View File

@ -16,7 +16,8 @@ use core::mem;
use cslice::CSlice; use cslice::CSlice;
use unwind as uw; use unwind as uw;
use libc::{c_int, c_void, uintptr_t}; use libc::{c_int, c_void, uintptr_t};
use log::trace; use log::{trace, error};
use crate::kernel::KERNEL_IMAGE;
use dwarf::eh::{self, EHAction, EHContext}; use dwarf::eh::{self, EHAction, EHContext};
@ -26,10 +27,12 @@ const EXCEPTION_CLASS: uw::_Unwind_Exception_Class = 0x4d_4c_42_53_41_52_54_51;
#[cfg(target_arch = "arm")] #[cfg(target_arch = "arm")]
const UNWIND_DATA_REG: (i32, i32) = (0, 1); // R0, R1 const UNWIND_DATA_REG: (i32, i32) = (0, 1); // R0, R1
// 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)] #[repr(C)]
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct Exception<'a> { pub struct Exception<'a> {
pub name: CSlice<'a, u8>, pub id: u32,
pub file: CSlice<'a, u8>, pub file: CSlice<'a, u8>,
pub line: u32, pub line: u32,
pub column: u32, pub column: u32,
@ -42,25 +45,79 @@ fn str_err(_: core::str::Utf8Error) -> core::fmt::Error {
core::fmt::Error core::fmt::Error
} }
impl<'a> core::fmt::Debug for Exception<'a> { fn exception_str<'a>(s: &'a CSlice<'a, u8>) -> Result<&'a str, core::str::Utf8Error> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { if s.len() == usize::MAX {
write!(f, "Exception {} from {} in {}:{}:{}, message: {}", Ok("<host string>")
core::str::from_utf8(self.name.as_ref()).map_err(str_err)?, } else {
core::str::from_utf8(self.function.as_ref()).map_err(str_err)?, core::str::from_utf8(s.as_ref())
core::str::from_utf8(self.file.as_ref()).map_err(str_err)?,
self.line, self.column,
core::str::from_utf8(self.message.as_ref()).map_err(str_err)?)
} }
} }
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)?)
}
}
const MAX_INFLIGHT_EXCEPTIONS: usize = 10;
const MAX_BACKTRACE_SIZE: usize = 128; const MAX_BACKTRACE_SIZE: usize = 128;
#[repr(C)] #[derive(Debug, Default)]
struct ExceptionInfo { pub struct StackPointerBacktrace {
uw_exception: uw::_Unwind_Exception, pub stack_pointer: usize,
exception: Option<Exception<'static>>, pub initial_backtrace_size: usize,
backtrace: [usize; MAX_BACKTRACE_SIZE], pub current_backtrace_size: usize,
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<Exception<'static>>; 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() {
trace!("reset exception buffer");
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;
} }
type _Unwind_Stop_Fn = extern "C" fn(version: c_int, type _Unwind_Stop_Fn = extern "C" fn(version: c_int,
@ -81,8 +138,7 @@ extern {
unsafe fn find_eh_action( unsafe fn find_eh_action(
context: *mut uw::_Unwind_Context, context: *mut uw::_Unwind_Context,
foreign_exception: bool, foreign_exception: bool,
name: *const u8, id: u32,
len: usize,
) -> Result<EHAction, ()> { ) -> Result<EHAction, ()> {
let lsda = uw::_Unwind_GetLanguageSpecificData(context) as *const u8; let lsda = uw::_Unwind_GetLanguageSpecificData(context) as *const u8;
let mut ip_before_instr: c_int = 0; let mut ip_before_instr: c_int = 0;
@ -95,7 +151,7 @@ unsafe fn find_eh_action(
get_text_start: &|| uw::_Unwind_GetTextRelBase(context), get_text_start: &|| uw::_Unwind_GetTextRelBase(context),
get_data_start: &|| uw::_Unwind_GetDataRelBase(context), get_data_start: &|| uw::_Unwind_GetDataRelBase(context),
}; };
eh::find_eh_action(lsda, &eh_context, foreign_exception, name, len) eh::find_eh_action(lsda, &eh_context, foreign_exception, id)
} }
pub unsafe fn artiq_personality(_state: uw::_Unwind_State, pub unsafe fn artiq_personality(_state: uw::_Unwind_State,
@ -119,19 +175,15 @@ pub unsafe fn artiq_personality(_state: uw::_Unwind_State,
let exception_class = (*exception_object).exception_class; let exception_class = (*exception_object).exception_class;
let foreign_exception = exception_class != EXCEPTION_CLASS; let foreign_exception = exception_class != EXCEPTION_CLASS;
assert!(!foreign_exception, "we do not expect foreign exceptions"); assert!(!foreign_exception, "we do not expect foreign exceptions");
let exception_info = &mut *(exception_object as *mut ExceptionInfo); 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 (name_ptr, len) = if foreign_exception || exception_info.exception.is_none() { let id = exception.id;
(core::ptr::null(), 0) let eh_action = match find_eh_action(context, foreign_exception, id) {
} else {
let name = (exception_info.exception.unwrap()).name;
(name.as_ptr(), name.len())
};
let eh_action = match find_eh_action(context, foreign_exception, name_ptr, len) {
Ok(action) => action, Ok(action) => action,
Err(_) => return uw::_URC_FAILURE, Err(_) => return uw::_URC_FAILURE,
}; };
let exception = &exception_info.exception.unwrap();
match eh_action { match eh_action {
EHAction::None => return continue_unwind(exception_object, context), EHAction::None => return continue_unwind(exception_object, context),
EHAction::Cleanup(lpad) | EHAction::Cleanup(lpad) |
@ -165,99 +217,219 @@ pub unsafe fn artiq_personality(_state: uw::_Unwind_State,
} }
} }
extern fn cleanup(_unwind_code: uw::_Unwind_Reason_Code, pub unsafe extern fn raise(exception: *const Exception) -> ! {
uw_exception: *mut uw::_Unwind_Exception) { use cslice::AsCSlice;
unsafe {
let exception_info = &mut *(uw_exception as *mut ExceptionInfo);
exception_info.exception = None; 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::<Option<Exception>>() * MAX_INFLIGHT_EXCEPTIONS) as isize {
let index = diff / (mem::size_of::<Option<Exception>>() as isize);
trace!("reraise at {}", index);
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 {
trace!("raising exception at level {}", count);
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 {
error!("too many nested exceptions");
// 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!();
} }
static mut INFLIGHT: ExceptionInfo = ExceptionInfo { pub unsafe extern fn resume() -> ! {
uw_exception: uw::_Unwind_Exception { trace!("resume");
exception_class: EXCEPTION_CLASS, assert!(EXCEPTION_BUFFER.exception_count != 0);
exception_cleanup: cleanup, let i = EXCEPTION_BUFFER.exception_stack[EXCEPTION_BUFFER.exception_count - 1];
private: [0; uw::unwinder_private_data_size], assert!(i != -1);
}, let _result = _Unwind_ForcedUnwind(&mut EXCEPTION_BUFFER.uw_exceptions[i as usize],
exception: None, stop_fn, core::ptr::null_mut());
backtrace: [0; MAX_BACKTRACE_SIZE],
backtrace_size: 0
};
pub unsafe extern fn raise(exception: *const Exception) -> ! {
// FIXME: unsound transmute
// This would cause stack memory corruption.
trace!("raising exception");
INFLIGHT.backtrace_size = 0;
INFLIGHT.exception = Some(mem::transmute::<Exception, Exception<'static>>(*exception));
let _result = _Unwind_ForcedUnwind(&mut INFLIGHT.uw_exception,
uncaught_exception, core::ptr::null_mut());
unreachable!() unreachable!()
} }
extern fn uncaught_exception(_version: c_int, pub unsafe extern fn end_catch() {
actions: i32, let mut count = EXCEPTION_BUFFER.exception_count;
_uw_exception_class: uw::_Unwind_Exception_Class, assert!(count != 0);
uw_exception: *mut uw::_Unwind_Exception, // we remove all exceptions with SP <= current exception SP
context: *mut uw::_Unwind_Context, // i.e. the outer exception escapes the finally block
_stop_parameter: *mut c_void) let index = EXCEPTION_BUFFER.exception_stack[count - 1] as usize;
-> uw::_Unwind_Reason_Code { EXCEPTION_BUFFER.exception_stack[count - 1] = -1;
unsafe { EXCEPTION_BUFFER.exceptions[index] = None;
trace!("uncaught exception"); let outer_sp = EXCEPTION_BUFFER.stack_pointers
let exception_info = &mut *(uw_exception as *mut ExceptionInfo); [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
};
}
if exception_info.backtrace_size < exception_info.backtrace.len() { extern fn cleanup(_unwind_code: uw::_Unwind_Reason_Code,
_uw_exception: *mut uw::_Unwind_Exception) {
unimplemented!()
}
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 {
crate::kernel::core1::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())
}
}
// stop function which would be executed when we unwind each frame
extern fn stop_fn(_version: c_int,
actions: i32,
_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 load_addr = KERNEL_IMAGE.as_ref().unwrap().get_load_addr();
let backtrace_size = EXCEPTION_BUFFER.backtrace_size;
// we try to remove unrelated backtrace here to save some buffer size
if backtrace_size < MAX_BACKTRACE_SIZE {
let ip = uw::_Unwind_GetIP(context); let ip = uw::_Unwind_GetIP(context);
trace!("SP: {:X}, backtrace_size: {}", uw::_Unwind_GetGR(context, uw::UNWIND_SP_REG), exception_info.backtrace_size); if ip >= load_addr {
exception_info.backtrace[exception_info.backtrace_size] = ip; let ip = ip - load_addr;
exception_info.backtrace_size += 1; let sp = uw::_Unwind_GetGR(context, uw::UNWIND_SP_REG);
trace!("SP: {:X}, backtrace_size: {}", sp, backtrace_size);
EXCEPTION_BUFFER.backtrace[backtrace_size] = (ip, sp);
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 = sp;
sp_info.current_backtrace_size = backtrace_size + 1;
}
} else {
trace!("backtrace size exceeded");
} }
if actions as u32 & uw::_US_END_OF_STACK as u32 != 0 { if actions as u32 & uw::_US_END_OF_STACK as u32 != 0 {
crate::kernel::core1::terminate(exception_info.exception.as_ref().unwrap(), uncaught_exception()
exception_info.backtrace[..exception_info.backtrace_size].as_mut())
} else { } else {
uw::_URC_NO_REASON uw::_URC_NO_REASON
} }
} }
} }
pub unsafe extern fn reraise() -> ! { static EXCEPTION_ID_LOOKUP: [(&str, u32); 6] = [
use cslice::AsCSlice; ("runtimeerror", 0),
("RTIOUnderflow", 1),
("RTIOOverflow", 2),
("RTIODestinationUnreachable", 3),
("DMAError", 4),
("I2CError", 5),
];
// Reraise is basically cxa_rethrow, which calls _Unwind_Resume_or_Rethrow, pub fn get_exception_id(name: &str) -> u32 {
// which for EHABI would always call _Unwind_RaiseException. for (n, id) in EXCEPTION_ID_LOOKUP.iter() {
match INFLIGHT.exception { if *n == name {
Some(ex) => { return *id
// we cannot call raise directly as that would corrupt the backtrace
INFLIGHT.exception = Some(mem::transmute::<Exception, Exception<'static>>(ex));
let _result = _Unwind_ForcedUnwind(&mut INFLIGHT.uw_exception,
uncaught_exception, core::ptr::null_mut());
unreachable!()
},
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]
})
} }
} }
unimplemented!("unallocated internal exception id")
} }
#[macro_export] #[macro_export]
macro_rules! artiq_raise { macro_rules! artiq_raise {
($name:expr, $message:expr, $param0:expr, $param1:expr, $param2:expr) => ({ ($name:expr, $message:expr, $param0:expr, $param1:expr, $param2:expr) => ({
use cslice::AsCSlice; use cslice::AsCSlice;
let name_id = $crate::eh_artiq::get_exception_id($name);
let exn = $crate::eh_artiq::Exception { let exn = $crate::eh_artiq::Exception {
name: concat!("0:artiq.coredevice.exceptions.", $name).as_c_slice(), id: name_id,
file: file!().as_c_slice(), file: file!().as_c_slice(),
line: line!(), line: line!(),
column: column!(), column: column!(),

View File

@ -204,7 +204,8 @@ pub fn resolve(required: &[u8]) -> Option<u32> {
api!(_Unwind_Resume = unwind::_Unwind_Resume), api!(_Unwind_Resume = unwind::_Unwind_Resume),
api!(__artiq_personality = eh_artiq::artiq_personality), api!(__artiq_personality = eh_artiq::artiq_personality),
api!(__artiq_raise = eh_artiq::raise), 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),
// Implementations for LLVM math intrinsics // Implementations for LLVM math intrinsics
api!(__powidf2), api!(__powidf2),

View File

@ -182,6 +182,7 @@ pub extern "C" fn main_core1() {
info!("kernel starting"); info!("kernel starting");
if let Some(kernel) = loaded_kernel.take() { if let Some(kernel) = loaded_kernel.take() {
unsafe { unsafe {
eh_artiq::reset_exception_buffer();
KERNEL_CHANNEL_0TO1 = Some(core1_rx); KERNEL_CHANNEL_0TO1 = Some(core1_rx);
KERNEL_CHANNEL_1TO0 = Some(core1_tx); KERNEL_CHANNEL_1TO0 = Some(core1_tx);
KERNEL_IMAGE = &kernel as *const KernelImage; KERNEL_IMAGE = &kernel as *const KernelImage;
@ -201,24 +202,13 @@ pub extern "C" fn main_core1() {
} }
/// Called by eh_artiq /// Called by eh_artiq
pub fn terminate(exception: &'static eh_artiq::Exception<'static>, backtrace: &'static mut [usize]) -> ! { pub fn terminate(exceptions: &'static [Option<eh_artiq::Exception<'static>>],
let load_addr = unsafe { stack_pointers: &'static [eh_artiq::StackPointerBacktrace],
KERNEL_IMAGE.as_ref().unwrap().get_load_addr() backtrace: &'static mut [(usize, usize)]) -> ! {
};
let mut cursor = 0;
// The address in the backtrace is relocated, so we have to convert it back to the address in
// the original python script, and remove those Rust function backtrace.
for i in 0..backtrace.len() {
if backtrace[i] >= load_addr {
backtrace[cursor] = backtrace[i] - load_addr;
cursor += 1;
}
}
{ {
let core1_tx = unsafe { KERNEL_CHANNEL_1TO0.as_mut().unwrap() }; let core1_tx = unsafe { KERNEL_CHANNEL_1TO0.as_mut().unwrap() };
let errors = unsafe { get_async_errors() }; let errors = unsafe { get_async_errors() };
core1_tx.send(Message::KernelException(exception, &backtrace[..cursor], errors)); core1_tx.send(Message::KernelException(exceptions, stack_pointers, backtrace, errors));
} }
loop {} loop {}
} }

View File

@ -15,13 +15,13 @@ mod cache;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct RPCException { pub struct RPCException {
pub name: String, pub id: u32,
pub message: String, pub message: u32,
pub param: [i64; 3], pub param: [i64; 3],
pub file: String, pub file: u32,
pub line: i32, pub line: i32,
pub column: i32, pub column: i32,
pub function: String pub function: u32
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -31,7 +31,10 @@ pub enum Message {
LoadFailed, LoadFailed,
StartRequest, StartRequest,
KernelFinished(u8), KernelFinished(u8),
KernelException(&'static eh_artiq::Exception<'static>, &'static [usize], u8), KernelException(&'static [Option<eh_artiq::Exception<'static>>],
&'static [eh_artiq::StackPointerBacktrace],
&'static [(usize, usize)],
u8),
RpcSend { is_async: bool, data: Vec<u8> }, RpcSend { is_async: bool, data: Vec<u8> },
RpcRecvRequest(*mut ()), RpcRecvRequest(*mut ()),
RpcRecvReply(Result<usize, RPCException>), RpcRecvReply(Result<usize, RPCException>),
@ -53,7 +56,7 @@ static CHANNEL_SEM: Semaphore = Semaphore::new(0, 1);
static mut KERNEL_CHANNEL_0TO1: Option<sync_channel::Receiver<'static, Message>> = None; static mut KERNEL_CHANNEL_0TO1: Option<sync_channel::Receiver<'static, Message>> = None;
static mut KERNEL_CHANNEL_1TO0: Option<sync_channel::Sender<'static, Message>> = None; static mut KERNEL_CHANNEL_1TO0: Option<sync_channel::Sender<'static, Message>> = None;
static mut KERNEL_IMAGE: *const core1::KernelImage = ptr::null(); pub static mut KERNEL_IMAGE: *const core1::KernelImage = ptr::null();
static INIT_LOCK: Mutex<()> = Mutex::new(()); static INIT_LOCK: Mutex<()> = Mutex::new(());

View File

@ -1,7 +1,7 @@
//! Kernel-side RPC API //! Kernel-side RPC API
use alloc::vec::Vec; use alloc::vec::Vec;
use cslice::{CSlice, AsCSlice}; use cslice::CSlice;
use crate::eh_artiq; use crate::eh_artiq;
use crate::rpc::send_args; use crate::rpc::send_args;
@ -36,12 +36,12 @@ pub extern fn rpc_recv(slot: *mut ()) -> usize {
Message::RpcRecvReply(Ok(alloc_size)) => alloc_size, Message::RpcRecvReply(Ok(alloc_size)) => alloc_size,
Message::RpcRecvReply(Err(exception)) => unsafe { Message::RpcRecvReply(Err(exception)) => unsafe {
eh_artiq::raise(&eh_artiq::Exception { eh_artiq::raise(&eh_artiq::Exception {
name: exception.name.as_bytes().as_c_slice(), id: exception.id,
file: exception.file.as_bytes().as_c_slice(), file: CSlice::new(exception.file as *const u8, usize::MAX),
line: exception.line as u32, line: exception.line as u32,
column: exception.column as u32, column: exception.column as u32,
function: exception.function.as_bytes().as_c_slice(), function: CSlice::new(exception.function as *const u8, usize::MAX),
message: exception.message.as_bytes().as_c_slice(), message: CSlice::new(exception.message as *const u8, usize::MAX),
param: exception.param param: exception.param
}) })
}, },