use core::{ffi::CStr, ops::Deref};
use std::vec::Vec;
use crate::abi::{FlatCStr, inline_cstr, pack_cstr};
use crate::arch::{CpuExceptionContext, GuestAddr};
mod generated {
#![allow(non_camel_case_types, non_upper_case_globals, dead_code)]
include!("generated.rs");
}
pub use generated::*;
#[repr(transparent)]
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct GuestOs(pub u32);
impl GuestOs {
pub const ANY: Self = Self(BARYL_OS_ANY);
pub const WINDOWS: Self = Self(BARYL_OS_WINDOWS);
pub const LINUX: Self = Self(BARYL_OS_LINUX);
pub const MACOS: Self = Self(BARYL_OS_MACOS);
}
impl EnlightenVtable {
pub const ABSENT: EnlightenVtable = EnlightenVtable {
ready: None,
processes: None,
process_by_cr3: None,
process_by_name: None,
threads: None,
current_thread: None,
kernel_modules: None,
user_modules: None,
module_at: None,
resolve_symbol: None,
free_modules: None,
symbolize: None,
classify_fault: None,
record_crash: None,
};
}
crate::abi::impl_sandbox_safe!(SymbolizedBlock);
impl SymbolizedBlock {
pub fn resolved(process: &CStr, module: &CStr, offset: u64) -> SymbolizedBlock {
SymbolizedBlock {
process: pack_cstr(process),
module: pack_cstr(module),
offset: offset,
}
}
pub fn unresolved(process: &CStr) -> SymbolizedBlock {
SymbolizedBlock::resolved(process, c"unresolved", BARYL_OFFSET_NONE as u64)
}
pub fn process(&self) -> &CStr {
inline_cstr(&self.process)
}
pub fn module(&self) -> &CStr {
inline_cstr(&self.module)
}
pub fn module_offset(&self) -> Option<u64> {
(self.offset != BARYL_OFFSET_NONE as u64).then_some(self.offset)
}
}
impl Process {
pub fn name(&self) -> &CStr {
inline_cstr(&self.name)
}
}
impl Thread {
pub fn name(&self) -> &CStr {
inline_cstr(&self.name)
}
}
impl Module {
pub fn name(&self) -> &CStr {
unsafe { CStr::from_ptr(self.name) }
}
pub fn file(&self) -> Option<&CStr> {
(!self.file.is_null()).then(|| unsafe { CStr::from_ptr(self.file) })
}
pub fn covers(&self, va: u64) -> bool {
va >= self.base_va && va - self.base_va < self.size
}
}
pub struct OwnedModule {
raw: Module,
enl: EnlRef,
}
impl Drop for OwnedModule {
fn drop(&mut self) {
let Some(f) = self.enl.table().and_then(|v| v.free_modules) else {
return;
};
unsafe { f(self.enl, &raw mut self.raw, 1) }
}
}
impl Deref for OwnedModule {
type Target = Module;
fn deref(&self) -> &Module {
&self.raw
}
}
pub struct OwnedModules {
raw: Vec<Module>,
enl: EnlRef,
}
impl Drop for OwnedModules {
fn drop(&mut self) {
let Some(f) = self.enl.table().and_then(|v| v.free_modules) else {
return;
};
unsafe { f(self.enl, self.raw.as_mut_ptr(), self.raw.len() as u64) }
}
}
impl Deref for OwnedModules {
type Target = [Module];
fn deref(&self) -> &[Module] {
&self.raw
}
}
fn collect<T>(mut f: impl FnMut(*mut T, u64) -> u64) -> Vec<T> {
let total = f(core::ptr::null_mut(), 0) as usize;
let mut out: Vec<T> = Vec::with_capacity(total);
let n = f(out.as_mut_ptr(), total as u64) as usize;
unsafe { out.set_len(n.min(total)) };
out
}
impl EnlRef {
fn table(&self) -> Option<&EnlightenVtable> {
unsafe { self.vtable.as_ref() }
}
pub fn ready(&self) -> bool {
self.table()
.and_then(|v| v.ready)
.is_some_and(|f| unsafe { f(*self) } == 0)
}
pub fn processes(&self) -> Vec<Process> {
let Some(f) = self.table().and_then(|v| v.processes) else {
return Vec::new();
};
collect(|out, cap| unsafe { f(*self, out, cap) })
}
pub fn process_by_cr3(&self, cr3: u64) -> Option<Process> {
let f = self.table().and_then(|v| v.process_by_cr3)?;
let mut out = Process::default();
(unsafe { f(*self, cr3, &raw mut out) } == 0).then_some(out)
}
pub fn process_by_name(&self, name: &str) -> Option<Process> {
let f = self.table().and_then(|v| v.process_by_name)?;
let name: FlatCStr<MAX_NAME_BYTES> = FlatCStr::try_new(name)?;
let mut out = Process::default();
(unsafe { f(*self, name.as_ptr(), &raw mut out) } == 0).then_some(out)
}
pub fn threads(&self, proc: &Process) -> Vec<Thread> {
let Some(f) = self.table().and_then(|v| v.threads) else {
return Vec::new();
};
collect(|out, cap| unsafe { f(*self, core::ptr::from_ref(proc), out, cap) })
}
pub fn current_thread(&self) -> Option<Thread> {
let f = self.table().and_then(|v| v.current_thread)?;
let mut out = Thread::default();
(unsafe { f(*self, &raw mut out) } == 0).then_some(out)
}
pub fn kernel_modules(&self) -> OwnedModules {
let Some(f) = self.table().and_then(|v| v.kernel_modules) else {
return OwnedModules { raw: Vec::new(), enl: *self };
};
OwnedModules {
raw: collect(|out, cap| unsafe { f(*self, out, cap) }),
enl: *self,
}
}
pub fn user_modules(&self, proc: &Process) -> OwnedModules {
let Some(f) = self.table().and_then(|v| v.user_modules) else {
return OwnedModules { raw: Vec::new(), enl: *self };
};
OwnedModules {
raw: collect(|out, cap| unsafe { f(*self, core::ptr::from_ref(proc), out, cap) }),
enl: *self,
}
}
pub fn module_at(&self, proc: Option<&Process>, va: u64) -> Option<(OwnedModule, u64)> {
let f = self.table().and_then(|v| v.module_at)?;
let (mut out, mut off) = (Module::default(), 0u64);
let rc = unsafe { f(*self, opt_ptr(proc), va, &raw mut out, &raw mut off) };
(rc == 0).then_some((OwnedModule { raw: out, enl: *self }, off))
}
pub fn resolve_symbol(&self, proc: Option<&Process>, spec: &str) -> Option<u64> {
let f = self.table().and_then(|v| v.resolve_symbol)?;
let spec: FlatCStr<MAX_NAME_BYTES> = FlatCStr::try_new(spec)?;
let mut out = 0u64;
let rc = unsafe { f(*self, opt_ptr(proc), spec.as_ptr(), &raw mut out) };
(rc == 0).then_some(out)
}
pub fn symbolize(&self, key: &GuestAddr) -> SymbolizedBlock {
let Some(f) = self.table().and_then(|v| v.symbolize) else {
return SymbolizedBlock::unresolved(c"unknown");
};
let mut out = SymbolizedBlock::default();
unsafe { f(*self, core::ptr::from_ref(key), &raw mut out) };
out
}
}
const MAX_NAME_BYTES: usize = 256;
fn opt_ptr(proc: Option<&Process>) -> *const Process {
proc.map_or(core::ptr::null(), core::ptr::from_ref)
}
impl EnlRef {
pub fn classify_fault(&self, ctx: &CpuExceptionContext) -> Option<GuestCrash> {
let f = self.table().and_then(|v| v.classify_fault)?;
let mut out = GuestCrash::default();
(unsafe { f(*self, core::ptr::from_ref(ctx), &raw mut out) } == 1).then_some(out)
}
pub fn record_crash(&self, crash: &GuestCrash, dir: &CStr) -> bool {
let Some(f) = self.table().and_then(|v| v.record_crash) else {
return false;
};
unsafe { f(*self, core::ptr::from_ref(crash), dir.as_ptr()) == 1 }
}
}