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