Skip to main content

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