blob: 31523e1c46b8c1cae957b415e1b81ba4de1127a1 [file] [log] [blame]
// Copyright 2018 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.
#include <stdint.h>
#include <stdio.h>
#include <threads.h>
#include <fbl/unique_ptr.h>
#include <fuchsia/crash/c/fidl.h>
#include <inspector/inspector.h>
#include <lib/fdio/util.h>
#include <lib/zx/channel.h>
#include <lib/zx/handle.h>
#include <lib/zx/job.h>
#include <lib/zx/port.h>
#include <lib/zx/process.h>
#include <lib/zx/thread.h>
#include <zircon/syscalls/exception.h>
static bool GetChildKoids(const zx::job& job, zx_object_info_topic_t child_kind,
fbl::unique_ptr<zx_koid_t[]>* koids, size_t* num_koids) {
size_t actual = 0;
size_t available = 0;
size_t count = 100;
koids->reset(new zx_koid_t[count]);
for (;;) {
if (job.get_info(child_kind, koids->get(), count * sizeof(zx_koid_t), &actual,
&available) != ZX_OK) {
fprintf(stderr, "crashsvc: failed to get child koids\n");
koids->reset();
*num_koids = 0;
return false;
}
if (actual == available) {
break;
}
// Resize to the expected number next time with a bit of slop to try to
// handle the race between here and the next request.
count = available + 10;
koids->reset(new zx_koid_t[count]);
}
// No need to actually downsize the output array since the size is separate.
*num_koids = actual;
return true;
}
static bool FindProcess(const zx::job& job, zx_koid_t process_koid, zx::process* out) {
// Search this job for the process.
zx::process process;
if (job.get_child(process_koid, ZX_RIGHT_SAME_RIGHTS, &process) == ZX_OK) {
*out = std::move(process);
return true;
}
// Otherwise, enumerate and recurse into child jobs.
fbl::unique_ptr<zx_koid_t[]> child_koids;
size_t num_koids;
if (GetChildKoids(job, ZX_INFO_JOB_CHILDREN, &child_koids, &num_koids)) {
for (size_t i = 0; i < num_koids; ++i) {
zx::job child_job;
if (job.get_child(child_koids[i], ZX_RIGHT_SAME_RIGHTS, &child_job) != ZX_OK) {
continue;
}
if (FindProcess(child_job, process_koid, out)) {
return true;
}
}
}
return false;
}
struct crash_ctx {
zx::job root_job;
zx::port exception_port;
zx::channel svc_request;
};
static void HandOffException(const crash_ctx& ctx, const zx_port_packet_t& packet) {
zx::process process;
if (!FindProcess(ctx.root_job, packet.exception.pid, &process)) {
fprintf(stderr, "crashsvc: failed to find process for pid=%zu\n", packet.exception.pid);
return;
}
zx::thread thread;
if (process.get_child(packet.exception.tid, ZX_RIGHT_SAME_RIGHTS, &thread) != ZX_OK) {
fprintf(stderr, "crashsvc: failed to find thread for tid=%zu\n", packet.exception.tid);
return;
}
if (ctx.svc_request.is_valid()) {
// Use the full system analyzer FIDL service, presumably crashpad_analyzer.
zx::port port;
if (ctx.exception_port.duplicate(ZX_RIGHT_SAME_RIGHTS, &port) != ZX_OK) {
fprintf(stderr, "crashsvc: failed to duplicate exception port\n");
return;
}
// The resume_thread is only needed if the FIDL call fails.
zx::thread resume_thread;
thread.duplicate(ZX_RIGHT_SAME_RIGHTS, &resume_thread);
zx_status_t analyzer_status = ZX_ERR_INTERNAL;
auto status = fuchsia_crash_AnalyzerHandleNativeException(
ctx.svc_request.get(), process.release(), thread.release(), port.release(),
&analyzer_status);
if ((status != ZX_OK) || (analyzer_status != ZX_OK)) {
fprintf(stderr, "crashsvc: analyzer failed, err (%d | %d)\n", status, analyzer_status);
if (resume_thread) {
zx_task_resume_from_exception(
resume_thread.get(), ctx.exception_port.get(), ZX_RESUME_TRY_NEXT);
}
}
} else {
// Use the zircon built-in analyzer. Does not return status so we presume
// that upon failure it resumes the thread.
inspector_print_debug_info_and_resume_thread(
process.get(), thread.get(), ctx.exception_port.get());
}
}
int crash_svc(void* arg) {
auto ctx = fbl::unique_ptr<crash_ctx>(reinterpret_cast<crash_ctx*>(arg));
for (;;) {
zx_port_packet_t packet;
zx_status_t status = ctx->exception_port.wait(zx::time::infinite(), &packet);
if (status != ZX_OK) {
fprintf(stderr, "crashsvc: zx_port_wait failed %d\n", status);
continue;
}
HandOffException(*ctx, packet);
}
}
// Initialize the crash service, this supersedes the standalone service with the same
// name that lived in zircon/system/core/crashsvc/crashsvc.cpp (/boot/bin/crashsvc) and
// ad-hoc microservice in devmgr that delegated to svchost. See ZX-3199 for details.
//
// The job of this service is to handle exceptions that reached |root_job| and delegate
// the crash analysis to one of two services:
//
// - built-in : using system/ulib/inspector
// - appmgr hosted: via FIDL interface call (fuchsia_crash_Analyzer).
//
// Which one depends if |analyzer_svc| is a valid channel handle, which svchost sets
// depending on "use_system".
//
void start_crashsvc(zx::job root_job, zx_handle_t analyzer_svc) {
zx::port exception_port;
zx::port::create(0, &exception_port);
if (root_job.bind_exception_port(exception_port, 0, 0) != ZX_OK) {
fprintf(stderr, "svchost: unable to bind to root job exception port\n");
return;
}
zx::channel ch0, ch1;
if (analyzer_svc != ZX_HANDLE_INVALID) {
zx::channel::create(0u, &ch0, &ch1);
auto status = fdio_service_connect_at(
analyzer_svc, fuchsia_crash_Analyzer_Name, ch0.release());
if (status != ZX_OK) {
fprintf(stderr, "svchost: unable to connect to analyzer service\n");
return;
}
}
auto ctx = new crash_ctx {
std::move(root_job),
std::move(exception_port),
std::move(ch1)
};
thrd_t t;
if ((thrd_create_with_name(&t, crash_svc, ctx, "crash-svc")) == thrd_success) {
thrd_detach(t);
} else {
delete ctx;
}
}