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 <stdint.h>
6 #include <stdio.h>
7 #include <threads.h>
8
9 #include <fbl/unique_ptr.h>
10 #include <fuchsia/crash/c/fidl.h>
11 #include <inspector/inspector.h>
12
13 #include <lib/fdio/util.h>
14 #include <lib/zx/channel.h>
15 #include <lib/zx/handle.h>
16 #include <lib/zx/job.h>
17 #include <lib/zx/port.h>
18 #include <lib/zx/process.h>
19 #include <lib/zx/thread.h>
20
21 #include <zircon/syscalls/exception.h>
22
GetChildKoids(const zx::job & job,zx_object_info_topic_t child_kind,fbl::unique_ptr<zx_koid_t[]> * koids,size_t * num_koids)23 static bool GetChildKoids(const zx::job& job, zx_object_info_topic_t child_kind,
24 fbl::unique_ptr<zx_koid_t[]>* koids, size_t* num_koids) {
25 size_t actual = 0;
26 size_t available = 0;
27
28 size_t count = 100;
29 koids->reset(new zx_koid_t[count]);
30
31 for (;;) {
32 if (job.get_info(child_kind, koids->get(), count * sizeof(zx_koid_t), &actual,
33 &available) != ZX_OK) {
34 fprintf(stderr, "crashsvc: failed to get child koids\n");
35 koids->reset();
36 *num_koids = 0;
37 return false;
38 }
39
40 if (actual == available) {
41 break;
42 }
43
44 // Resize to the expected number next time with a bit of slop to try to
45 // handle the race between here and the next request.
46 count = available + 10;
47 koids->reset(new zx_koid_t[count]);
48 }
49
50 // No need to actually downsize the output array since the size is separate.
51 *num_koids = actual;
52 return true;
53 }
54
FindProcess(const zx::job & job,zx_koid_t process_koid,zx::process * out)55 static bool FindProcess(const zx::job& job, zx_koid_t process_koid, zx::process* out) {
56 // Search this job for the process.
57 zx::process process;
58 if (job.get_child(process_koid, ZX_RIGHT_SAME_RIGHTS, &process) == ZX_OK) {
59 *out = std::move(process);
60 return true;
61 }
62
63 // Otherwise, enumerate and recurse into child jobs.
64 fbl::unique_ptr<zx_koid_t[]> child_koids;
65 size_t num_koids;
66 if (GetChildKoids(job, ZX_INFO_JOB_CHILDREN, &child_koids, &num_koids)) {
67 for (size_t i = 0; i < num_koids; ++i) {
68 zx::job child_job;
69 if (job.get_child(child_koids[i], ZX_RIGHT_SAME_RIGHTS, &child_job) != ZX_OK) {
70 continue;
71 }
72 if (FindProcess(child_job, process_koid, out)) {
73 return true;
74 }
75 }
76 }
77
78 return false;
79 }
80
81 struct crash_ctx {
82 zx::job root_job;
83 zx::port exception_port;
84 zx::channel svc_request;
85 };
86
HandOffException(const crash_ctx & ctx,const zx_port_packet_t & packet)87 static void HandOffException(const crash_ctx& ctx, const zx_port_packet_t& packet) {
88 zx::process process;
89 if (!FindProcess(ctx.root_job, packet.exception.pid, &process)) {
90 fprintf(stderr, "crashsvc: failed to find process for pid=%zu\n", packet.exception.pid);
91 return;
92 }
93
94 zx::thread thread;
95 if (process.get_child(packet.exception.tid, ZX_RIGHT_SAME_RIGHTS, &thread) != ZX_OK) {
96 fprintf(stderr, "crashsvc: failed to find thread for tid=%zu\n", packet.exception.tid);
97 return;
98 }
99
100 if (ctx.svc_request.is_valid()) {
101 // Use the full system analyzer FIDL service, presumably crashpad_analyzer.
102 zx::port port;
103 if (ctx.exception_port.duplicate(ZX_RIGHT_SAME_RIGHTS, &port) != ZX_OK) {
104 fprintf(stderr, "crashsvc: failed to duplicate exception port\n");
105 return;
106 }
107 // The resume_thread is only needed if the FIDL call fails.
108 zx::thread resume_thread;
109 thread.duplicate(ZX_RIGHT_SAME_RIGHTS, &resume_thread);
110
111 zx_status_t analizer_status = ZX_ERR_INTERNAL;
112 auto status = fuchsia_crash_AnalyzerHandleNativeException(
113 ctx.svc_request.get(), process.release(), thread.release(), port.release(),
114 &analizer_status);
115
116 if ((status != ZX_OK) || (analizer_status != ZX_OK)) {
117 fprintf(stderr, "crashsvc: analyzer failed, err (%d | %d)\n", status, analizer_status);
118 if (resume_thread) {
119 zx_task_resume_from_exception(
120 resume_thread.get(), ctx.exception_port.get(), ZX_RESUME_TRY_NEXT);
121 }
122 }
123 } else {
124 // Use the zircon built-in analyzer. Does not return status so we presume
125 // that upon failure it resumes the thread.
126 inspector_print_debug_info_and_resume_thread(
127 process.get(), thread.get(), ctx.exception_port.get());
128 }
129 }
130
crash_svc(void * arg)131 int crash_svc(void* arg) {
132 auto ctx = fbl::unique_ptr<crash_ctx>(reinterpret_cast<crash_ctx*>(arg));
133
134 for (;;) {
135 zx_port_packet_t packet;
136 zx_status_t status = ctx->exception_port.wait(zx::time::infinite(), &packet);
137 if (status != ZX_OK) {
138 fprintf(stderr, "crashsvc: zx_port_wait failed %d\n", status);
139 continue;
140 }
141
142 HandOffException(*ctx, packet);
143 }
144 }
145
146 // Initialize the crash service, this supersedes the standalone service with the same
147 // name that lived in zircon/system/core/crashsvc/crashsvc.cpp (/boot/bin/crashsvc) and
148 // ad-hoc microservice in devmgr that delegated to svchost. See ZX-3199 for details.
149 //
150 // The job of this service is to handle exceptions that reached |root_job| and delegate
151 // the crash analysis to one of two services:
152 //
153 // - built-in : using system/ulib/inspector
154 // - appmgr hosted: via FIDL interface call (fuchsia_crash_Analyzer).
155 //
156 // Which one depends if |analyzer_svc| is a valid channel handle, which svchost sets
157 // depending on "use_system".
158 //
start_crashsvc(zx::job root_job,zx_handle_t analyzer_svc)159 void start_crashsvc(zx::job root_job, zx_handle_t analyzer_svc) {
160
161 zx::port exception_port;
162 zx::port::create(0, &exception_port);
163
164 if (root_job.bind_exception_port(exception_port, 0, 0) != ZX_OK) {
165 fprintf(stderr, "svchost: unable to bind to root job exception port\n");
166 return;
167 }
168
169 zx::channel ch0, ch1;
170
171 if (analyzer_svc != ZX_HANDLE_INVALID) {
172 zx::channel::create(0u, &ch0, &ch1);
173 auto status = fdio_service_connect_at(
174 analyzer_svc, fuchsia_crash_Analyzer_Name, ch0.release());
175 if (status != ZX_OK) {
176 fprintf(stderr, "svchost: to connect to analyzer service\n");
177 return;
178 }
179 }
180
181 auto ctx = new crash_ctx {
182 std::move(root_job),
183 std::move(exception_port),
184 std::move(ch1)
185 };
186
187 thrd_t t;
188 if ((thrd_create_with_name(&t, crash_svc, ctx, "crash-svc")) == thrd_success) {
189 thrd_detach(t);
190 } else {
191 delete ctx;
192 }
193 }
194