Эх сурвалжийг харах

Implement rust personality

Gary Guo 3 жил өмнө
parent
commit
f0e87da0aa
5 өөрчлөгдсөн 191 нэмэгдсэн , 3 устгасан
  1. 1 0
      Cargo.toml
  2. 2 3
      src/abi.rs
  3. 4 0
      src/arch/x86_64.rs
  4. 3 0
      src/lib.rs
  5. 181 0
      src/personality.rs

+ 1 - 0
Cargo.toml

@@ -18,6 +18,7 @@ fde-phdr = ["libc"]
 fde-registry = ["alloc"]
 dwarf-expr = []
 hide-trace = []
+personality = []
 personality-dummy = []
 system-alloc = []
 default = ["dwarf-expr", "hide-trace", "fde-phdr", "fde-registry"]

+ 2 - 3
src/abi.rs

@@ -75,6 +75,7 @@ pub struct UnwindException {
     pub exception_cleanup: Option<UnwindExceptionCleanupFn>,
     private_1: Option<UnwindStopFn>,
     private_2: usize,
+    private_unused: [usize; Arch::UNWIND_PRIVATE_DATA_SIZE - 2],
 }
 
 pub type UnwindTraceFn =
@@ -128,9 +129,7 @@ pub extern "C" fn _Unwind_SetIP(unwind_ctx: &mut UnwindContext<'_>, value: usize
 }
 
 #[no_mangle]
