task_exceptions/
lib.rs

1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use fuchsia_async as fasync;
6use std::task::Poll;
7use std::{mem, ptr};
8use zx::{self as zx, sys as zx_sys, HandleBased};
9
10#[derive(Debug, PartialEq, Clone)]
11pub enum ExceptionType {
12    General,
13    FatalPageFault,
14    UndefinedInstruction,
15    SwBreakpoint,
16    HwBreakpoint,
17    UnalignedAccess,
18    ThreadStarting,
19    ThreadExiting,
20    PolicyError,
21    ProcessStarting,
22}
23
24impl TryFrom<u32> for ExceptionType {
25    type Error = zx::Status;
26
27    /// Maps to the types defined in `zx_excp_type_t`.
28    /// If zircon/syscalls/exception.h changes, this needs to be updates as well to
29    /// reflect that.
30    fn try_from(value: u32) -> Result<Self, Self::Error> {
31        match value {
32            0x8 => Ok(ExceptionType::General),
33            0x108 => Ok(ExceptionType::FatalPageFault),
34            0x208 => Ok(ExceptionType::UndefinedInstruction),
35            0x308 => Ok(ExceptionType::SwBreakpoint),
36            0x408 => Ok(ExceptionType::HwBreakpoint),
37            0x508 => Ok(ExceptionType::UnalignedAccess),
38            0x8008 => Ok(ExceptionType::ThreadStarting),
39            0x8108 => Ok(ExceptionType::ThreadExiting),
40            0x8208 => Ok(ExceptionType::PolicyError),
41            0x8308 => Ok(ExceptionType::ProcessStarting),
42            _ => Err(zx::Status::INVALID_ARGS),
43        }
44    }
45}
46
47pub struct ExceptionInfo {
48    pub process: zx::Process,
49    pub thread: zx::Thread,
50    pub type_: ExceptionType,
51
52    pub exception_handle: zx::Exception,
53}
54
55#[repr(C)]
56struct ZxExceptionInfo {
57    pid: zx_sys::zx_koid_t,
58    tid: zx_sys::zx_koid_t,
59    type_: u32,
60    padding1: [u8; 4],
61}
62
63pub struct ExceptionsStream {
64    inner: fasync::Channel,
65    is_terminated: bool,
66}
67
68impl ExceptionsStream {
69    pub fn register_with_task<T>(task: &T) -> Result<Self, zx::Status>
70    where
71        T: zx::Task,
72    {
73        Self::from_channel(task.create_exception_channel()?)
74    }
75
76    pub fn from_channel(chan: zx::Channel) -> Result<Self, zx::Status> {
77        Ok(Self { inner: fasync::Channel::from_channel(chan), is_terminated: false })
78    }
79}
80
81impl futures::Stream for ExceptionsStream {
82    type Item = Result<ExceptionInfo, zx::Status>;
83
84    fn poll_next(
85        mut self: ::std::pin::Pin<&mut Self>,
86        cx: &mut core::task::Context<'_>,
87    ) -> Poll<Option<Self::Item>> {
88        let this = &mut *self;
89
90        if this.is_terminated {
91            return Poll::Ready(None);
92        }
93
94        let mut msg_buf = zx::MessageBuf::new();
95        msg_buf.ensure_capacity_bytes(mem::size_of::<ZxExceptionInfo>());
96        msg_buf.ensure_capacity_handles(1);
97
98        match this.inner.recv_from(cx, &mut msg_buf) {
99            Poll::Pending => {
100                return Poll::Pending;
101            }
102            Poll::Ready(Err(zx::Status::PEER_CLOSED)) => {
103                this.is_terminated = true;
104                return Poll::Ready(None);
105            }
106            Poll::Ready(Err(status)) => {
107                this.is_terminated = true;
108                return Poll::Ready(Some(Err(status)));
109            }
110            Poll::Ready(Ok(())) => {
111                if msg_buf.n_handles() != 1 {
112                    return Poll::Ready(Some(Err(zx::Status::BAD_HANDLE)));
113                }
114                let exception_handle = zx::Exception::from_handle(msg_buf.take_handle(0).unwrap());
115                let zx_exception_info: ZxExceptionInfo =
116                    unsafe { ptr::read(msg_buf.bytes().as_ptr() as *const _) };
117                return Poll::Ready(Some(Ok(ExceptionInfo {
118                    process: exception_handle.get_process()?,
119                    thread: exception_handle.get_thread()?,
120                    type_: ExceptionType::try_from(zx_exception_info.type_)?,
121                    exception_handle,
122                })));
123            }
124        }
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131    use anyhow::{format_err, Context, Error};
132    use fuchsia_component::client as fclient;
133    use futures::TryStreamExt;
134    use {fidl_fuchsia_io as fio, fidl_fuchsia_process as fprocess, fuchsia_runtime as fruntime};
135
136    #[fasync::run_singlethreaded(test)]
137    async fn catch_exception() -> Result<(), Error> {
138        // Create a new job
139        let child_job =
140            fruntime::job_default().create_child_job().context("failed to create child job")?;
141
142        // Register ourselves as its exception handler
143        let mut exceptions_stream = ExceptionsStream::register_with_task(&child_job)
144            .context("failed to register with task ")?;
145
146        // Connect to the process launcher
147        let launcher_proxy = fclient::connect_to_protocol::<fprocess::LauncherMarker>()?;
148
149        // Set up a new library loader and provide it to the loader service
150        let (ll_client_chan, ll_service_chan) = zx::Channel::create();
151        library_loader::start(
152            fuchsia_fs::directory::open_in_namespace(
153                "/pkg/lib",
154                fio::PERM_READABLE | fio::PERM_EXECUTABLE,
155            )?,
156            ll_service_chan,
157        );
158        let handle_infos = vec![fprocess::HandleInfo {
159            handle: ll_client_chan.into_handle(),
160            id: fruntime::HandleInfo::new(fruntime::HandleType::LdsvcLoader, 0).as_raw(),
161        }];
162        launcher_proxy.add_handles(handle_infos).context("failed to add loader service handle")?;
163
164        // Load the executable into a vmo
165        let executable_file_proxy = fuchsia_fs::file::open_in_namespace(
166            "/pkg/bin/panic_on_start",
167            fio::PERM_READABLE | fio::PERM_EXECUTABLE,
168        )?;
169        let vmo = executable_file_proxy
170            .get_backing_memory(fio::VmoFlags::READ | fio::VmoFlags::EXECUTE)
171            .await?
172            .map_err(zx::Status::from_raw)
173            .context("failed to get VMO of executable")?;
174
175        // Launch the process
176        let child_job_dup = child_job.duplicate_handle(zx::Rights::SAME_RIGHTS)?;
177        let launch_info = fprocess::LaunchInfo {
178            executable: vmo,
179            job: child_job_dup,
180            name: "panic_on_start".to_string(),
181        };
182        let (status, _process) =
183            launcher_proxy.launch(launch_info).await.context("failed to launch process")?;
184        zx::Status::ok(status).context("error returned by process launcher")?;
185
186        // The process panics when it starts, so wait for a message on the exceptions stream
187        match exceptions_stream.try_next().await {
188            Ok(Some(_)) => (),
189            Ok(None) => return Err(format_err!("the exceptions stream ended unexpectedly")),
190            Err(e) => return Err(format_err!("exceptions stream returned an error: {:?}", e)),
191        }
192        Ok(())
193    }
194}