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 std::sync::Arc;
135    use {fidl_fuchsia_io as fio, fidl_fuchsia_process as fprocess, fuchsia_runtime as fruntime};
136
137    #[fasync::run_singlethreaded(test)]
138    async fn catch_exception() -> Result<(), Error> {
139        // Create a new job
140        let child_job =
141            fruntime::job_default().create_child_job().context("failed to create child job")?;
142
143        // Register ourselves as its exception handler
144        let mut exceptions_stream = ExceptionsStream::register_with_task(&child_job)
145            .context("failed to register with task ")?;
146
147        // Connect to the process launcher
148        let launcher_proxy = fclient::connect_to_protocol::<fprocess::LauncherMarker>()?;
149
150        // Set up a new library loader and provide it to the loader service
151        let (ll_client_chan, ll_service_chan) = zx::Channel::create();
152        library_loader::start(
153            Arc::new(fuchsia_fs::directory::open_in_namespace(
154                "/pkg/lib",
155                fio::PERM_READABLE | fio::PERM_EXECUTABLE,
156            )?),
157            ll_service_chan,
158        );
159        let handle_infos = vec![fprocess::HandleInfo {
160            handle: ll_client_chan.into_handle(),
161            id: fruntime::HandleInfo::new(fruntime::HandleType::LdsvcLoader, 0).as_raw(),
162        }];
163        launcher_proxy.add_handles(handle_infos).context("failed to add loader service handle")?;
164
165        // Load the executable into a vmo
166        let executable_file_proxy = fuchsia_fs::file::open_in_namespace(
167            "/pkg/bin/panic_on_start",
168            fio::PERM_READABLE | fio::PERM_EXECUTABLE,
169        )?;
170        let vmo = executable_file_proxy
171            .get_backing_memory(fio::VmoFlags::READ | fio::VmoFlags::EXECUTE)
172            .await?
173            .map_err(zx::Status::from_raw)
174            .context("failed to get VMO of executable")?;
175
176        // Launch the process
177        let child_job_dup = child_job.duplicate_handle(zx::Rights::SAME_RIGHTS)?;
178        let launch_info = fprocess::LaunchInfo {
179            executable: vmo,
180            job: child_job_dup,
181            name: "panic_on_start".to_string(),
182        };
183        let (status, _process) =
184            launcher_proxy.launch(launch_info).await.context("failed to launch process")?;
185        zx::Status::ok(status).context("error returned by process launcher")?;
186
187        // The process panics when it starts, so wait for a message on the exceptions stream
188        match exceptions_stream.try_next().await {
189            Ok(Some(_)) => (),
190            Ok(None) => return Err(format_err!("the exceptions stream ended unexpectedly")),
191            Err(e) => return Err(format_err!("exceptions stream returned an error: {:?}", e)),
192        }
193        Ok(())
194    }
195}