// Copyright 2017 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 "crash-handler.h" #include #include #include #include #include #include #define EXCEPTION_PORT_KEY 1 // The test completed without the test thread crashing. #define TEST_ENDED_EVENT_KEY 2 // The test thread had a registered crash. #define TEST_THREAD_TERMINATED_KEY 3 // Signals sent from the test thread to the crash handler port to indicate // the test result. #define TEST_PASSED_SIGNAL ZX_USER_SIGNAL_0 #define TEST_FAILED_SIGNAL ZX_USER_SIGNAL_1 /** * Kills the crashing process or thread found in the registered list matching * the exception report. Processes or threads are registered in tests via * REGISTER_CRASH if a crash is expected. * * If killing failed, the test will be terminated. * * If the crash was not registered, it will be bubbled up to the crashlogger, * and then the test will be terminated. */ static void process_exception(crash_list_t crash_list, const zx_port_packet_t* packet, zx_handle_t exception_port) { const zx_packet_exception_t* exception = &packet->exception; // Check for exceptions from registered processes that are not really crashes. switch (packet->type) { case ZX_EXCP_THREAD_STARTING: case ZX_EXCP_THREAD_EXITING: { zx_handle_t process = crash_list_lookup_koid(crash_list, exception->pid); zx_handle_t thread = ZX_HANDLE_INVALID; if (process == ZX_HANDLE_INVALID) { // The test may have registered a thread handle instead. thread = crash_list_lookup_koid(crash_list, exception->tid); } if (process != ZX_HANDLE_INVALID || thread != ZX_HANDLE_INVALID) { zx_status_t status; if (thread == ZX_HANDLE_INVALID) { status = zx_object_get_child(process, exception->tid, ZX_RIGHT_SAME_RIGHTS, &thread); if (status != ZX_OK) { UNITTEST_FAIL_TRACEF( "FATAL: failed to get a handle to [%" PRIu64 "%." PRIu64 "] : error %s\n", exception->pid, exception->tid, zx_status_get_string(status)); exit(ZX_ERR_INTERNAL); } } status = zx_task_resume_from_exception(thread, exception_port, 0); if (status != ZX_OK) { UNITTEST_FAIL_TRACEF("FATAL: failed to resume [%" PRIu64 ".%" PRIu64 "] : error %s\n", exception->pid, exception->tid, zx_status_get_string(status)); exit(ZX_ERR_INTERNAL); } return; } break; } default: break; } // Check if the crashed process is in the registered list and remove // it if so. zx_handle_t match = crash_list_delete_koid(crash_list, exception->pid); if (match == ZX_HANDLE_INVALID) { // The test may have registered a thread handle instead. match = crash_list_delete_koid(crash_list, exception->tid); } // The crash was not registered. We should let crashlogger print out the // details and then fail the test. if (match == ZX_HANDLE_INVALID) { UNITTEST_FAIL_TRACEF("FATAL: [%" PRIu64 ".%" PRIu64 "] crashed with exception 0x%x but was not registered\n", exception->pid, exception->tid, packet->type); zx_handle_t job = zx_job_default(); if (job == ZX_HANDLE_INVALID) { UNITTEST_FAIL_TRACEF("FATAL: Unexpected environment. Tests should have a " "default job available\n"); exit(ZX_ERR_INTERNAL); } zx_handle_t process; zx_status_t status = zx_object_get_child(job, exception->pid, ZX_RIGHT_SAME_RIGHTS, &process); if (status != ZX_OK) { UNITTEST_FAIL_TRACEF("FATAL: failed to get a handle to [%" PRIu64 "] : error %s\n", exception->pid, zx_status_get_string(status)); exit(ZX_ERR_INTERNAL); } zx_handle_t thread; status = zx_object_get_child(process, exception->tid, ZX_RIGHT_SAME_RIGHTS, &thread); if (status != ZX_OK) { UNITTEST_FAIL_TRACEF("FATAL: failed to get a handle to [%" PRIu64 ".%" PRIu64 "] : error %s\n", exception->pid, exception->tid, zx_status_get_string(status)); zx_handle_close(process); exit(ZX_ERR_INTERNAL); } // Pass the exception up to crashlogger. status = zx_task_resume_from_exception(thread, exception_port, ZX_RESUME_TRY_NEXT); if (status == ZX_OK) { // Give crashlogger a little time to print info about the crashed // thread. zx_nanosleep(zx_deadline_after(ZX_MSEC(100))); } else { UNITTEST_FAIL_TRACEF("FATAL: could not pass exception from [%" PRIu64 ".%" PRIu64 "] : error %s\n", exception->pid, exception->tid, zx_status_get_string(status)); } // This may not be reached if the test process itself crashed, // as crashlogger will kill the crashed process. zx_handle_close(process); zx_handle_close(thread); // TODO: fail the test more gracefully. exit(ZX_ERR_INTERNAL); } zx_status_t status = zx_task_kill(match); if (status != ZX_OK) { UNITTEST_FAIL_TRACEF("FATAL: failed to kill [%" PRIu64 ".%" PRIu64 "] : error %s\n", exception->pid, exception->tid, zx_status_get_string(status)); exit(ZX_ERR_INTERNAL); } // The exception is still unprocessed. We should wait for termination so // there is no race condition with when we unbind the exception port. status = zx_object_wait_one(match, ZX_TASK_TERMINATED, ZX_TIME_INFINITE, NULL); if (status != ZX_OK) { UNITTEST_FAIL_TRACEF("FATAL: failed to wait for termination : error %s\n", zx_status_get_string(status)); exit(ZX_ERR_INTERNAL); } zx_handle_close(match); } // Returns the test result if it completes, else true if the test thread // had a registered crash. static test_result_t watch_test_thread(zx_handle_t port, crash_list_t crash_list) { zx_port_packet_t packet; while (true) { zx_status_t status = zx_port_wait(port, ZX_TIME_INFINITE, &packet); if (status != ZX_OK) { UNITTEST_FAIL_TRACEF("failed to wait on port: error %s\n", zx_status_get_string(status)); exit(ZX_ERR_INTERNAL); } switch (packet.key) { case EXCEPTION_PORT_KEY: process_exception(crash_list, &packet, port); break; case TEST_ENDED_EVENT_KEY: if (packet.signal.observed & TEST_PASSED_SIGNAL) { return TEST_PASSED; } else if (packet.signal.observed & TEST_FAILED_SIGNAL) { return TEST_FAILED; } else { UNITTEST_FAIL_TRACEF("unknown test ended event signal: %u\n", packet.signal.observed); exit(ZX_ERR_INTERNAL); } case TEST_THREAD_TERMINATED_KEY: // The test thread exited without sending the // TEST_ENDED_EVENT_KEY packet, so we must have killed the crashing // thread. If it was an unregistered crash, we would have exited // and failed the test already, so this must be a registered crash. return TEST_CRASHED; } } __UNREACHABLE; } struct test_data_t { // The test function to call. bool (*test_function)(void*); void* test_function_arg; // For signaling TEST_PASSED_SIGNAL or TEST_FAILED_SIGNAL. zx_handle_t test_ended_event; // For registering test termination. zx_handle_t port; // For registering the test thread, if it is expected to crash. crash_list_t crash_list; // Whether to bind to the thread exception port. bool bind_to_thread; }; // This is run as a separate thread, so exit() is used instead of returning // status values. static int run_test(void* arg) { test_data_t* data = (test_data_t*)arg; zx_handle_t self = zx_thread_self(); // We need to register for thread termination here instead of the main // thread. The main thread can't get a handle to this thread before it has // started, at which point the test may have run and crashed already, // leading to an invalid handle. zx_status_t status = zx_object_wait_async(self, data->port, TEST_THREAD_TERMINATED_KEY, ZX_THREAD_TERMINATED, ZX_WAIT_ASYNC_ONCE); if (status != ZX_OK) { UNITTEST_FAIL_TRACEF("FATAL: failed to wait on test thread termination : error %s\n", zx_status_get_string(status)); exit(ZX_ERR_INTERNAL); } // We also can't do this in the main thread as we wouldn't have the // thread handle yet. if (data->bind_to_thread) { status = zx_task_bind_exception_port(self, data->port, EXCEPTION_PORT_KEY, 0); if (status != ZX_OK) { UNITTEST_FAIL_TRACEF("FATAL: failed to bind to exception port: error %s\n", zx_status_get_string(status)); exit(ZX_ERR_INTERNAL); } crash_list_register(data->crash_list, self); } bool test_result = data->test_function(data->test_function_arg); // Notify the crash handler of the test result before returning. // We can't just return the test result as the test thread could // be registered to crash, so the crash handler can't use thrd_join. uint32_t signal = test_result ? TEST_PASSED_SIGNAL : TEST_FAILED_SIGNAL; status = zx_object_signal(data->test_ended_event, 0, signal); if (status != ZX_OK) { UNITTEST_FAIL_TRACEF("FATAL: failed to signal test result : error %s\n", zx_status_get_string(status)); exit(ZX_ERR_INTERNAL); } return 0; } // Runs the function in a separate thread with the given argument, // catching any crashes. // If bind_to_job is true, this will bind to the job exception port // before starting the test. // If false, this will bind to the test thread's exception port once started // and add the thread to the expected crashes list. static zx_status_t run_with_crash_handler(crash_list_t crash_list, bool (*fn_to_run)(void*), void* arg, bool bind_to_job, test_result_t* test_result) { zx_handle_t port; zx_status_t status = zx_port_create(0, &port); if (status != ZX_OK) { UNITTEST_FAIL_TRACEF("failed to create port: error %s\n", zx_status_get_string(status)); return status; } if (bind_to_job) { status = zx_task_bind_exception_port(zx_job_default(), port, EXCEPTION_PORT_KEY, 0); if (status != ZX_OK) { UNITTEST_FAIL_TRACEF("failed to bind to exception port: error %s\n", zx_status_get_string(status)); zx_handle_close(port); return status; } } zx_handle_t test_ended_event; status = zx_event_create(0, &test_ended_event); if (status != ZX_OK) { UNITTEST_FAIL_TRACEF("failed to create event: error %s\n", zx_status_get_string(status)); zx_handle_close(port); return status; } status = zx_object_wait_async(test_ended_event, port, TEST_ENDED_EVENT_KEY, TEST_PASSED_SIGNAL | TEST_FAILED_SIGNAL, ZX_WAIT_ASYNC_ONCE); if (status != ZX_OK) { UNITTEST_FAIL_TRACEF("failed to wait on test_ended_event: error %s\n", zx_status_get_string(status)); zx_handle_close(port); zx_handle_close(test_ended_event); return status; } // Run the test in a separate thread in case it crashes. thrd_t test_thread; test_data_t test_data = {.test_function = fn_to_run, .test_function_arg = arg, .test_ended_event = test_ended_event, .port = port, .crash_list = crash_list, .bind_to_thread = !bind_to_job}; int thrd_res = thrd_create(&test_thread, run_test, (void*)&test_data); if (thrd_res != thrd_success) { UNITTEST_FAIL_TRACEF("failed to create test thread\n"); zx_handle_close(port); zx_handle_close(test_ended_event); return thrd_status_to_zx_status(thrd_res); } // The test thread will signal on the test_ended event when it completes, // or the crash handler will catch it crashing. *test_result = watch_test_thread(port, crash_list); zx_handle_close(port); zx_handle_close(test_ended_event); return ZX_OK; } struct test_wrapper_arg_t { bool (*fn)(void); }; static bool test_wrapper(void* arg) { return static_cast(arg)->fn(); } zx_status_t run_test_with_crash_handler(crash_list_t crash_list, bool (*test_to_run)(), test_result_t* test_result) { test_wrapper_arg_t twarg = {.fn = test_to_run}; return run_with_crash_handler(crash_list, test_wrapper, &twarg, true, test_result); } struct crash_fn_wrapper_arg_t { void (*fn)(void*); void* arg; }; static bool crash_fn_wrapper(void* arg) { crash_fn_wrapper_arg_t* cfwarg = static_cast(arg); cfwarg->fn(cfwarg->arg); // The function is expected to crash and shouldn't get to here. return false; } zx_status_t run_fn_with_crash_handler(void (*fn_to_run)(void*), void* arg, test_result_t* test_result) { crash_list_t crash_list = crash_list_new(); crash_fn_wrapper_arg_t cfwarg = {.fn = fn_to_run, .arg = arg}; zx_status_t status = run_with_crash_handler(crash_list, crash_fn_wrapper, &cfwarg, false, test_result); crash_list_delete(crash_list); return status; }