elf_runner/
crash_handler.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use crate::crash_info::{ComponentCrashInfo, CrashRecords};
use crate::error::ExceptionError;
use fuchsia_async as fasync;
use futures::TryStreamExt;
use moniker::Moniker;
use tracing::error;
use zx::{self as zx, AsHandleRef};

// Registers with the job to catch exceptions raised by it. Whenever we see an exception from this
// job, record that the crash happened, and inform zircon that it should proceed to the next crash
// handler. No actual handling of the crash occurs here, we merely save some data on it.
pub fn run_exceptions_server(
    component_job: &zx::Job,
    moniker: Moniker,
    resolved_url: String,
    crash_records: CrashRecords,
) -> Result<(), zx::Status> {
    let mut task_exceptions_stream =
        task_exceptions::ExceptionsStream::register_with_task(component_job)?;
    fasync::Task::spawn(async move {
        loop {
            match task_exceptions_stream.try_next().await {
                Ok(Some(exception_info)) => {
                    if let Err(error) = record_exception(
                        resolved_url.clone(),
                        moniker.clone(),
                        exception_info,
                        &crash_records,
                    )
                    .await
                    {
                        error!(url=%resolved_url, ?error, "failed to handle exception");
                    }
                }
                Ok(None) => break,
                Err(error) => {
                    error!(
                        url=%resolved_url, ?error,
                        "failed to read message stream for fuchsia.sys2.CrashIntrospect",
                    );
                    break;
                }
            }
        }
    })
    .detach();
    Ok(())
}

async fn record_exception(
    resolved_url: String,
    moniker: Moniker,
    exception_info: task_exceptions::ExceptionInfo,
    crash_records: &CrashRecords,
) -> Result<(), ExceptionError> {
    // An exception has occurred, record information about the crash so that it may be retrieved
    // later.
    let thread_koid = exception_info.thread.get_koid().map_err(ExceptionError::GetThreadKoid)?;
    crash_records.add_report(thread_koid, ComponentCrashInfo { url: resolved_url, moniker }).await;

    // We've stored all the information we need, so mark the exception handle such that the next
    // exception handler should be attempted.
    exception_info
        .exception_handle
        .set_exception_state(&zx::sys::ZX_EXCEPTION_STATE_TRY_NEXT)
        .map_err(ExceptionError::SetState)?;

    // Returning drops exception_info.exception_handle, which allows zircon to proceed with
    // exception handling.
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use anyhow::{Context as _, Error};
    use fuchsia_component::client as fclient;
    use std::sync::Arc;
    use zx::HandleBased;
    use {fidl_fuchsia_io as fio, fidl_fuchsia_process as fprocess, fuchsia_runtime as fruntime};

    #[fuchsia::test]
    async fn crash_test() -> Result<(), Error> {
        let crash_records = CrashRecords::new();
        let url = "example://component#url".to_string();
        let moniker = Moniker::try_from(vec!["a"]).unwrap();

        let child_job =
            fruntime::job_default().create_child_job().context("failed to create child job")?;

        run_exceptions_server(&child_job, moniker.clone(), url.clone(), crash_records.clone())?;

        // Connect to the process launcher
        let launcher_proxy = fclient::connect_to_protocol::<fprocess::LauncherMarker>()?;

        // Set up a new library loader and provide it to the loader service
        let (ll_client_chan, ll_service_chan) = zx::Channel::create();
        library_loader::start(
            Arc::new(fuchsia_fs::directory::open_in_namespace(
                "/pkg/lib",
                fio::PERM_READABLE | fio::PERM_EXECUTABLE,
            )?),
            ll_service_chan,
        );
        let handle_infos = vec![fprocess::HandleInfo {
            handle: ll_client_chan.into_handle(),
            id: fruntime::HandleInfo::new(fruntime::HandleType::LdsvcLoader, 0).as_raw(),
        }];
        launcher_proxy.add_handles(handle_infos).context("failed to add loader service handle")?;

        // Load the executable into a vmo
        let executable_file_proxy = fuchsia_fs::file::open_in_namespace(
            "/pkg/bin/panic_on_start",
            fio::PERM_READABLE | fio::PERM_EXECUTABLE,
        )?;
        let vmo = executable_file_proxy
            .get_backing_memory(fio::VmoFlags::READ | fio::VmoFlags::EXECUTE)
            .await?
            .map_err(zx::Status::from_raw)
            .context("failed to get VMO of executable")?;

        // Create the process, but don't start it yet
        let child_job_dup = child_job.duplicate_handle(zx::Rights::SAME_RIGHTS)?;
        let launch_info = fprocess::LaunchInfo {
            executable: vmo,
            job: child_job_dup,
            name: "panic_on_start".to_string(),
        };
        let (status, process_start_data) = launcher_proxy
            .create_without_starting(launch_info)
            .await
            .context("failed to launch process")?;
        zx::Status::ok(status).context("error returned by process launcher")?;
        let process_start_data = process_start_data.unwrap();

        // Get the thread's koid, so that we know which thread to look for in the records once it
        // crashes
        let thread_koid = process_start_data.thread.get_koid()?;

        // We've got the thread koid, so now we can actually start the process
        process_start_data.process.start(
            &process_start_data.thread,
            // Some of these values are u64, but this function is expecting usize
            process_start_data.entry.try_into().unwrap(),
            process_start_data.stack.try_into().unwrap(),
            process_start_data.bootstrap.into_handle(),
            process_start_data.vdso_base.try_into().unwrap(),
        )?;

        // The process panics when it starts, so wait for the job to be killed
        fasync::OnSignals::new(&process_start_data.process, zx::Signals::PROCESS_TERMINATED)
            .await?;
        let crash_info = crash_records
            .take_report(&thread_koid)
            .await
            .expect("crash_records is missing crash information");
        assert_eq!(ComponentCrashInfo { url, moniker }, crash_info);
        Ok(())
    }
}