task_exceptions/
lib.rs
1use 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 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 let child_job =
141 fruntime::job_default().create_child_job().context("failed to create child job")?;
142
143 let mut exceptions_stream = ExceptionsStream::register_with_task(&child_job)
145 .context("failed to register with task ")?;
146
147 let launcher_proxy = fclient::connect_to_protocol::<fprocess::LauncherMarker>()?;
149
150 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 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 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 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}