-pub extern "C" fn _Unwind_GetLanguageSpecificData(
-    unwind_ctx: &UnwindContext<'_>,
-) -> *mut c_void {
+pub extern "C" fn _Unwind_GetLanguageSpecificData(unwind_ctx: &UnwindContext<'_>) -> *mut c_void {
     unwind_ctx
         .frame
         .map(|f| f.lsda() as *mut c_void)

+ 4 - 0
src/arch/x86_64.rs

@@ -13,9 +13,13 @@ pub struct Context {
 
 pub struct Arch;
 
+#[allow(unused)]
 impl Arch {
     pub const SP: Register = X86_64::RSP;
     pub const RA: Register = X86_64::RA;
+
+    pub const UNWIND_DATA_REG: (Register, Register) = (X86_64::RAX, X86_64::RDX);
+    pub const UNWIND_PRIVATE_DATA_SIZE: usize = 6;
 }
 
 impl fmt::Debug for Context {

+ 3 - 0
src/lib.rs

@@ -1,6 +1,7 @@
 #![feature(c_unwind)]
 #![feature(naked_functions)]
 #![feature(asm)]
+#![cfg_attr(any(feature = "personality", feature = "personality-dummy"), feature(lang_items))]
 #![warn(rust_2018_idioms)]
 #![warn(unsafe_op_in_unsafe_fn)]
 #![no_std]
@@ -14,6 +15,8 @@ mod find_fde;
 mod frame;
 mod util;
 
+#[cfg(feature = "personality")]
+mod personality;
 #[cfg(feature = "personality-dummy")]
 mod personality_dummy;
 

+ 181 - 0
src/personality.rs

@@ -0,0 +1,181 @@
+// References:
+// https://github.com/rust-lang/rust/blob/c4be230b4a30eb74e3a3908455731ebc2f731d3d/library/panic_unwind/src/gcc.rs
+// https://github.com/rust-lang/rust/blob/c4be230b4a30eb74e3a3908455731ebc2f731d3d/library/panic_unwind/src/dwarf/eh.rs
+// https://docs.rs/gimli/0.25.0/src/gimli/read/cfi.rs.html
+
+use core::mem;
+use gimli::{constants, NativeEndian};
+use gimli::{EndianSlice, Error, Pointer, Reader};
+
+use crate::abi::*;
+use crate::arch::*;
+use crate::util::*;
+
+#[derive(Debug)]
+enum EHAction {
+    None,
+    Cleanup(usize),
+    Catch(usize),
+}
+
+fn parse_pointer_encoding(input: &mut StaticSlice) -> gimli::Result<constants::DwEhPe> {
+    let eh_pe = input.read_u8()?;
+    let eh_pe = constants::DwEhPe(eh_pe);
+
+    if eh_pe.is_valid_encoding() {
+        Ok(eh_pe)
+    } else {
+        Err(gimli::Error::UnknownPointerEncoding)
+    }
+}
+
+fn parse_encoded_pointer(
+    encoding: constants::DwEhPe,
+    unwind_ctx: &UnwindContext<'_>,
+    input: &mut StaticSlice,
+) -> gimli::Result<Pointer> {
+    if encoding == constants::DW_EH_PE_omit {
+        return Err(Error::CannotParseOmitPointerEncoding);
+    }
+
+    let base = match encoding.application() {
+        constants::DW_EH_PE_absptr => 0,
+        constants::DW_EH_PE_pcrel => input.slice().as_ptr() as u64,
+        constants::DW_EH_PE_textrel => _Unwind_GetTextRelBase(unwind_ctx) as u64,
+        constants::DW_EH_PE_datarel => _Unwind_GetDataRelBase(unwind_ctx) as u64,
+        constants::DW_EH_PE_funcrel => _Unwind_GetRegionStart(unwind_ctx) as u64,
+        constants::DW_EH_PE_aligned => return Err(Error::UnsupportedPointerEncoding),
+        _ => unreachable!(),
+    };
+
+    let offset = match encoding.format() {
+        constants::DW_EH_PE_absptr => input.read_address(mem::size_of::<usize>() as _),
+        constants::DW_EH_PE_uleb128 => input.read_uleb128(),
+        constants::DW_EH_PE_udata2 => input.read_u16().map(u64::from),
+        constants::DW_EH_PE_udata4 => input.read_u32().map(u64::from),
+        constants::DW_EH_PE_udata8 => input.read_u64(),
+        constants::DW_EH_PE_sleb128 => input.read_sleb128().map(|a| a as u64),
+        constants::DW_EH_PE_sdata2 => input.read_i16().map(|a| a as u64),
+        constants::DW_EH_PE_sdata4 => input.read_i32().map(|a| a as u64),
+        constants::DW_EH_PE_sdata8 => input.read_i64().map(|a| a as u64),
+        _ => unreachable!(),
+    }?;
+
+    let address = base.wrapping_add(offset);
+    Ok(if encoding.is_indirect() {
+        Pointer::Indirect(address)
+    } else {
+        Pointer::Direct(address)
+    })
+}
+
+fn find_eh_action(
+    reader: &mut StaticSlice,
+    unwind_ctx: &UnwindContext<'_>,
+) -> gimli::Result<EHAction> {
+    let func_start = _Unwind_GetRegionStart(unwind_ctx);
+    let mut ip_before_instr = 0;
+    let ip = _Unwind_GetIPInfo(unwind_ctx, &mut ip_before_instr);
+    let ip = if ip_before_instr != 0 { ip } else { ip - 1 };
+
+    let start_encoding = parse_pointer_encoding(reader)?;
+    let lpad_base = if !start_encoding.is_absent() {
+        unsafe { deref_pointer(parse_encoded_pointer(start_encoding, unwind_ctx, reader)?) }
+    } else {
+        func_start
+    };
+
+    let ttype_encoding = parse_pointer_encoding(reader)?;
+    if !ttype_encoding.is_absent() {
+        reader.read_uleb128()?;
+    }
+
+    let call_site_encoding = parse_pointer_encoding(reader)?;
+    let call_site_table_length = reader.read_uleb128()?;
+    reader.truncate(call_site_table_length as _)?;
+
+    while !reader.is_empty() {
+        let cs_start = unsafe {
+            deref_pointer(parse_encoded_pointer(
+                call_site_encoding,
+                unwind_ctx,
+                reader,
+            )?)
+        };
+        let cs_len = unsafe {
+            deref_pointer(parse_encoded_pointer(
+                call_site_encoding,
+                unwind_ctx,
+                reader,
+            )?)
+        };
+        let cs_lpad = unsafe {
+            deref_pointer(parse_encoded_pointer(
+                call_site_encoding,
+                unwind_ctx,
+                reader,
+            )?)
+        };
+        let cs_action = reader.read_uleb128()?;
+        if ip < func_start + cs_start {
+            break;
+        }
+        if ip < func_start + cs_start + cs_len {
+            if cs_lpad == 0 {
+                return Ok(EHAction::None);
+            } else {
+                let lpad = lpad_base + cs_lpad;
+                return Ok(match cs_action {
+                    0 => EHAction::Cleanup(lpad),
+                    _ => EHAction::Catch(lpad),
+                });
+            }
+        }
+    }
+    Ok(EHAction::None)
+}
+
+#[lang = "eh_personality"]
+fn rust_eh_personality(
+    version: c_int,
+    actions: UnwindAction,
+    _exception_class: u64,
+    exception: &mut UnwindException,
+    unwind_ctx: &mut UnwindContext<'_>,
+) -> UnwindReasonCode {
+    if version != 1 {
+        return UnwindReasonCode::FATAL_PHASE1_ERROR;
+    }
+
+    let lsda = _Unwind_GetLanguageSpecificData(unwind_ctx);
+    if lsda.is_null() {
+        return UnwindReasonCode::CONTINUE_UNWIND;
+    }
+
+    let mut lsda = EndianSlice::new(unsafe { get_unlimited_slice(lsda as _) }, NativeEndian);
+    let eh_action = match find_eh_action(&mut lsda, unwind_ctx) {
+        Ok(v) => v,
+        Err(_) => return UnwindReasonCode::FATAL_PHASE1_ERROR,
+    };
+
+    if actions.contains(UnwindAction::SEARCH_PHASE) {
+        match eh_action {
+            EHAction::None | EHAction::Cleanup(_) => UnwindReasonCode::CONTINUE_UNWIND,
+            EHAction::Catch(_) => UnwindReasonCode::HANDLER_FOUND,
+        }
+    } else {
+        match eh_action {
+            EHAction::None => UnwindReasonCode::CONTINUE_UNWIND,
+            EHAction::Cleanup(lpad) | EHAction::Catch(lpad) => {
+                _Unwind_SetGR(
+                    unwind_ctx,
+                    Arch::UNWIND_DATA_REG.0 .0 as _,
+                    exception as *mut _ as usize,
+                );
+                _Unwind_SetGR(unwind_ctx, Arch::UNWIND_DATA_REG.1 .0 as _, 0);
+                _Unwind_SetIP(unwind_ctx, lpad);
+                UnwindReasonCode::INSTALL_CONTEXT
+            }
+        }
+    }
+}