use {
super::*, crate::ipc::*, crate::object::*, alloc::sync::Arc, alloc::vec, alloc::vec::Vec,
core::mem::size_of, futures::channel::oneshot, kernel_hal::UserContext, spin::Mutex,
};
pub struct Exceptionate {
type_: ExceptionChannelType,
inner: Mutex<ExceptionateInner>,
}
enum ExceptionateInner {
Init,
Bind {
channel: Arc<Channel>,
rights: Rights,
},
Shutdown,
}
impl Exceptionate {
pub(super) fn new(type_: ExceptionChannelType) -> Arc<Self> {
Arc::new(Exceptionate {
type_,
inner: Mutex::new(ExceptionateInner::Init),
})
}
pub(super) fn shutdown(&self) {
*self.inner.lock() = ExceptionateInner::Shutdown;
}
pub fn create_channel(&self, rights: Rights) -> ZxResult<Arc<Channel>> {
let mut inner = self.inner.lock();
match &*inner {
ExceptionateInner::Shutdown => return Err(ZxError::BAD_STATE),
ExceptionateInner::Bind { channel, .. } if channel.peer().is_ok() => {
return Err(ZxError::ALREADY_BOUND);
}
_ => {}
}
let (channel, user_channel) = Channel::create();
*inner = ExceptionateInner::Bind { channel, rights };
Ok(user_channel)
}
pub(super) fn has_channel(&self) -> bool {
let inner = self.inner.lock();
matches!(&*inner, ExceptionateInner::Bind { channel, .. } if channel.peer().is_ok())
}
pub(super) fn send_exception(
&self,
exception: &Arc<Exception>,
) -> ZxResult<oneshot::Receiver<()>> {
debug!(
"Exception: {:?} ,try send to {:?}",
exception.type_, self.type_
);
let mut inner = self.inner.lock();
let (channel, rights) = match &*inner {
ExceptionateInner::Bind { channel, rights } => (channel, *rights),
_ => return Err(ZxError::NEXT),
};
let info = ExceptionInfo {
pid: exception.thread.proc().id(),
tid: exception.thread.id(),
type_: exception.type_,
padding: Default::default(),
};
let (object, closed) = ExceptionObject::create(exception.clone(), rights);
let msg = MessagePacket {
data: info.pack(),
handles: vec![Handle::new(object, Rights::DEFAULT_EXCEPTION)],
};
channel.write(msg).map_err(|err| {
if err == ZxError::PEER_CLOSED {
*inner = ExceptionateInner::Init;
return ZxError::NEXT;
}
err
})?;
Ok(closed)
}
}
#[repr(C)]
#[derive(Debug)]
struct ExceptionInfo {
pid: KoID,
tid: KoID,
type_: ExceptionType,
padding: u32,
}
impl ExceptionInfo {
#[allow(unsafe_code)]
fn pack(&self) -> Vec<u8> {
let buf: [u8; size_of::<ExceptionInfo>()] = unsafe { core::mem::transmute_copy(self) };
Vec::from(buf)
}
}
#[repr(C)]
#[derive(Debug, Clone)]
struct ExceptionHeader {
size: u32,
type_: ExceptionType,
}
#[cfg(target_arch = "x86_64")]
#[repr(C)]
#[derive(Debug, Default, Clone)]
struct ExceptionContext {
vector: u64,
err_code: u64,
cr2: u64,
}
#[cfg(target_arch = "aarch64")]
#[repr(C)]
#[derive(Debug, Default, Clone)]
struct ExceptionContext {
esr: u32,
padding1: u32,
far: u64,
padding2: u64,
}
impl ExceptionContext {
#[cfg(target_arch = "x86_64")]
fn from_user_context(cx: &UserContext) -> Self {
ExceptionContext {
vector: cx.trap_num as u64,
err_code: cx.error_code as u64,
cr2: kernel_hal::fetch_fault_vaddr() as u64,
}
}
#[cfg(target_arch = "aarch64")]
fn from_user_context(_cx: &UserContext) -> Self {
unimplemented!()
}
}
#[repr(C)]
#[derive(Debug, Clone)]
pub struct ExceptionReport {
header: ExceptionHeader,
context: ExceptionContext,
}
impl ExceptionReport {
fn new(type_: ExceptionType, cx: Option<&UserContext>) -> Self {
ExceptionReport {
header: ExceptionHeader {
type_,
size: core::mem::size_of::<ExceptionReport>() as u32,
},
context: cx
.map(ExceptionContext::from_user_context)
.unwrap_or_default(),
}
}
}
#[allow(missing_docs)]
#[repr(u32)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ExceptionType {
General = 0x008,
FatalPageFault = 0x108,
UndefinedInstruction = 0x208,
SoftwareBreakpoint = 0x308,
HardwareBreakpoint = 0x408,
UnalignedAccess = 0x508,
Synth = 0x8000,
ThreadStarting = 0x8008,
ThreadExiting = 0x8108,
PolicyError = 0x8208,
ProcessStarting = 0x8308,
}
impl ExceptionType {
pub fn is_synth(self) -> bool {
(self as u32) & (ExceptionType::Synth as u32) != 0
}
}
#[repr(u32)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(super) enum ExceptionChannelType {
None = 0,
Debugger = 1,
Thread = 2,
Process = 3,
Job = 4,
JobDebugger = 5,
}
pub struct ExceptionObject {
base: KObjectBase,
exception: Arc<Exception>,
rights: Rights,
close_signal: Option<oneshot::Sender<()>>,
}
impl_kobject!(ExceptionObject);
impl ExceptionObject {
fn create(exception: Arc<Exception>, rights: Rights) -> (Arc<Self>, oneshot::Receiver<()>) {
let (sender, receiver) = oneshot::channel();
let object = Arc::new(ExceptionObject {
base: KObjectBase::new(),
exception,
rights,
close_signal: Some(sender),
});
(object, receiver)
}
pub fn get_thread_handle(&self) -> Handle {
Handle {
object: self.exception.thread.clone(),
rights: self.rights & Rights::DEFAULT_THREAD,
}
}
pub fn get_process_handle(&self) -> ZxResult<Handle> {
if self.exception.current_channel_type() == ExceptionChannelType::Thread {
return Err(ZxError::ACCESS_DENIED);
}
Ok(Handle {
object: self.exception.thread.proc().clone(),
rights: self.rights & Rights::DEFAULT_PROCESS,
})
}
pub fn state(&self) -> u32 {
self.exception.inner.lock().handled as u32
}
pub fn set_state(&self, state: u32) -> ZxResult {
if state > 1 {
return Err(ZxError::INVALID_ARGS);
}
self.exception.inner.lock().handled = state == 1;
Ok(())
}
pub fn strategy(&self) -> u32 {
self.exception.inner.lock().second_chance as u32
}
pub fn set_strategy(&self, strategy: u32) -> ZxResult {
if strategy > 1 {
return Err(ZxError::INVALID_ARGS);
}
let mut inner = self.exception.inner.lock();
match inner.current_channel_type {
ExceptionChannelType::Debugger | ExceptionChannelType::JobDebugger => {
inner.second_chance = strategy == 1;
Ok(())
}
_ => Err(ZxError::BAD_STATE),
}
}
}
impl Drop for ExceptionObject {
fn drop(&mut self) {
self.close_signal.take().unwrap().send(()).ok();
}
}
pub(super) struct Exception {
thread: Arc<Thread>,
type_: ExceptionType,
report: ExceptionReport,
inner: Mutex<ExceptionInner>,
}
struct ExceptionInner {
current_channel_type: ExceptionChannelType,
handled: bool,
second_chance: bool,
}
impl Exception {
pub fn new(thread: &Arc<Thread>, type_: ExceptionType, cx: Option<&UserContext>) -> Arc<Self> {
Arc::new(Exception {
thread: thread.clone(),
type_,
report: ExceptionReport::new(type_, cx),
inner: Mutex::new(ExceptionInner {
current_channel_type: ExceptionChannelType::None,
handled: false,
second_chance: false,
}),
})
}
pub async fn handle(self: &Arc<Self>) {
let result = match self.type_ {
ExceptionType::ProcessStarting => {
self.handle_with(JobDebuggerIterator::new(self.thread.proc().job()), true)
.await
}
ExceptionType::ThreadStarting | ExceptionType::ThreadExiting => {
self.handle_with(Some(self.thread.proc().debug_exceptionate()), false)
.await
}
_ => {
self.handle_with(ExceptionateIterator::new(self), false)
.await
}
};
if result == Err(ZxError::NEXT) && !self.type_.is_synth() {
self.thread.proc().exit(TASK_RETCODE_SYSCALL_KILL);
}
}
async fn handle_with(
self: &Arc<Self>,
exceptionates: impl IntoIterator<Item = Arc<Exceptionate>>,
first_only: bool,
) -> ZxResult {
for exceptionate in exceptionates.into_iter() {
let closed = match exceptionate.send_exception(self) {
Err(ZxError::NEXT) => continue,
res => res?,
};
self.inner.lock().current_channel_type = exceptionate.type_;
closed.await.ok();
let handled = {
let mut inner = self.inner.lock();
inner.current_channel_type = ExceptionChannelType::None;
inner.handled
};
if handled | first_only {
return Ok(());
}
}
Err(ZxError::NEXT)
}
pub fn current_channel_type(&self) -> ExceptionChannelType {
self.inner.lock().current_channel_type
}
pub fn report(&self) -> ExceptionReport {
self.report.clone()
}
}
struct ExceptionateIterator<'a> {
exception: &'a Exception,
state: ExceptionateIteratorState,
}
enum ExceptionateIteratorState {
Debug(bool),
Thread,
Process,
Job(Arc<Job>),
Finished,
}
impl<'a> ExceptionateIterator<'a> {
fn new(exception: &'a Exception) -> Self {
ExceptionateIterator {
exception,
state: ExceptionateIteratorState::Debug(false),
}
}
}
impl<'a> Iterator for ExceptionateIterator<'a> {
type Item = Arc<Exceptionate>;
fn next(&mut self) -> Option<Self::Item> {
loop {
match &self.state {
ExceptionateIteratorState::Debug(second_chance) => {
if *second_chance && !self.exception.inner.lock().second_chance {
self.state =
ExceptionateIteratorState::Job(self.exception.thread.proc().job());
continue;
}
let proc = self.exception.thread.proc();
self.state = if *second_chance {
ExceptionateIteratorState::Job(self.exception.thread.proc().job())
} else {
ExceptionateIteratorState::Thread
};
return Some(proc.debug_exceptionate());
}
ExceptionateIteratorState::Thread => {
self.state = ExceptionateIteratorState::Process;
return Some(self.exception.thread.exceptionate());
}
ExceptionateIteratorState::Process => {
let proc = self.exception.thread.proc();
self.state = ExceptionateIteratorState::Debug(true);
return Some(proc.exceptionate());
}
ExceptionateIteratorState::Job(job) => {
let parent = job.parent();
let result = job.exceptionate();
self.state = parent.map_or(
ExceptionateIteratorState::Finished,
ExceptionateIteratorState::Job,
);
return Some(result);
}
ExceptionateIteratorState::Finished => return None,
}
}
}
}
struct JobDebuggerIterator {
job: Option<Arc<Job>>,
}
impl JobDebuggerIterator {
fn new(job: Arc<Job>) -> Self {
JobDebuggerIterator { job: Some(job) }
}
}
impl Iterator for JobDebuggerIterator {
type Item = Arc<Exceptionate>;
fn next(&mut self) -> Option<Self::Item> {
let result = self.job.as_ref().map(|job| job.debug_exceptionate());
self.job = self.job.as_ref().and_then(|job| job.parent());
result
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn exceptionate_iterator() {
let parent_job = Job::root();
let job = parent_job.create_child().unwrap();
let proc = Process::create(&job, "proc").unwrap();
let thread = Thread::create(&proc, "thread").unwrap();
let exception = Exception::new(&thread, ExceptionType::Synth, None);
let actual: Vec<_> = ExceptionateIterator::new(&exception).collect();
let expected = [
proc.debug_exceptionate(),
thread.exceptionate(),
proc.exceptionate(),
job.exceptionate(),
parent_job.exceptionate(),
];
assert_eq!(actual.len(), expected.len());
for (actual, expected) in actual.iter().zip(expected.iter()) {
assert!(Arc::ptr_eq(&actual, expected));
}
}
#[test]
fn exceptionate_iterator_second_chance() {
let parent_job = Job::root();
let job = parent_job.create_child().unwrap();
let proc = Process::create(&job, "proc").unwrap();
let thread = Thread::create(&proc, "thread").unwrap();
let exception = Exception::new(&thread, ExceptionType::Synth, None);
exception.inner.lock().second_chance = true;
let actual: Vec<_> = ExceptionateIterator::new(&exception).collect();
let expected = [
proc.debug_exceptionate(),
thread.exceptionate(),
proc.exceptionate(),
proc.debug_exceptionate(),
job.exceptionate(),
parent_job.exceptionate(),
];
assert_eq!(actual.len(), expected.len());
for (actual, expected) in actual.iter().zip(expected.iter()) {
assert!(Arc::ptr_eq(&actual, expected));
}
}
#[test]
fn job_debugger_iterator() {
let parent_job = Job::root();
let job = parent_job.create_child().unwrap();
let child_job = job.create_child().unwrap();
let _grandson_job = child_job.create_child().unwrap();
let actual: Vec<_> = JobDebuggerIterator::new(child_job.clone()).collect();
let expected = [
child_job.debug_exceptionate(),
job.debug_exceptionate(),
parent_job.debug_exceptionate(),
];
assert_eq!(actual.len(), expected.len());
for (actual, expected) in actual.iter().zip(expected.iter()) {
assert!(Arc::ptr_eq(&actual, expected));
}
}
#[async_std::test]
async fn exception_handling() {
let parent_job = Job::root();
let job = parent_job.create_child().unwrap();
let proc = Process::create(&job, "proc").unwrap();
let thread = Thread::create(&proc, "thread").unwrap();
let exception = Exception::new(&thread, ExceptionType::Synth, None);
let handled_order = Arc::new(Mutex::new(Vec::<usize>::new()));
let create_handler = |exceptionate: &Arc<Exceptionate>,
should_receive: bool,
should_handle: bool,
order: usize| {
let channel = exceptionate
.create_channel(Rights::DEFAULT_THREAD | Rights::DEFAULT_PROCESS)
.unwrap();
let handled_order = handled_order.clone();
async_std::task::spawn(async move {
let channel_object: Arc<dyn KernelObject> = channel.clone();
channel_object
.wait_signal(Signal::READABLE | Signal::PEER_CLOSED)
.await;
if !should_receive {
assert_eq!(channel.read().err(), Some(ZxError::PEER_CLOSED));
return;
}
let data = channel.read().unwrap();
assert_eq!(data.handles.len(), 1);
let exception = data.handles[0]
.object
.clone()
.downcast_arc::<ExceptionObject>()
.unwrap();
if should_handle {
exception.set_state(1).unwrap();
}
handled_order.lock().push(order);
})
};
create_handler(&proc.debug_exceptionate(), true, false, 0);
create_handler(&thread.exceptionate(), true, false, 1);
create_handler(&job.exceptionate(), true, true, 3);
create_handler(&parent_job.exceptionate(), false, false, 4);
exception.handle().await;
thread.exceptionate().shutdown();
proc.debug_exceptionate().shutdown();
job.exceptionate().shutdown();
parent_job.exceptionate().shutdown();
assert_eq!(handled_order.lock().clone(), vec![0, 1, 3]);
}
}