| // 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; |
| } |
| } |