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