Skip to main content

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};
8
9#[derive(Debug, PartialEq, Clone)]
10pub enum ExceptionType {
11    General,
12    FatalPageFault,
13    UndefinedInstruction,
14    SwBreakpoint,
15    HwBreakpoint,
16    UnalignedAccess,
17    ThreadStarting,
18    ThreadExiting,
19    PolicyError,
20    ProcessStarting,
21}
22
23impl TryFrom<u32> for ExceptionType {
24    type Error = zx::Status;
25
26    /// Maps to the types defined in `zx_excp_type_t`.
27    /// If zircon/syscalls/exception.h changes, this needs to be updates as well to
28    /// reflect that.
29    fn try_from(value: u32) -> Result<Self, Self::Error> {
30        match value {
31            0x8 => Ok(ExceptionType::General),
32            0x108 => Ok(ExceptionType::FatalPageFault),
33            0x208 => Ok(ExceptionType::UndefinedInstruction),
34            0x308 => Ok(ExceptionType::SwBreakpoint),
35            0x408 => Ok(ExceptionType::HwBreakpoint),
36            0x508 => Ok(ExceptionType::UnalignedAccess),
37            0x8008 => Ok(ExceptionType::ThreadStarting),
38            0x8108 => Ok(ExceptionType::ThreadExiting),
39            0x8208 => Ok(ExceptionType::PolicyError),
40            0x8308 => Ok(ExceptionType::ProcessStarting),
41            _ => Err(zx::Status::INVALID_ARGS),
42        }
43    }
44}
45
46pub struct ExceptionInfo {
47    pub process: zx::Process,
48    pub thread: zx::Thread,
49    pub type_: ExceptionType,
50
51    pub exception_handle: zx::Exception,
52}
53
54#[repr(C)]
55struct ZxExceptionInfo {
56    pid: zx::sys::zx_koid_t,
57    tid: zx::sys::zx_koid_t,
58    type_: u32,
59    padding1: [u8; 4],
60}
61
62pub struct ExceptionsStream {
63    inner: fasync::Channel,
64    is_terminated: bool,
65}
66
67impl ExceptionsStream {
68    pub fn register_with_task<T>(task: &T) -> Result<Self, zx::Status>
69    where
70        T: zx::Task,
71    {
72        Self::from_channel(task.create_exception_channel()?)
73    }
74
75    pub fn from_channel(chan: zx::Channel) -> Result<Self, zx::Status> {
76        Ok(Self { inner: fasync::Channel::from_channel(chan), is_terminated: false })
77    }
78}
79
80impl futures::Stream for ExceptionsStream {
81    type Item = Result<ExceptionInfo, zx::Status>;
82
83    fn poll_next(
84        mut self: ::std::pin::Pin<&mut Self>,
85        cx: &mut core::task::Context<'_>,
86    ) -> Poll<Option<Self::Item>> {
87        let this = &mut *self;
88
89        if this.is_terminated {
90            return Poll::Ready(None);
91        }
92
93        let mut msg_buf = zx::MessageBuf::new();
94        msg_buf.ensure_capacity_bytes(mem::size_of::<ZxExceptionInfo>());
95        msg_buf.ensure_capacity_handles(1);
96
97        match this.inner.recv_from(cx, &mut msg_buf) {
98            Poll::Pending => {
99                return Poll::Pending;
100            }
101            Poll::Ready(Err(zx::Status::PEER_CLOSED)) => {
102                this.is_terminated = true;
103                return Poll::Ready(None);
104            }
105            Poll::Ready(Err(status)) => {
106                this.is_terminated = true;
107                return Poll::Ready(Some(Err(status)));
108            }
109            Poll::Ready(Ok(())) => {
110                if msg_buf.n_handles() != 1 {
111                    return Poll::Ready(Some(Err(zx::Status::BAD_HANDLE)));
112                }
113                let exception_handle = zx::Exception::from(msg_buf.take_handle(0).unwrap());
114                let zx_exception_info: ZxExceptionInfo =
115                    unsafe { ptr::read(msg_buf.bytes().as_ptr() as *const _) };
116                return Poll::Ready(Some(Ok(ExceptionInfo {
117                    process: exception_handle.get_process()?,
118                    thread: exception_handle.get_thread()?,
119                    type_: ExceptionType::try_from(zx_exception_info.type_)?,
120                    exception_handle,
121                })));
122            }
123        }
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130    use anyhow::{Context, Error, format_err};
131    use fuchsia_component::client as fclient;
132    use futures::TryStreamExt;
133    use zx::HandleBased;
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(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}