| // 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 <inttypes.h> |
| |
| #include <zircon/process.h> |
| #include <zircon/status.h> |
| #include <zircon/syscalls/exception.h> |
| #include <zircon/syscalls/port.h> |
| #include <zircon/threads.h> |
| |
| #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<test_wrapper_arg_t*>(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<crash_fn_wrapper_arg_t*>(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; |
| } |