blob: 5832915daee943604d12c9526734726bfa1579c8 [file] [log] [blame] [edit]
// Copyright 2019 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 <lib/fit/function.h>
#include <lib/zx/channel.h>
#include <lib/zx/event.h>
#include <lib/zx/exception.h>
#include <lib/zx/port.h>
#include <lib/zx/task.h>
#include <lib/zx/thread.h>
#include <lib/zx/time.h>
#include <threads.h>
#include <zircon/assert.h>
#include <zircon/status.h>
#include <zircon/syscalls/debug.h>
#include <zircon/syscalls/exception.h>
#include <zircon/syscalls/port.h>
#include <zircon/types.h>
#include <cstdint>
#include <string>
#include <fbl/auto_call.h>
#include <zxtest/base/death-statement.h>
namespace zxtest {
namespace internal {
namespace {
#define STRING(x) #x
#define MAKE_MESSAGE(reason, line) \
"Death Test Internal Error at " __FILE__ ":" STRING(line) " " reason
#define SET_ERROR(var, msg) \
do { \
var = MAKE_MESSAGE(msg, __LINE__); \
} while (0)
// Keys used to filter exception ports.
enum class PortKeys : uint64_t {
// Exception raised and handled.
kException = 1,
kThreadTermination = 2,
kThreadCompletion = 3,
kThreadError = 4,
};
// It is only safe to transmit within the same process.
struct ErrorInfo {
zx_port_packet_t ToPacket() {
zx_port_packet_t packet;
packet.key = static_cast<uint64_t>(PortKeys::kThreadError);
packet.type = ZX_PKT_TYPE_USER;
packet.user.u64[0] = reinterpret_cast<uint64_t>(this);
return packet;
}
std::string error_msg;
};
struct RoutineArgs {
// Statement to be executed.
fit::function<void()> statement;
// Port for signaling thread termination. This is used to unblock the main thread.
zx::port event_port;
// The thread will bind this channel as the exception handler.
zx::channel exception_channel;
};
void SendError(const zx::port& port, const char* message) {
std::unique_ptr<ErrorInfo> info = std::make_unique<ErrorInfo>();
info->error_msg = message;
zx_port_packet_t packet = info->ToPacket();
zx_status_t result = port.queue(&packet);
if (result != ZX_OK) {
fprintf(stderr, "%s.\nDeath Test Fatal Error: zx::port::queue failed with status %s.\n",
info->error_msg.c_str(), zx_status_get_string(result));
fflush(stderr);
exit(-1);
}
// This will we released by the main thread once the error message is dequeued.
info.release();
return;
}
// Try to exit cleanly, if not just kill the entire process.
#define SEND_ERROR_AND_RETURN(port, message) \
do { \
const char* error_message; \
SET_ERROR(error_message, message); \
SendError(port, error_message); \
return -1; \
} while (0)
// Even though it is a separate thread, it is stalling the main thread, until it completes,
// which is why it is safe to interact with the test harness.
int RoutineThread(void* args) {
RoutineArgs* routine_args = reinterpret_cast<RoutineArgs*>(args);
zx::unowned_thread thread = zx::thread::self();
auto signal_completion = fbl::MakeAutoCall([&routine_args]() {
zx_port_packet_t packet;
packet.type = ZX_PKT_TYPE_USER;
packet.key = static_cast<uint64_t>(PortKeys::kThreadCompletion);
if (routine_args->event_port.queue(&packet) != ZX_OK) {
fprintf(stderr, "Death Test Fatal Error: zx::port::queue failed.\n");
fflush(stderr);
exit(-1);
}
});
// Register thread termination with the port.
if (thread->wait_async(routine_args->event_port,
static_cast<uint64_t>(PortKeys::kThreadTermination), ZX_THREAD_TERMINATED,
0) != ZX_OK) {
SEND_ERROR_AND_RETURN(routine_args->event_port, "Failed to register thread events with port");
}
if (!routine_args->statement) {
SEND_ERROR_AND_RETURN(routine_args->event_port, "Empty death statement");
}
// Bind the exception channel, so main thread can inspect for exceptions once this thread is
// terminated.
if (thread->create_exception_channel(0, &routine_args->exception_channel)) {
SEND_ERROR_AND_RETURN(routine_args->event_port, "Failed to create exception_channel");
}
// Register the exception channel with the port so we can process exceptions and
// unblock/terminate this thread.
if (routine_args->exception_channel.wait_async(routine_args->event_port,
static_cast<uint64_t>(PortKeys::kException),
ZX_CHANNEL_READABLE, 0) != ZX_OK) {
SEND_ERROR_AND_RETURN(routine_args->event_port,
"Failed to register exception channel with port");
}
routine_args->statement();
return 0;
}
__NO_SAFESTACK static void thrd_exit_success() { thrd_exit(0); }
// Extracts the thread from |exception| and causes it to exit.
zx_status_t ExitExceptionThread(zx::exception exception, std::string* error_message) {
zx::thread thread;
zx_status_t status = exception.get_thread(&thread);
if (status != ZX_OK) {
SET_ERROR(*error_message, "Failed to obtain thread from exception handle");
return status;
}
if (!thread.is_valid()) {
SET_ERROR(*error_message, "Exception contained invalid exception handle");
return ZX_ERR_INTERNAL;
}
// Set the thread's registers to `zx_thread_exit`.
zx_thread_state_general_regs_t regs;
status = thread.read_state(ZX_THREAD_STATE_GENERAL_REGS, &regs, sizeof(regs));
if (status != ZX_OK) {
SET_ERROR(*error_message, "Failed to read exception thread state");
return status;
}
#if defined(__aarch64__)
regs.pc = reinterpret_cast<uintptr_t>(thrd_exit_success);
#elif defined(__x86_64__)
regs.rip = reinterpret_cast<uintptr_t>(thrd_exit_success);
#else
#error "what machine?"
#endif
status = thread.write_state(ZX_THREAD_STATE_GENERAL_REGS, &regs, sizeof(regs));
if (status != ZX_OK) {
SET_ERROR(*error_message, "Failed to write exception thread state");
return status;
}
// Clear the exception so the thread continues.
uint32_t state = ZX_EXCEPTION_STATE_HANDLED;
status = exception.set_property(ZX_PROP_EXCEPTION_STATE, &state, sizeof(state));
if (status != ZX_OK) {
SET_ERROR(*error_message, "Failed to handle exception");
return status;
}
exception.reset();
// Wait until the thread exits.
status = thread.wait_one(ZX_THREAD_TERMINATED, zx::time::infinite(), nullptr);
if (status != ZX_OK) {
SET_ERROR(*error_message, "Failed to wait for thread exit");
return status;
}
return ZX_OK;
}
} // namespace
DeathStatement::DeathStatement(fit::function<void()> statement) : statement_(std::move(statement)) {
state_ = State::kUnknown;
error_message_ = "";
}
void DeathStatement::Execute() {
RoutineArgs routine_args;
routine_args.statement = std::move(statement_);
state_ = State::kStarted;
if (zx::port::create(0u, &routine_args.event_port) != ZX_OK) {
SET_ERROR(error_message_, "Failed to created event_port");
return;
}
thrd_t death_thread;
if (thrd_create(&death_thread, &RoutineThread, &routine_args) != thrd_success) {
SET_ERROR(error_message_, "Failed to create death_thred");
return;
}
Listen(routine_args.event_port, routine_args.exception_channel);
thrd_join(death_thread, nullptr);
}
// Listens for events on |event_port|. Eventually the thread will register its termination and the
// exception channel so that they can be processed.
void DeathStatement::Listen(const zx::port& event_port, const zx::channel& exception_channel) {
zx_port_packet_t packet;
// Wait until either the port is closed or a packet arrives.
while (event_port.wait(zx::time::infinite(), &packet) == ZX_OK) {
switch (static_cast<PortKeys>(packet.key)) {
case PortKeys::kException:
if (HandleException(exception_channel)) {
return;
}
break;
case PortKeys::kThreadCompletion:
case PortKeys::kThreadTermination:
// We only mark the execution as success if there was no internal error.
if (state_ == State::kStarted) {
state_ = DeathStatement::State::kSuccess;
}
return;
case PortKeys::kThreadError: {
ErrorInfo* info = reinterpret_cast<ErrorInfo*>(packet.user.u64[0]);
state_ = State::kInternalError;
error_message_ = std::move(info->error_msg);
delete info;
} break;
default:
continue;
}
}
// If this is reached, we are in a bad state.
state_ = State::kBadState;
return;
}
bool DeathStatement::HandleException(const zx::channel& exception_channel) {
zx_exception_info_t exception_info;
zx::exception exception;
uint32_t num_handles = 1;
uint32_t num_bytes = sizeof(zx_exception_info_t);
auto set_internal_error = fbl::MakeAutoCall([this]() { state_ = State::kInternalError; });
if (exception_channel.read(0, &exception_info, exception.reset_and_get_address(),
sizeof(zx_exception_info_t), 1, &num_bytes, &num_handles) != ZX_OK) {
SET_ERROR(error_message_, "Failed to read exception from exception channel");
return true;
}
if (num_handles != 1 || num_bytes != sizeof(zx_exception_info_t)) {
SET_ERROR(error_message_, "Missing exception handle or exception info");
return true;
}
if (!exception.is_valid()) {
SET_ERROR(error_message_, "Exception handle is not valid");
return true;
}
set_internal_error.cancel();
// Ignore exceptions that are not really crashes and resume the thread.
switch (exception_info.type) {
case ZX_EXCP_THREAD_STARTING:
case ZX_EXCP_THREAD_EXITING:
// Returning will close the exception handle and will resume the blocked threads.
return false;
default:
break;
}
// If we fail to kill the thread, we set the statement to a bad state so the harness can exit
// cleanly.
if (ExitExceptionThread(std::move(exception), &error_message_) != ZX_OK) {
// ExitExceptionThread sets error_message_ correctly.
state_ = DeathStatement::State::kBadState;
return true;
}
// If everything went ok, we mark the statement as completed with exception.
state_ = DeathStatement::State::kException;
return true;
}
} // namespace internal
} // namespace zxtest