1// Copyright 2018 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
5#include <fbl/algorithm.h>
6#include <fbl/type_support.h>
7#include <fbl/unique_ptr.h>
8#include <lib/zx/channel.h>
9#include <lib/zx/handle.h>
10#include <lib/zx/job.h>
11#include <lib/zx/port.h>
12#include <lib/zx/process.h>
13#include <lib/zx/thread.h>
14#include <lib/zx/time.h>
15#include <stdint.h>
16#include <stdio.h>
17#include <string.h>
18#include <zircon/process.h>
19#include <zircon/processargs.h>
20#include <zircon/syscalls/exception.h>
21
22static bool GetChildKoids(const zx::job& job, zx_object_info_topic_t child_kind,
23                          fbl::unique_ptr<zx_koid_t[]>* koids, size_t* num_koids) {
24    size_t actual = 0;
25    size_t available = 0;
26
27    size_t count = 100;
28    koids->reset(new zx_koid_t[count]);
29
30    for (;;) {
31        if (job.get_info(child_kind, koids->get(), count * sizeof(zx_koid_t), &actual,
32                         &available) != ZX_OK) {
33            fprintf(stderr, "crashsvc: failed to get child koids\n");
34            koids->reset();
35            *num_koids = 0;
36            return false;
37        }
38
39        if (actual == available) {
40            break;
41        }
42
43        // Resize to the expected number next time with a bit of slop to try to
44        // handle the race between here and the next request.
45        count = available + 10;
46        koids->reset(new zx_koid_t[count]);
47    }
48
49    // No need to actually downsize the output array since the size is separate.
50    *num_koids = actual;
51    return true;
52}
53
54static bool FindProcess(const zx::job& job, zx_koid_t process_koid, zx::process* out) {
55    // Search this job for the process.
56    fbl::unique_ptr<zx_koid_t[]> child_koids;
57    size_t num_koids;
58    if (GetChildKoids(job, ZX_INFO_JOB_PROCESSES, &child_koids, &num_koids)) {
59        for (size_t i = 0; i < num_koids; ++i) {
60            if (child_koids[i] == process_koid) {
61                zx::process process;
62                if (job.get_child(child_koids[i], ZX_RIGHT_SAME_RIGHTS, &process) != ZX_OK) {
63                    return false;
64                }
65                *out = fbl::move(process);
66                return true;
67            }
68        }
69    }
70
71    // Otherwise, search child jobs in the same way.
72    if (GetChildKoids(job, ZX_INFO_JOB_CHILDREN, &child_koids, &num_koids)) {
73        for (size_t i = 0; i < num_koids; ++i) {
74            zx::job child_job;
75            if (job.get_child(child_koids[i], ZX_RIGHT_SAME_RIGHTS, &child_job) != ZX_OK) {
76                continue;
77            }
78            if (FindProcess(child_job, process_koid, out)) {
79                return true;
80            }
81        }
82    }
83
84    return false;
85}
86
87static void HandOffException(const zx::job& root_job, const zx::channel& channel,
88                             const zx_port_packet_t& packet) {
89    zx::process exception_process;
90    if (!FindProcess(root_job, packet.exception.pid, &exception_process)) {
91        fprintf(stderr, "crashsvc: failed to find process for pid=%zu\n", packet.exception.pid);
92        return;
93    }
94
95    zx::thread exception_thread;
96    if (exception_process.get_child(packet.exception.tid, ZX_RIGHT_SAME_RIGHTS,
97                                    &exception_thread) != ZX_OK) {
98        fprintf(stderr, "crashsvc: failed to find thread for tid=%zu\n", packet.exception.tid);
99        return;
100    }
101
102    zx_handle_t handles[] = {exception_process.release(), exception_thread.release()};
103    zx_status_t status =
104        channel.write(0, &packet.type, sizeof(packet.type), handles, fbl::count_of(handles));
105    if (status != ZX_OK) {
106        // If the channel write failed, things are going badly, attempt to
107        // resume the excepted  thread which will typically result in the
108        // process being terminated by the kernel.
109        fprintf(stderr, "crashsvc: channel write failed: %d\n", status);
110        status = zx_task_resume(handles[1], ZX_RESUME_EXCEPTION | ZX_RESUME_TRY_NEXT);
111        if (status != ZX_OK) {
112            fprintf(stderr, "crashsvc: zx_task_resume failed: %d\n", status);
113        }
114    }
115}
116
117// crashsvc watches the exception port on the root job and dispatches to
118// an analyzer process that's responsible for handling the exception.
119int main(int argc, char** argv) {
120    fprintf(stderr, "crashsvc: starting\n");
121
122    // crashsvc receives 3 handles at startup:
123    // - the root job handle
124    // - the exception port handle, already bound
125    // - a channel on which to write messages when exceptions are encountered
126    zx::job root_job(zx_take_startup_handle(PA_HND(PA_USER0, 0)));
127    if (!root_job.is_valid()) {
128        fprintf(stderr, "crashsvc: no root job\n");
129        return 1;
130    }
131    zx::port exception_port(zx_take_startup_handle(PA_HND(PA_USER0, 1)));
132    if (!exception_port.is_valid()) {
133        fprintf(stderr, "crashsvc: no exception port\n");
134        return 1;
135    }
136    zx::channel channel(zx_take_startup_handle(PA_HND(PA_USER0, 2)));
137    if (!channel.is_valid()) {
138        fprintf(stderr, "crashsvc: no channel\n");
139        return 1;
140    }
141
142    for (;;) {
143        zx_port_packet_t packet;
144        zx_status_t status = exception_port.wait(zx::time::infinite(), &packet);
145        if (status != ZX_OK) {
146            fprintf(stderr, "crashsvc: zx_port_wait failed %d\n", status);
147            continue;
148        }
149
150        HandOffException(root_job, channel, packet);
151    }
152}
153