blob: c09333e5e7d316fe70d43b0bef7b02b9dd8ed1ad [file] [log] [blame]
// Copyright 2016 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 <fcntl.h>
#include <fuchsia/process/llcpp/fidl.h>
#include <lib/backtrace-request/backtrace-request.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/io.h>
#include <lib/fdio/namespace.h>
#include <lib/zx/channel.h>
#include <lib/zx/handle.h>
#include <lib/zx/job.h>
#include <lib/zx/vmo.h>
#include <stdlib.h>
#include <string.h>
#include <zircon/dlfcn.h>
#include <zircon/process.h>
#include <zircon/processargs.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/exception.h>
#include <zircon/syscalls/port.h>
#include <cstdio>
#include <string>
#include <runtime/thread.h>
#include <test-utils/test-utils.h>
#include <unittest/unittest.h>
#define TU_FAIL_ERRCODE 10
namespace fprocess = ::llcpp::fuchsia::process;
void tu_fatal(const char* what, zx_status_t status) {
const char* reason = zx_status_get_string(status);
unittest_printf_critical("\nFATAL: %s failed, rc %d (%s)\n", what, status, reason);
// Request a backtrace to assist debugging.
unittest_printf_critical("FATAL: backtrace follows:\n");
unittest_printf_critical(" (using sw breakpoint request to crashlogger)\n");
backtrace_request();
unittest_printf_critical("FATAL: exiting process\n");
exit(TU_FAIL_ERRCODE);
}
// Prints a message and terminates the process if |status| is not |ZX_OK|.
static void tu_check(const char* what, zx_status_t status) {
if (status != ZX_OK) {
tu_fatal(what, status);
}
}
bool tu_channel_wait_readable(zx_handle_t channel) {
zx_signals_t signals = ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED;
zx_signals_t pending;
zx_status_t result = zx_object_wait_one(channel, signals, ZX_TIME_INFINITE, &pending);
if (result != ZX_OK)
tu_fatal(__func__, result);
if ((pending & ZX_CHANNEL_READABLE) == 0) {
unittest_printf("%s: peer closed\n", __func__);
return false;
}
return true;
}
zx_handle_t tu_launch_process(zx_handle_t job, const char* name, int argc, const char* const* argv,
int envc, const char* const* envp, size_t num_handles,
zx_handle_t* handles, uint32_t* handle_ids) {
springboard_t* sb =
tu_launch_init(job, name, argc, argv, envc, envp, num_handles, handles, handle_ids);
return tu_launch_fini(sb);
}
// Loads the executable at the given path into the given VMO.
static zx_status_t load_executable_vmo(const char* path, zx::vmo* result) {
int fd = open(path, O_RDONLY);
if (fd < 0) {
return ZX_ERR_NOT_FOUND;
}
zx::vmo vmo;
zx_status_t status = fdio_get_vmo_clone(fd, vmo.reset_and_get_address());
close(fd);
tu_check("load vmo from fd", status);
status = vmo.replace_as_executable(zx::resource(), result);
tu_check("replace vmo as executable", status);
if (strlen(path) >= ZX_MAX_NAME_LEN) {
const char* p = strrchr(path, '/');
if (p != NULL) {
path = p + 1;
}
}
status = result->set_property(ZX_PROP_NAME, path, strlen(path));
tu_check("setting vmo name", status);
return ZX_OK;
}
struct springboard {
fprocess::ProcessStartData data;
springboard(fprocess::ProcessStartData* reference) {
data.process.reset(reference->process.release());
data.root_vmar.reset(reference->root_vmar.release());
data.thread.reset(reference->thread.release());
data.entry = reference->entry;
data.stack = reference->stack;
data.bootstrap.reset(reference->bootstrap.release());
data.vdso_base = reference->vdso_base;
// Not using base.
}
};
zx_handle_t springboard_get_process_handle(springboard_t* sb) { return sb->data.process.get(); }
zx_handle_t springboard_get_root_vmar_handle(springboard_t* sb) { return sb->data.root_vmar.get(); }
springboard_t* tu_launch_init(zx_handle_t job, const char* name, int argc, const char* const* argv,
int envc, const char* const* envp, size_t num_handles,
zx_handle_t* handles, uint32_t* handle_ids) {
zx_status_t status;
// Connect to the Launcher service.
zx::channel launcher_channel, launcher_request;
status = zx::channel::create(0, &launcher_channel, &launcher_request);
tu_check("creating channel for launcher service", status);
std::string service_name = "/svc/" + std::string(fprocess::Launcher::Name);
status = fdio_service_connect(service_name.c_str(), launcher_request.release());
tu_check("connecting to launcher service", status);
fprocess::Launcher::SyncClient launcher(std::move(launcher_channel));
// Add arguments.
{
fidl::VectorView<uint8_t> data[argc];
for (int i = 0; i < argc; i++) {
data[i] = fidl::VectorView<uint8_t>(
fidl::unowned_ptr(reinterpret_cast<uint8_t*>(const_cast<char*>(argv[i]))),
strlen(argv[i]));
}
fidl::VectorView<fidl::VectorView<uint8_t>> args(fidl::unowned_ptr(data), argc);
fprocess::Launcher::ResultOf::AddArgs result = launcher.AddArgs(std::move(args));
tu_check("sending arguments", result.status());
}
// Add environment.
if (envp) {
fidl::VectorView<uint8_t> data[envc];
for (int i = 0; i < envc; i++) {
data[i] = fidl::VectorView<uint8_t>(
fidl::unowned_ptr(reinterpret_cast<uint8_t*>(const_cast<char*>(envp[i]))),
strlen(envp[i]));
}
fidl::VectorView<fidl::VectorView<uint8_t>> env(fidl::unowned_ptr(data), envc);
fprocess::Launcher::ResultOf::AddEnvirons result = launcher.AddEnvirons(std::move(env));
tu_check("sending environment", result.status());
}
// Add names.
{
fdio_flat_namespace_t* flat;
zx_status_t status = fdio_ns_export_root(&flat);
tu_check("getting namespace", status);
size_t count = flat->count;
fprocess::NameInfo data[count];
for (size_t i = 0; i < count; i++) {
const char* path = flat->path[i];
data[i].path = fidl::unowned_str(path, strlen(path));
data[i].directory.reset(flat->handle[i]);
}
fidl::VectorView<fprocess::NameInfo> names(fidl::unowned_ptr(data), count);
fprocess::Launcher::ResultOf::AddNames result = launcher.AddNames(std::move(names));
tu_check("sending names", result.status());
free(flat);
}
// Add handles.
{
const size_t handle_count = num_handles + 1;
fprocess::HandleInfo handle_infos[handle_count];
// Input handles.
size_t index;
for (index = 0; index < num_handles; index++) {
handle_infos[index].handle.reset(handles[index]);
handle_infos[index].id = handle_ids[index];
}
// LDSVC
zx::channel ldsvc;
status = dl_clone_loader_service(handle_infos[index].handle.reset_and_get_address());
tu_check("getting loader service", status);
handle_infos[index++].id = PA_LDSVC_LOADER;
fidl::VectorView<fprocess::HandleInfo> handle_vector(fidl::unowned_ptr(handle_infos),
handle_count);
fprocess::Launcher::ResultOf::AddHandles result = launcher.AddHandles(std::move(handle_vector));
tu_check("sending handles", result.status());
}
// Create the process.
fprocess::LaunchInfo launch_info;
const char* filename = argv[0];
status = load_executable_vmo(filename, &launch_info.executable);
tu_check("loading executable", status);
if (job == ZX_HANDLE_INVALID) {
job = zx_job_default();
}
zx::unowned_job unowned_job(job);
status = unowned_job->duplicate(ZX_RIGHT_SAME_RIGHTS, &launch_info.job);
tu_check("duplicating job for launch", status);
const char* process_name = name ? name : filename;
size_t process_name_size = strlen(process_name);
if (process_name_size >= ZX_MAX_NAME_LEN) {
process_name_size = ZX_MAX_NAME_LEN - 1;
}
launch_info.name = fidl::unowned_str(process_name, process_name_size);
fprocess::Launcher::ResultOf::CreateWithoutStarting result =
launcher.CreateWithoutStarting(std::move(launch_info));
tu_check("process creation", result.status());
fprocess::Launcher::CreateWithoutStartingResponse* response = result.Unwrap();
tu_check("fuchsia.process.Launcher#CreateWithoutStarting failed", response->status);
return new springboard(response->data.get());
}
void springboard_set_bootstrap(springboard_t* sb, zx_handle_t bootstrap) {
sb->data.bootstrap.reset(bootstrap);
}
zx_handle_t tu_launch_fini(springboard* sb) {
zx_handle_t process = sb->data.process.release();
zx_handle_t thread = sb->data.thread.release();
zx_status_t status = zx_process_start(process, thread, sb->data.entry, sb->data.stack,
sb->data.bootstrap.release(), sb->data.vdso_base);
zx_handle_close(thread);
tu_check("starting process", status);
delete sb;
return process;
}
void tu_process_wait_signaled(zx_handle_t process) {
zx_signals_t signals = ZX_PROCESS_TERMINATED;
zx_signals_t pending;
zx_status_t result = zx_object_wait_one(process, signals, ZX_TIME_INFINITE, &pending);
if (result != ZX_OK)
tu_fatal(__func__, result);
if ((pending & ZX_PROCESS_TERMINATED) == 0) {
unittest_printf_critical("%s: unexpected return from zx_object_wait_one\n", __func__);
exit(TU_FAIL_ERRCODE);
}
}
bool tu_process_has_exited(zx_handle_t process) {
zx_info_process_t info;
zx_status_t status;
if ((status = zx_object_get_info(process, ZX_INFO_PROCESS, &info, sizeof(info), NULL, NULL)) < 0)
tu_fatal("get process info", status);
return info.exited;
}
int tu_process_get_return_code(zx_handle_t process) {
zx_info_process_t info;
zx_status_t status;
if ((status = zx_object_get_info(process, ZX_INFO_PROCESS, &info, sizeof(info), NULL, NULL)) < 0)
tu_fatal("get process info", status);
if (!info.exited) {
unittest_printf_critical("attempt to read return code of non-exited process");
exit(TU_FAIL_ERRCODE);
}
return static_cast<int>(info.return_code);
}
int tu_process_wait_exit(zx_handle_t process) {
tu_process_wait_signaled(process);
return tu_process_get_return_code(process);
}
zx_handle_t tu_process_get_thread(zx_handle_t process, zx_koid_t tid) {
zx_handle_t thread;
zx_status_t status = zx_object_get_child(process, tid, ZX_RIGHT_SAME_RIGHTS, &thread);
if (status == ZX_ERR_NOT_FOUND)
return ZX_HANDLE_INVALID;
if (status < 0)
tu_fatal(__func__, status);
return thread;
}
size_t tu_process_get_threads(zx_handle_t process, zx_koid_t* threads, size_t max_threads) {
size_t num_threads;
size_t buf_size = max_threads * sizeof(threads[0]);
zx_status_t status =
zx_object_get_info(process, ZX_INFO_PROCESS_THREADS, threads, buf_size, &num_threads, NULL);
if (status < 0)
tu_fatal(__func__, status);
return num_threads;
}
zx_handle_t tu_create_exception_channel(zx_handle_t task, uint32_t options) {
zx_handle_t channel = ZX_HANDLE_INVALID;
zx_status_t status = zx_task_create_exception_channel(task, options, &channel);
if (status < 0)
tu_fatal(__func__, status);
return channel;
}
zx_handle_t tu_exception_get_process(zx_handle_t exception) {
zx_handle_t process = ZX_HANDLE_INVALID;
zx_status_t status = zx_exception_get_process(exception, &process);
if (status < 0)
tu_fatal(__func__, status);
return process;
}
zx_status_t tu_cleanup_breakpoint(zx_handle_t thread) {
#if defined(__x86_64__)
// On x86, the pc is left at one past the s/w break insn,
// so there's nothing more we need to do.
return ZX_OK;
#elif defined(__aarch64__)
// Skip past the brk instruction.
zx_thread_state_general_regs_t regs = {};
zx_status_t status =
zx_thread_read_state(thread, ZX_THREAD_STATE_GENERAL_REGS, &regs, sizeof(regs));
if (status != ZX_OK)
return status;
regs.pc += 4;
return zx_thread_write_state(thread, ZX_THREAD_STATE_GENERAL_REGS, &regs, sizeof(regs));
#else
return ZX_ERR_NOT_SUPPORTED;
#endif
}
zx_handle_t tu_exception_get_thread(zx_handle_t exception) {
zx_handle_t thread = ZX_HANDLE_INVALID;
zx_status_t status = zx_exception_get_thread(exception, &thread);
if (status < 0)
tu_fatal(__func__, status);
return thread;
}
void tu_resume_from_exception(zx_handle_t exception_handle) {
uint32_t state = ZX_EXCEPTION_STATE_HANDLED;
zx_status_t status =
zx_object_set_property(exception_handle, ZX_PROP_EXCEPTION_STATE, &state, sizeof(state));
if (status != ZX_OK)
tu_fatal(__func__, status);
status = zx_handle_close(exception_handle);
if (status != ZX_OK)
tu_fatal(__func__, status);
}
void tu_object_wait_async(zx_handle_t handle, zx_handle_t port, zx_signals_t signals) {
uint64_t key = tu_get_koid(handle);
uint32_t options = ZX_WAIT_ASYNC_ONCE;
zx_status_t status = zx_object_wait_async(handle, port, key, signals, options);
if (status < 0)
tu_fatal(__func__, status);
}
void tu_handle_get_basic_info(zx_handle_t handle, zx_info_handle_basic_t* info) {
zx_status_t status =
zx_object_get_info(handle, ZX_INFO_HANDLE_BASIC, info, sizeof(*info), NULL, NULL);
if (status < 0)
tu_fatal(__func__, status);
}
zx_koid_t tu_get_koid(zx_handle_t handle) {
zx_info_handle_basic_t info;
tu_handle_get_basic_info(handle, &info);
return info.koid;
}
zx_koid_t tu_get_related_koid(zx_handle_t handle) {
zx_info_handle_basic_t info;
tu_handle_get_basic_info(handle, &info);
return info.related_koid;
}
zx_info_thread_t tu_thread_get_info(zx_handle_t thread) {
zx_info_thread_t info;
zx_status_t status = zx_object_get_info(thread, ZX_INFO_THREAD, &info, sizeof(info), NULL, NULL);
if (status < 0)
tu_fatal("zx_object_get_info(ZX_INFO_THREAD)", status);
return info;
}
uint32_t tu_thread_get_state(zx_handle_t thread) {
zx_info_thread_t info = tu_thread_get_info(thread);
return info.state;
}
const char* tu_exception_to_string(uint32_t exception) {
switch (exception) {
case ZX_EXCP_GENERAL:
return "ZX_EXCP_GENERAL";
case ZX_EXCP_FATAL_PAGE_FAULT:
return "ZX_EXCP_FATAL_PAGE_FAULT";
case ZX_EXCP_UNDEFINED_INSTRUCTION:
return "ZX_EXCP_UNDEFINED_INSTRUCTION";
case ZX_EXCP_SW_BREAKPOINT:
return "ZX_EXCP_SW_BREAKPOINT";
case ZX_EXCP_HW_BREAKPOINT:
return "ZX_EXCP_HW_BREAKPOINT";
case ZX_EXCP_UNALIGNED_ACCESS:
return "ZX_EXCP_UNALIGNED_ACCESS";
case ZX_EXCP_THREAD_STARTING:
return "ZX_EXCP_THREAD_STARTING";
case ZX_EXCP_THREAD_EXITING:
return "ZX_EXCP_THREAD_EXITING";
case ZX_EXCP_POLICY_ERROR:
return "ZX_EXCP_POLICY_ERROR";
case ZX_EXCP_PROCESS_STARTING:
return "ZX_EXCP_PROCESS_STARTING";
default:
break;
}
return "<unknown>";
}