artiq/artiq/firmware/ksupport/eh.rs

461 lines
16 KiB
Rust
Raw Normal View History

// Portions of the code in this file are derived from code by:
//
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![allow(non_upper_case_globals, non_camel_case_types, dead_code)]
use core::{ptr, mem};
use cslice::CSlice;
use unwind as uw;
use libc::{c_int, c_void};
type _Unwind_Stop_Fn = extern "C" fn(version: c_int,
actions: uw::_Unwind_Action,
exception_class: uw::_Unwind_Exception_Class,
exception_object: *mut uw::_Unwind_Exception,
context: *mut uw::_Unwind_Context,
stop_parameter: *mut c_void)
-> uw::_Unwind_Reason_Code;
extern {
fn _Unwind_ForcedUnwind(exception: *mut uw::_Unwind_Exception,
stop_fn: _Unwind_Stop_Fn,
stop_parameter: *mut c_void) -> uw::_Unwind_Reason_Code;
}
const DW_EH_PE_omit: u8 = 0xFF;
const DW_EH_PE_absptr: u8 = 0x00;
const DW_EH_PE_uleb128: u8 = 0x01;
const DW_EH_PE_udata2: u8 = 0x02;
const DW_EH_PE_udata4: u8 = 0x03;
const DW_EH_PE_udata8: u8 = 0x04;
const DW_EH_PE_sleb128: u8 = 0x09;
const DW_EH_PE_sdata2: u8 = 0x0A;
const DW_EH_PE_sdata4: u8 = 0x0B;
const DW_EH_PE_sdata8: u8 = 0x0C;
const DW_EH_PE_pcrel: u8 = 0x10;
const DW_EH_PE_textrel: u8 = 0x20;
const DW_EH_PE_datarel: u8 = 0x30;
const DW_EH_PE_funcrel: u8 = 0x40;
const DW_EH_PE_aligned: u8 = 0x50;
const DW_EH_PE_indirect: u8 = 0x80;
#[derive(Clone)]
struct DwarfReader {
pub ptr: *const u8,
}
#[repr(C,packed)]
#[derive(Clone, Copy)]
struct Unaligned<T>(T);
// This contortion is required due to https://github.com/rust-lang/rust/issues/27060.
impl<T> Unaligned<T> {
fn get(self) -> T { self.0 }
}
impl DwarfReader {
fn new(ptr: *const u8) -> DwarfReader {
DwarfReader { ptr: ptr }
}
// DWARF streams are packed, so e.g. a u32 would not necessarily be aligned
// on a 4-byte boundary. This may cause problems on platforms with strict
// alignment requirements. By wrapping data in a "packed" struct, we are
// telling the backend to generate "misalignment-safe" code.
unsafe fn read<T: Copy>(&mut self) -> T {
let result = *(self.ptr as *const Unaligned<T>);
self.ptr = self.ptr.offset(mem::size_of::<T>() as isize);
result.get()
}
// ULEB128 and SLEB128 encodings are defined in Section 7.6 - "Variable
// Length Data".
unsafe fn read_uleb128(&mut self) -> u64 {
let mut shift: usize = 0;
let mut result: u64 = 0;
let mut byte: u8;
loop {
byte = self.read::<u8>();
result |= ((byte & 0x7F) as u64) << shift;
shift += 7;
if byte & 0x80 == 0 {
break;
}
}
result
}
unsafe fn read_sleb128(&mut self) -> i64 {
let mut shift: usize = 0;
let mut result: u64 = 0;
let mut byte: u8;
loop {
byte = self.read::<u8>();
result |= ((byte & 0x7F) as u64) << shift;
shift += 7;
if byte & 0x80 == 0 {
break;
}
}
// sign-extend
if shift < 8 * mem::size_of::<u64>() && (byte & 0x40) != 0 {
result |= (!0 as u64) << shift;
}
result as i64
}
unsafe fn read_encoded_pointer(&mut self, encoding: u8) -> usize {
fn round_up(unrounded: usize, align: usize) -> usize {
debug_assert!(align.is_power_of_two());
(unrounded + align - 1) & !(align - 1)
}
debug_assert!(encoding != DW_EH_PE_omit);
// DW_EH_PE_aligned implies it's an absolute pointer value
if encoding == DW_EH_PE_aligned {
self.ptr = round_up(self.ptr as usize, mem::size_of::<usize>()) as *const u8;
return self.read::<usize>()
}
let value_ptr = self.ptr;
let mut result = match encoding & 0x0F {
DW_EH_PE_absptr => self.read::<usize>(),
DW_EH_PE_uleb128 => self.read_uleb128() as usize,
DW_EH_PE_udata2 => self.read::<u16>() as usize,
DW_EH_PE_udata4 => self.read::<u32>() as usize,
DW_EH_PE_udata8 => self.read::<u64>() as usize,
DW_EH_PE_sleb128 => self.read_sleb128() as usize,
DW_EH_PE_sdata2 => self.read::<i16>() as usize,
DW_EH_PE_sdata4 => self.read::<i32>() as usize,
DW_EH_PE_sdata8 => self.read::<i64>() as usize,
_ => panic!(),
};
result += match encoding & 0x70 {
DW_EH_PE_absptr => 0,
// relative to address of the encoded value, despite the name
DW_EH_PE_pcrel => value_ptr as usize,
_ => panic!(),
};
if encoding & DW_EH_PE_indirect != 0 {
result = *(result as *const usize);
}
result
}
}
fn encoding_size(encoding: u8) -> usize {
if encoding == DW_EH_PE_omit {
return 0
}
match encoding & 0x0F {
DW_EH_PE_absptr => mem::size_of::<usize>(),
DW_EH_PE_udata2 => 2,
DW_EH_PE_udata4 => 4,
DW_EH_PE_udata8 => 8,
DW_EH_PE_sdata2 => 2,
DW_EH_PE_sdata4 => 4,
DW_EH_PE_sdata8 => 8,
_ => panic!()
}
}
pub enum EHAction {
None,
Cleanup(usize),
Catch(usize),
Terminate,
}
unsafe fn find_eh_action(lsda: *const u8, func_start: usize, ip: usize,
exn_name: CSlice<u8>) -> EHAction {
if lsda.is_null() {
return EHAction::None
}
let mut reader = DwarfReader::new(lsda);
let start_encoding = reader.read::<u8>();
// base address for landing pad offsets
let lpad_base = if start_encoding != DW_EH_PE_omit {
reader.read_encoded_pointer(start_encoding)
} else {
func_start
};
let ttype_encoding = reader.read::<u8>();
let ttype_encoding_size = encoding_size(ttype_encoding) as isize;
let class_info;
if ttype_encoding != DW_EH_PE_omit {
let class_info_offset = reader.read_uleb128();
class_info = reader.ptr.offset(class_info_offset as isize);
} else {
class_info = ptr::null();
}
assert!(!class_info.is_null());
let call_site_encoding = reader.read::<u8>();
let call_site_table_length = reader.read_uleb128();
let action_table = reader.ptr.offset(call_site_table_length as isize);
while reader.ptr < action_table {
let cs_start = reader.read_encoded_pointer(call_site_encoding);
let cs_len = reader.read_encoded_pointer(call_site_encoding);
let cs_lpad = reader.read_encoded_pointer(call_site_encoding);
let cs_action = reader.read_uleb128();
if ip < func_start + cs_start {
// Callsite table is sorted by cs_start, so if we've passed the ip, we
// may stop searching.
break
}
if ip > func_start + cs_start + cs_len {
continue
}
if cs_lpad == 0 {
return EHAction::None
}
let lpad = lpad_base + cs_lpad;
if cs_action == 0 {
return EHAction::Cleanup(lpad)
}
let action_entry = action_table.offset((cs_action - 1) as isize);
let mut action_reader = DwarfReader::new(action_entry);
loop {
let type_info_offset = action_reader.read_sleb128() as isize;
let action_offset = action_reader.clone().read_sleb128() as isize;
assert!(type_info_offset >= 0);
if type_info_offset > 0 {
let type_info_ptr_ptr = class_info.offset(-type_info_offset * ttype_encoding_size);
let type_info_ptr = DwarfReader::new(type_info_ptr_ptr)
.read_encoded_pointer(ttype_encoding);
let type_info = *(type_info_ptr as *const CSlice<u8>);
if type_info.as_ref() == exn_name.as_ref() {
return EHAction::Catch(lpad)
}
if type_info.len() == 0 {
// This is a catch-all clause. We don't compare type_info_ptr with null here
// because, in PIC mode, the OR1K LLVM backend emits a literal zero
// encoded with DW_EH_PE_pcrel, which of course doesn't result in
// a proper null pointer.
return EHAction::Catch(lpad)
}
}
if action_offset == 0 {
break
} else {
action_reader.ptr = action_reader.ptr.offset(action_offset)
}
}
return EHAction::None
}
// the function has a personality but no landing pads; this is fine
EHAction::None
}
#[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]
}
const EXCEPTION_CLASS: uw::_Unwind_Exception_Class = 0x4d_4c_42_53_41_52_54_51; /* 'MLBSARTQ' */
const MAX_BACKTRACE_SIZE: usize = 128;
#[repr(C)]
struct ExceptionInfo {
uw_exception: uw::_Unwind_Exception,
exception: Option<Exception<'static>>,
handled: bool,
backtrace: [usize; MAX_BACKTRACE_SIZE],
backtrace_size: usize
}
#[cfg(target_arch = "x86_64")]
const UNWIND_DATA_REG: (i32, i32) = (0, 1); // RAX, RDX
#[cfg(any(target_arch = "or1k"))]
const UNWIND_DATA_REG: (i32, i32) = (3, 4); // R3, R4
#[export_name="__artiq_personality"]
pub extern fn personality(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)
-> uw::_Unwind_Reason_Code {
unsafe {
if version != 1 || uw_exception_class != EXCEPTION_CLASS {
return uw::_URC_FATAL_PHASE1_ERROR
}
let lsda = uw::_Unwind_GetLanguageSpecificData(context) as *const u8;
let ip = uw::_Unwind_GetIP(context) - 1;
let func_start = uw::_Unwind_GetRegionStart(context);
let exception_info = &mut *(uw_exception as *mut ExceptionInfo);
let exception = &exception_info.exception.unwrap();
let eh_action = find_eh_action(lsda, func_start, ip, exception.name);
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,
}
}
}
}
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);
exception_info.exception = None;
}
}
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 {
unsafe {
let exception_info = &mut *(uw_exception as *mut ExceptionInfo);
if exception_info.backtrace_size < exception_info.backtrace.len() {
let ip = uw::_Unwind_GetIP(context);
exception_info.backtrace[exception_info.backtrace_size] = ip;
exception_info.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())
} 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: false,
backtrace: [0; MAX_BACKTRACE_SIZE],
backtrace_size: 0
};
#[export_name="__artiq_raise"]
#[unwind]
pub unsafe extern fn raise(exception: *const Exception) -> ! {
// Zing! The Exception<'a> as Exception<'static> cast 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, Exception<'static>>(*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]
pub unsafe extern fn reraise() -> ! {
if INFLIGHT.handled {
raise(&INFLIGHT.exception.unwrap())
} else {
uw::_Unwind_Resume(&mut INFLIGHT.uw_exception)
}
}
// Stub implementations for the functions the panic_unwind crate expects to be provided.
// These all do nothing in libunwind, but aren't built for OR1K.
pub mod stubs {
#![allow(bad_style, unused_variables)]
use super::{uw, c_int};
#[export_name="_Unwind_GetIPInfo"]
pub unsafe extern fn _Unwind_GetIPInfo(ctx: *mut uw::_Unwind_Context,
ip_before_insn: *mut c_int) -> uw::_Unwind_Word {
*ip_before_insn = 0;
uw::_Unwind_GetIP(ctx)
}
#[export_name="_Unwind_GetTextRelBase"]
pub unsafe extern fn _Unwind_GetTextRelBase(ctx: *mut uw::_Unwind_Context) -> uw::_Unwind_Ptr {
unimplemented!()
}
#[export_name="_Unwind_GetDataRelBase"]
pub unsafe extern fn _Unwind_GetDataRelBase(ctx: *mut uw::_Unwind_Context) -> uw::_Unwind_Ptr {
unimplemented!()
}
}