blob: d2fdc16ffada5deba374c53e79900da2fc5279ca [file] [log] [blame]
// 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;
}