1// Copyright 2021 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.
45use crate::crash_info::{ComponentCrashInfo, CrashRecords};
6use crate::error::ExceptionError;
7use fuchsia_async as fasync;
8use futures::TryStreamExt;
9use log::error;
10use moniker::Moniker;
11use zx::{self as zx, AsHandleRef};
1213// Registers with the job to catch exceptions raised by it. Whenever we see an exception from this
14// job, record that the crash happened, and inform zircon that it should proceed to the next crash
15// handler. No actual handling of the crash occurs here, we merely save some data on it.
16pub fn run_exceptions_server(
17 component_job: &zx::Job,
18 moniker: Moniker,
19 resolved_url: String,
20 crash_records: CrashRecords,
21) -> Result<(), zx::Status> {
22let mut task_exceptions_stream =
23 task_exceptions::ExceptionsStream::register_with_task(component_job)?;
24 fasync::Task::spawn(async move {
25loop {
26match task_exceptions_stream.try_next().await {
27Ok(Some(exception_info)) => {
28if let Err(error) = record_exception(
29 resolved_url.clone(),
30 moniker.clone(),
31 exception_info,
32&crash_records,
33 )
34 .await
35{
36error!(url:% = resolved_url, error:?; "failed to handle exception");
37 }
38 }
39Ok(None) => break,
40Err(error) => {
41error!(
42 url:% = resolved_url, error:?;
43"failed to read message stream for fuchsia.sys2.CrashIntrospect",
44 );
45break;
46 }
47 }
48 }
49 })
50 .detach();
51Ok(())
52}
5354async fn record_exception(
55 resolved_url: String,
56 moniker: Moniker,
57 exception_info: task_exceptions::ExceptionInfo,
58 crash_records: &CrashRecords,
59) -> Result<(), ExceptionError> {
60// An exception has occurred, record information about the crash so that it may be retrieved
61 // later.
62let thread_koid = exception_info.thread.get_koid().map_err(ExceptionError::GetThreadKoid)?;
63 crash_records.add_report(thread_koid, ComponentCrashInfo { url: resolved_url, moniker }).await;
6465// We've stored all the information we need, so mark the exception handle such that the next
66 // exception handler should be attempted.
67exception_info
68 .exception_handle
69 .set_exception_state(&zx::sys::ZX_EXCEPTION_STATE_TRY_NEXT)
70 .map_err(ExceptionError::SetState)?;
7172// Returning drops exception_info.exception_handle, which allows zircon to proceed with
73 // exception handling.
74Ok(())
75}
7677#[cfg(test)]
78mod tests {
79use super::*;
80use anyhow::{Context as _, Error};
81use fuchsia_component::client as fclient;
82use std::sync::Arc;
83use zx::HandleBased;
84use {fidl_fuchsia_io as fio, fidl_fuchsia_process as fprocess, fuchsia_runtime as fruntime};
8586#[fuchsia::test]
87async fn crash_test() -> Result<(), Error> {
88let crash_records = CrashRecords::new();
89let url = "example://component#url".to_string();
90let moniker = Moniker::try_from(vec!["a"]).unwrap();
9192let child_job =
93 fruntime::job_default().create_child_job().context("failed to create child job")?;
9495 run_exceptions_server(&child_job, moniker.clone(), url.clone(), crash_records.clone())?;
9697// Connect to the process launcher
98let launcher_proxy = fclient::connect_to_protocol::<fprocess::LauncherMarker>()?;
99100// Set up a new library loader and provide it to the loader service
101let (ll_client_chan, ll_service_chan) = zx::Channel::create();
102 library_loader::start(
103 Arc::new(fuchsia_fs::directory::open_in_namespace(
104"/pkg/lib",
105 fio::PERM_READABLE | fio::PERM_EXECUTABLE,
106 )?),
107 ll_service_chan,
108 );
109let handle_infos = vec![fprocess::HandleInfo {
110 handle: ll_client_chan.into_handle(),
111 id: fruntime::HandleInfo::new(fruntime::HandleType::LdsvcLoader, 0).as_raw(),
112 }];
113 launcher_proxy.add_handles(handle_infos).context("failed to add loader service handle")?;
114115// Load the executable into a vmo
116let executable_file_proxy = fuchsia_fs::file::open_in_namespace(
117"/pkg/bin/panic_on_start",
118 fio::PERM_READABLE | fio::PERM_EXECUTABLE,
119 )?;
120let vmo = executable_file_proxy
121 .get_backing_memory(fio::VmoFlags::READ | fio::VmoFlags::EXECUTE)
122 .await?
123.map_err(zx::Status::from_raw)
124 .context("failed to get VMO of executable")?;
125126// Create the process, but don't start it yet
127let child_job_dup = child_job.duplicate_handle(zx::Rights::SAME_RIGHTS)?;
128let launch_info = fprocess::LaunchInfo {
129 executable: vmo,
130 job: child_job_dup,
131 name: "panic_on_start".to_string(),
132 };
133let (status, process_start_data) = launcher_proxy
134 .create_without_starting(launch_info)
135 .await
136.context("failed to launch process")?;
137 zx::Status::ok(status).context("error returned by process launcher")?;
138let process_start_data = process_start_data.unwrap();
139140// Get the thread's koid, so that we know which thread to look for in the records once it
141 // crashes
142let thread_koid = process_start_data.thread.get_koid()?;
143144// We've got the thread koid, so now we can actually start the process
145process_start_data.process.start(
146&process_start_data.thread,
147// Some of these values are u64, but this function is expecting usize
148process_start_data.entry.try_into().unwrap(),
149 process_start_data.stack.try_into().unwrap(),
150 process_start_data.bootstrap.into_handle(),
151 process_start_data.vdso_base.try_into().unwrap(),
152 )?;
153154// The process panics when it starts, so wait for the job to be killed
155fasync::OnSignals::new(&process_start_data.process, zx::Signals::PROCESS_TERMINATED)
156 .await?;
157let crash_info = crash_records
158 .take_report(&thread_koid)
159 .await
160.expect("crash_records is missing crash information");
161assert_eq!(ComponentCrashInfo { url, moniker }, crash_info);
162Ok(())
163 }
164}