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