| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use { |
| fuchsia_async as fasync, |
| fuchsia_zircon::sys as zx_sys, |
| fuchsia_zircon::{self as zx, HandleBased}, |
| std::{mem, ptr, task::Poll}, |
| }; |
| |
| #[derive(Debug, PartialEq, Clone)] |
| pub enum ExceptionType { |
| General, |
| FatalPageFault, |
| UndefinedInstruction, |
| SwBreakpoint, |
| HwBreakpoint, |
| UnalignedAccess, |
| ThreadStarting, |
| ThreadExiting, |
| PolicyError, |
| ProcessStarting, |
| } |
| |
| impl TryFrom<u32> for ExceptionType { |
| type Error = zx::Status; |
| |
| /// Maps to the types defined in `zx_excp_type_t`. |
| /// If zircon/syscalls/exception.h changes, this needs to be updates as well to |
| /// reflect that. |
| fn try_from(value: u32) -> Result<Self, Self::Error> { |
| match value { |
| 0x8 => Ok(ExceptionType::General), |
| 0x108 => Ok(ExceptionType::FatalPageFault), |
| 0x208 => Ok(ExceptionType::UndefinedInstruction), |
| 0x308 => Ok(ExceptionType::SwBreakpoint), |
| 0x408 => Ok(ExceptionType::HwBreakpoint), |
| 0x508 => Ok(ExceptionType::UnalignedAccess), |
| 0x8008 => Ok(ExceptionType::ThreadStarting), |
| 0x8108 => Ok(ExceptionType::ThreadExiting), |
| 0x8208 => Ok(ExceptionType::PolicyError), |
| 0x8308 => Ok(ExceptionType::ProcessStarting), |
| _ => Err(zx::Status::INVALID_ARGS), |
| } |
| } |
| } |
| |
| pub struct ExceptionInfo { |
| pub process: zx::Process, |
| pub thread: zx::Thread, |
| pub type_: ExceptionType, |
| |
| pub exception_handle: zx::Exception, |
| } |
| |
| #[repr(C)] |
| struct ZxExceptionInfo { |
| pid: zx_sys::zx_koid_t, |
| tid: zx_sys::zx_koid_t, |
| type_: u32, |
| padding1: [u8; 4], |
| } |
| |
| pub struct ExceptionsStream { |
| inner: fasync::Channel, |
| is_terminated: bool, |
| } |
| |
| impl ExceptionsStream { |
| pub fn register_with_task<T>(task: &T) -> Result<Self, zx::Status> |
| where |
| T: zx::Task, |
| { |
| Self::from_channel(task.create_exception_channel()?) |
| } |
| |
| pub fn from_channel(chan: zx::Channel) -> Result<Self, zx::Status> { |
| Ok(Self { inner: fasync::Channel::from_channel(chan), is_terminated: false }) |
| } |
| } |
| |
| impl futures::Stream for ExceptionsStream { |
| type Item = Result<ExceptionInfo, zx::Status>; |
| |
| fn poll_next( |
| mut self: ::std::pin::Pin<&mut Self>, |
| cx: &mut core::task::Context<'_>, |
| ) -> Poll<Option<Self::Item>> { |
| let this = &mut *self; |
| |
| if this.is_terminated { |
| return Poll::Ready(None); |
| } |
| |
| let mut msg_buf = zx::MessageBuf::new(); |
| msg_buf.ensure_capacity_bytes(mem::size_of::<ZxExceptionInfo>()); |
| msg_buf.ensure_capacity_handles(1); |
| |
| match this.inner.recv_from(cx, &mut msg_buf) { |
| Poll::Pending => { |
| return Poll::Pending; |
| } |
| Poll::Ready(Err(zx::Status::PEER_CLOSED)) => { |
| this.is_terminated = true; |
| return Poll::Ready(None); |
| } |
| Poll::Ready(Err(status)) => { |
| this.is_terminated = true; |
| return Poll::Ready(Some(Err(status))); |
| } |
| Poll::Ready(Ok(())) => { |
| if msg_buf.n_handles() != 1 { |
| return Poll::Ready(Some(Err(zx::Status::BAD_HANDLE))); |
| } |
| let exception_handle = zx::Exception::from_handle(msg_buf.take_handle(0).unwrap()); |
| let zx_exception_info: ZxExceptionInfo = |
| unsafe { ptr::read(msg_buf.bytes().as_ptr() as *const _) }; |
| return Poll::Ready(Some(Ok(ExceptionInfo { |
| process: exception_handle.get_process()?, |
| thread: exception_handle.get_thread()?, |
| type_: ExceptionType::try_from(zx_exception_info.type_)?, |
| exception_handle, |
| }))); |
| } |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use { |
| super::*, |
| anyhow::{format_err, Context, Error}, |
| fidl_fuchsia_io as fio, fidl_fuchsia_process as fprocess, |
| fuchsia_component::client as fclient, |
| fuchsia_runtime as fruntime, |
| futures::TryStreamExt, |
| std::sync::Arc, |
| }; |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn catch_exception() -> Result<(), Error> { |
| // Create a new job |
| let child_job = |
| fruntime::job_default().create_child_job().context("failed to create child job")?; |
| |
| // Register ourselves as its exception handler |
| let mut exceptions_stream = ExceptionsStream::register_with_task(&child_job) |
| .context("failed to register with task ")?; |
| |
| // Connect to the process launcher |
| let launcher_proxy = fclient::connect_to_protocol::<fprocess::LauncherMarker>()?; |
| |
| // Set up a new library loader and provide it to the loader service |
| let (ll_client_chan, ll_service_chan) = zx::Channel::create(); |
| library_loader::start( |
| Arc::new(fuchsia_fs::directory::open_in_namespace( |
| "/pkg/lib", |
| fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_EXECUTABLE, |
| )?), |
| ll_service_chan, |
| ); |
| let handle_infos = vec![fprocess::HandleInfo { |
| handle: ll_client_chan.into_handle(), |
| id: fruntime::HandleInfo::new(fruntime::HandleType::LdsvcLoader, 0).as_raw(), |
| }]; |
| launcher_proxy.add_handles(handle_infos).context("failed to add loader service handle")?; |
| |
| // Load the executable into a vmo |
| let executable_file_proxy = fuchsia_fs::file::open_in_namespace( |
| "/pkg/bin/panic_on_start", |
| fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_EXECUTABLE, |
| )?; |
| let vmo = executable_file_proxy |
| .get_backing_memory(fio::VmoFlags::READ | fio::VmoFlags::EXECUTE) |
| .await? |
| .map_err(zx::Status::from_raw) |
| .context("failed to get VMO of executable")?; |
| |
| // Launch the process |
| let child_job_dup = child_job.duplicate_handle(zx::Rights::SAME_RIGHTS)?; |
| let launch_info = fprocess::LaunchInfo { |
| executable: vmo, |
| job: child_job_dup, |
| name: "panic_on_start".to_string(), |
| }; |
| let (status, _process) = |
| launcher_proxy.launch(launch_info).await.context("failed to launch process")?; |
| zx::Status::ok(status).context("error returned by process launcher")?; |
| |
| // The process panics when it starts, so wait for a message on the exceptions stream |
| match exceptions_stream.try_next().await { |
| Ok(Some(_)) => (), |
| Ok(None) => return Err(format_err!("the exceptions stream ended unexpectedly")), |
| Err(e) => return Err(format_err!("exceptions stream returned an error: {:?}", e)), |
| } |
| Ok(()) |
| } |
| } |