elf_runner/
crash_handler.rs

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.
4
5use crate::crash_info::{ComponentCrashInfo, CrashRecords};
6use crate::error::ExceptionError;
7use futures::TryStreamExt;
8use log::error;
9use moniker::Moniker;
10use vfs::ExecutionScope;
11use zx::{self as zx, AsHandleRef};
12
13// 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    scope: &ExecutionScope,
18    component_job: &zx::Job,
19    moniker: Moniker,
20    resolved_url: String,
21    crash_records: CrashRecords,
22) -> Result<(), zx::Status> {
23    let mut task_exceptions_stream =
24        task_exceptions::ExceptionsStream::register_with_task(component_job)?;
25    scope.spawn(async move {
26        loop {
27            match task_exceptions_stream.try_next().await {
28                Ok(Some(exception_info)) => {
29                    if let Err(error) = record_exception(
30                        resolved_url.clone(),
31                        moniker.clone(),
32                        exception_info,
33                        &crash_records,
34                    )
35                    .await
36                    {
37                        error!(url:% = resolved_url, error:?; "failed to handle exception");
38                    }
39                }
40                Ok(None) => break,
41                Err(error) => {
42                    error!(
43                        url:% = resolved_url, error:?;
44                        "failed to read message stream for fuchsia.sys2.CrashIntrospect",
45                    );
46                    break;
47                }
48            }
49        }
50    });
51    Ok(())
52}
53
54async 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.
62    let thread_koid = exception_info.thread.get_koid().map_err(ExceptionError::GetThreadKoid)?;
63    crash_records.add_report(thread_koid, ComponentCrashInfo { url: resolved_url, moniker }).await;
64
65    // We've stored all the information we need, so mark the exception handle such that the next
66    // exception handler should be attempted.
67    exception_info
68        .exception_handle
69        .set_exception_state(&zx::sys::ZX_EXCEPTION_STATE_TRY_NEXT)
70        .map_err(ExceptionError::SetState)?;
71
72    // Returning drops exception_info.exception_handle, which allows zircon to proceed with
73    // exception handling.
74    Ok(())
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80    use anyhow::{Context as _, Error};
81    use fuchsia_component::client as fclient;
82    use vfs::ExecutionScope;
83    use zx::HandleBased;
84    use {
85        fidl_fuchsia_io as fio, fidl_fuchsia_process as fprocess, fuchsia_async as fasync,
86        fuchsia_runtime as fruntime,
87    };
88
89    #[fuchsia::test]
90    async fn crash_test() -> Result<(), Error> {
91        let crash_records = CrashRecords::new();
92        let url = "example://component#url".to_string();
93        let moniker = Moniker::try_from(["a"]).unwrap();
94
95        let child_job =
96            fruntime::job_default().create_child_job().context("failed to create child job")?;
97
98        let scope = ExecutionScope::new();
99        run_exceptions_server(
100            &scope,
101            &child_job,
102            moniker.clone(),
103            url.clone(),
104            crash_records.clone(),
105        )?;
106
107        // Connect to the process launcher
108        let launcher_proxy = fclient::connect_to_protocol::<fprocess::LauncherMarker>()?;
109
110        // Set up a new library loader and provide it to the loader service
111        let (ll_client_chan, ll_service_chan) = zx::Channel::create();
112        library_loader::start(
113            fuchsia_fs::directory::open_in_namespace(
114                "/pkg/lib",
115                fio::PERM_READABLE | fio::PERM_EXECUTABLE,
116            )?,
117            ll_service_chan,
118        );
119        let handle_infos = vec![fprocess::HandleInfo {
120            handle: ll_client_chan.into_handle(),
121            id: fruntime::HandleInfo::new(fruntime::HandleType::LdsvcLoader, 0).as_raw(),
122        }];
123        launcher_proxy.add_handles(handle_infos).context("failed to add loader service handle")?;
124
125        // Load the executable into a vmo
126        let executable_file_proxy = fuchsia_fs::file::open_in_namespace(
127            "/pkg/bin/panic_on_start",
128            fio::PERM_READABLE | fio::PERM_EXECUTABLE,
129        )?;
130        let vmo = executable_file_proxy
131            .get_backing_memory(fio::VmoFlags::READ | fio::VmoFlags::EXECUTE)
132            .await?
133            .map_err(zx::Status::from_raw)
134            .context("failed to get VMO of executable")?;
135
136        // Create the process, but don't start it yet
137        let child_job_dup = child_job.duplicate_handle(zx::Rights::SAME_RIGHTS)?;
138        let launch_info = fprocess::LaunchInfo {
139            executable: vmo,
140            job: child_job_dup,
141            name: "panic_on_start".to_string(),
142        };
143        let (status, process_start_data) = launcher_proxy
144            .create_without_starting(launch_info)
145            .await
146            .context("failed to launch process")?;
147        zx::Status::ok(status).context("error returned by process launcher")?;
148        let process_start_data = process_start_data.unwrap();
149
150        // Get the thread's koid, so that we know which thread to look for in the records once it
151        // crashes
152        let thread_koid = process_start_data.thread.get_koid()?;
153
154        // We've got the thread koid, so now we can actually start the process
155        process_start_data.process.start(
156            &process_start_data.thread,
157            // Some of these values are u64, but this function is expecting usize
158            process_start_data.entry.try_into().unwrap(),
159            process_start_data.stack.try_into().unwrap(),
160            process_start_data.bootstrap.into_handle(),
161            process_start_data.vdso_base.try_into().unwrap(),
162        )?;
163
164        // The process panics when it starts, so wait for the job to be killed
165        fasync::OnSignals::new(&process_start_data.process, zx::Signals::PROCESS_TERMINATED)
166            .await?;
167        let crash_info = crash_records
168            .take_report(&thread_koid)
169            .await
170            .expect("crash_records is missing crash information");
171        assert_eq!(ComponentCrashInfo { url, moniker }, crash_info);
172        Ok(())
173    }
174}