blob: dc4a4056d6df40ba7c2df0fac18f447fe34836c7 [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 <inttypes.h>
#include <link.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <fbl/algorithm.h>
#include <launchpad/launchpad.h>
#include <launchpad/vmo.h>
#include <test-utils/test-utils.h>
#include <unittest/unittest.h>
#include <zircon/compiler.h>
#include <zircon/process.h>
#include <zircon/processargs.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/debug.h>
#include <zircon/syscalls/object.h>
#include <zircon/syscalls/port.h>
#include "utils.h"
// argv[0]
const char* program_path;
static const uint64_t exception_port_key = 0x6b6579; // "key"
uint32_t get_uint32(char* buf) {
uint32_t value = 0;
memcpy(&value, buf, sizeof(value));
return value;
}
uint64_t get_uint64(char* buf) {
uint64_t value = 0;
memcpy(&value, buf, sizeof(value));
return value;
}
void set_uint64(char* buf, uint64_t value) {
memcpy(buf, &value, sizeof(value));
}
uint32_t get_uint32_property(zx_handle_t handle, uint32_t prop) {
uint32_t value;
zx_status_t status = zx_object_get_property(handle, prop, &value, sizeof(value));
if (status != ZX_OK)
tu_fatal("zx_object_get_property failed", status);
return value;
}
void send_msg(zx_handle_t handle, message msg) {
uint64_t data = msg;
unittest_printf("sending message %d on handle %u\n", msg, handle);
tu_channel_write(handle, 0, &data, sizeof(data), NULL, 0);
}
// This returns "bool" because it uses ASSERT_*.
bool recv_msg(zx_handle_t handle, message* msg) {
BEGIN_HELPER;
uint64_t data;
uint32_t num_bytes = sizeof(data);
unittest_printf("waiting for message on handle %u\n", handle);
ASSERT_TRUE(tu_channel_wait_readable(handle), "peer closed while trying to read message");
tu_channel_read(handle, 0, &data, &num_bytes, NULL, 0);
ASSERT_EQ(num_bytes, sizeof(data), "unexpected message size");
*msg = static_cast<message>(data);
unittest_printf("received message %d\n", *msg);
END_HELPER;
}
void dump_gregs(zx_handle_t thread_handle, const zx_thread_state_general_regs_t* regs) {
unittest_printf("Registers for thread %d\n", thread_handle);
#define DUMP_NAMED_REG(name) \
unittest_printf(" %8s %24ld 0x%lx\n", #name, (long)regs->name, (long)regs->name)
#if defined(__x86_64__)
DUMP_NAMED_REG(rax);
DUMP_NAMED_REG(rbx);
DUMP_NAMED_REG(rcx);
DUMP_NAMED_REG(rdx);
DUMP_NAMED_REG(rsi);
DUMP_NAMED_REG(rdi);
DUMP_NAMED_REG(rbp);
DUMP_NAMED_REG(rsp);
DUMP_NAMED_REG(r8);
DUMP_NAMED_REG(r9);
DUMP_NAMED_REG(r10);
DUMP_NAMED_REG(r11);
DUMP_NAMED_REG(r12);
DUMP_NAMED_REG(r13);
DUMP_NAMED_REG(r14);
DUMP_NAMED_REG(r15);
DUMP_NAMED_REG(rip);
DUMP_NAMED_REG(rflags);
#elif defined(__aarch64__)
for (int i = 0; i < 30; i++) {
unittest_printf(" r[%2d] %24ld 0x%lx\n", i, (long)regs->r[i], (long)regs->r[i]);
}
DUMP_NAMED_REG(lr);
DUMP_NAMED_REG(sp);
DUMP_NAMED_REG(pc);
DUMP_NAMED_REG(cpsr);
#endif
#undef DUMP_NAMED_REG
}
void dump_inferior_regs(zx_handle_t thread) {
zx_thread_state_general_regs_t regs;
read_inferior_gregs(thread, &regs);
dump_gregs(thread, &regs);
}
// N.B. It is assumed |buf_size| is large enough.
void read_inferior_gregs(zx_handle_t thread, zx_thread_state_general_regs_t* in) {
zx_status_t status = zx_thread_read_state(
thread, ZX_THREAD_STATE_GENERAL_REGS, in, sizeof(zx_thread_state_general_regs_t));
// It's easier to just terminate if this fails.
if (status != ZX_OK)
tu_fatal("read_inferior_gregs: zx_thread_read_state", status);
}
void write_inferior_gregs(zx_handle_t thread, const zx_thread_state_general_regs_t* out) {
zx_status_t status = zx_thread_write_state(thread, ZX_THREAD_STATE_GENERAL_REGS, out,
sizeof(zx_thread_state_general_regs_t));
// It's easier to just terminate if this fails.
if (status != ZX_OK)
tu_fatal("write_inferior_gregs: zx_thread_write_state", status);
}
size_t read_inferior_memory(zx_handle_t proc, uintptr_t vaddr, void* buf, size_t len) {
zx_status_t status = zx_process_read_memory(proc, vaddr, buf, len, &len);
if (status < 0)
tu_fatal("read_inferior_memory", status);
return len;
}
size_t write_inferior_memory(zx_handle_t proc, uintptr_t vaddr, const void* buf, size_t len) {
zx_status_t status = zx_process_write_memory(proc, vaddr, buf, len, &len);
if (status < 0)
tu_fatal("write_inferior_memory", status);
return len;
}
// This does everything that launchpad_launch_fdio_etc does except
// start the inferior. We want to attach to it first.
// TODO(dje): Are there other uses of such a wrapper? Move to launchpad?
// Plus there's a fair bit of code here. IWBN to not have to update it as
// launchpad_launch_fdio_etc changes.
zx_status_t create_inferior(const char* name, int argc, const char* const* argv,
const char* const* envp, size_t hnds_count, zx_handle_t* handles,
uint32_t* ids, launchpad_t** out_launchpad) {
launchpad_t* lp = NULL;
const char* filename = argv[0];
if (name == NULL)
name = filename;
zx_status_t status;
launchpad_create(0u, name, &lp);
launchpad_load_from_file(lp, filename);
launchpad_set_args(lp, argc, argv);
launchpad_set_environ(lp, envp);
launchpad_clone(lp, LP_CLONE_FDIO_ALL);
status = launchpad_add_handles(lp, hnds_count, handles, ids);
if (status < 0) {
launchpad_destroy(lp);
} else {
*out_launchpad = lp;
}
return status;
}
bool setup_inferior(const char* name, launchpad_t** out_lp, zx_handle_t* out_inferior,
zx_handle_t* out_channel) {
BEGIN_HELPER;
zx_status_t status;
zx_handle_t channel1, channel2;
tu_channel_create(&channel1, &channel2);
const char verbosity_string[] = {'v', '=', static_cast<char>(utest_verbosity_level + '0'),
'\0'};
const char* test_child_path = program_path;
const char* const argv[] = {test_child_path, name, verbosity_string};
zx_handle_t handles[1] = {channel2};
uint32_t handle_ids[1] = {PA_USER0};
launchpad_t* lp;
unittest_printf("Creating process \"%s\"\n", name);
status = create_inferior(name, fbl::count_of(argv), argv, NULL, fbl::count_of(handles),
handles, handle_ids, &lp);
ASSERT_EQ(status, ZX_OK, "failed to create inferior");
// Note: |inferior| is a borrowed handle here.
zx_handle_t inferior = launchpad_get_process_handle(lp);
ASSERT_NE(inferior, ZX_HANDLE_INVALID, "can't get launchpad process handle");
zx_info_handle_basic_t process_info;
tu_handle_get_basic_info(inferior, &process_info);
unittest_printf("Inferior pid = %llu\n", (long long)process_info.koid);
// |inferior| is given to the child by launchpad_go.
// We need our own copy, and launchpad_go will give us one, but we need
// it before we call launchpad_go in order to attach to the debugging
// exception port. We could leave this to our caller to do, but since every
// caller needs this for convenience sake we do this here.
status = zx_handle_duplicate(inferior, ZX_RIGHT_SAME_RIGHTS, &inferior);
ASSERT_EQ(status, ZX_OK, "zx_handle_duplicate failed");
*out_lp = lp;
*out_inferior = inferior;
*out_channel = channel1;
END_HELPER;
}
// While this should perhaps take a launchpad_t* argument instead of the
// inferior's handle, we later want to test attaching to an already running
// inferior.
// |max_threads| is the maximum number of threads the process is expected
// to have in its lifetime. A real debugger would be more flexible of course.
// N.B. |inferior| cannot be the result of launchpad_get_process_handle().
// That handle is passed to the inferior when started and thus is lost to us.
// Returns a boolean indicating success.
inferior_data_t* attach_inferior(zx_handle_t inferior, zx_handle_t eport, size_t max_threads) {
// Fetch all current threads and attach async-waiters to them.
// N.B. We assume threads aren't being created as we're running.
// This is just a testcase so we can assume that. A real debugger
// would not have this assumption.
zx_koid_t* thread_koids = static_cast<zx_koid_t*>(tu_malloc(max_threads * sizeof(zx_koid_t)));
size_t num_threads = tu_process_get_threads(inferior, thread_koids, max_threads);
// For now require |max_threads| to be big enough.
if (num_threads > max_threads)
tu_fatal(__func__, ZX_ERR_BUFFER_TOO_SMALL);
tu_set_exception_port(inferior, eport, exception_port_key, ZX_EXCEPTION_PORT_DEBUGGER);
tu_object_wait_async(inferior, eport, ZX_PROCESS_TERMINATED);
inferior_data_t* data = static_cast<inferior_data_t*>(tu_malloc(sizeof(*data)));
data->threads = static_cast<thread_data_t*>(tu_calloc(max_threads, sizeof(data->threads[0])));
data->inferior = inferior;
data->eport = eport;
data->max_num_threads = max_threads;
// Notification of thread termination and suspension is delivered by
// signals. So that we can continue to only have to wait on |eport|
// for inferior status change notification, install async-waiters
// for each thread.
size_t j = 0;
zx_signals_t thread_signals = ZX_THREAD_TERMINATED | ZX_THREAD_RUNNING | ZX_THREAD_SUSPENDED;
for (size_t i = 0; i < num_threads; ++i) {
zx_handle_t thread = tu_process_get_thread(inferior, thread_koids[i]);
if (thread != ZX_HANDLE_INVALID) {
data->threads[j].tid = thread_koids[i];
data->threads[j].handle = thread;
tu_object_wait_async(thread, eport, thread_signals);
++j;
}
}
free(thread_koids);
unittest_printf("Attached to inferior\n");
return data;
}
void detach_inferior(inferior_data_t* data, bool unbind_eport) {
if (unbind_eport) {
tu_set_exception_port(data->inferior, ZX_HANDLE_INVALID, exception_port_key,
ZX_EXCEPTION_PORT_DEBUGGER);
}
for (size_t i = 0; i < data->max_num_threads; ++i) {
if (data->threads[i].handle != ZX_HANDLE_INVALID)
tu_handle_close(data->threads[i].handle);
}
free(data->threads);
free(data);
}
bool start_inferior(launchpad_t* lp) {
zx_handle_t dup_inferior = tu_launch_fdio_fini(lp);
unittest_printf("Inferior started\n");
// launchpad_go returns a dup of |inferior|. The original inferior
// handle is given to the child. However we don't need it, we already
// created one so that we could attach to the inferior before starting it.
tu_handle_close(dup_inferior);
return true;
}
bool verify_inferior_running(zx_handle_t channel) {
BEGIN_HELPER;
enum message msg;
send_msg(channel, MSG_PING);
if (!recv_msg(channel, &msg))
return false;
EXPECT_EQ(msg, MSG_PONG, "unexpected response from ping");
END_HELPER;
}
static bool recv_msg_handle(zx_handle_t channel, message expected_msg, zx_handle_t* handle) {
BEGIN_HELPER;
unittest_printf("waiting for message on channel %u\n", channel);
ASSERT_TRUE(tu_channel_wait_readable(channel), "peer closed while trying to read message");
uint64_t data;
uint32_t num_bytes = sizeof(data);
uint32_t num_handles = 1;
tu_channel_read(channel, 0, &data, &num_bytes, handle, &num_handles);
ASSERT_EQ(num_bytes, sizeof(data));
ASSERT_EQ(num_handles, 1u);
message msg = static_cast<message>(data);
ASSERT_EQ(msg, expected_msg);
unittest_printf("received handle %d\n", *handle);
END_HELPER;
}
bool get_inferior_thread_handle(zx_handle_t channel, zx_handle_t* thread) {
BEGIN_HELPER;
send_msg(channel, MSG_GET_THREAD_HANDLE);
ASSERT_TRUE(recv_msg_handle(channel, MSG_THREAD_HANDLE, thread));
END_HELPER;
}
bool resume_inferior(zx_handle_t inferior, zx_handle_t port, zx_koid_t tid) {
BEGIN_HELPER;
zx_handle_t thread;
zx_status_t status = zx_object_get_child(inferior, tid, ZX_RIGHT_SAME_RIGHTS, &thread);
if (status == ZX_ERR_NOT_FOUND) {
// If the process has exited then the kernel may have reaped the
// thread already. Check.
if (tu_process_has_exited(inferior))
return true;
}
ASSERT_EQ(status, ZX_OK, "zx_object_get_child failed");
unittest_printf("Resuming inferior ...\n");
status = zx_task_resume_from_exception(thread, port, 0);
if (status == ZX_ERR_BAD_STATE) {
// If the process has exited then the thread may have exited
// ExceptionHandlerExchange already. Check.
if (tu_thread_is_dying_or_dead(thread)) {
tu_handle_close(thread);
return true;
}
}
tu_handle_close(thread);
ASSERT_EQ(status, ZX_OK, "zx_task_resume_from_exception failed");
END_HELPER;
}
bool shutdown_inferior(zx_handle_t channel, zx_handle_t inferior) {
BEGIN_HELPER;
unittest_printf("Shutting down inferior\n");
send_msg(channel, MSG_DONE);
tu_process_wait_signaled(inferior);
EXPECT_EQ(tu_process_get_return_code(inferior), 1234, "unexpected inferior return code");
END_HELPER;
}
// Wait for and read an exception/signal on |eport|.
bool read_exception(zx_handle_t eport, zx_port_packet_t* packet) {
BEGIN_HELPER;
unittest_printf("Waiting for exception/signal on eport %d\n", eport);
ASSERT_EQ(zx_port_wait(eport, ZX_TIME_INFINITE, packet), ZX_OK, "zx_port_wait failed");
if (ZX_PKT_IS_EXCEPTION(packet->type))
ASSERT_EQ(packet->key, exception_port_key);
unittest_printf("read_exception: got exception/signal %d\n", packet->type);
END_HELPER;
}
// Wait for the thread to suspend
// We could get a thread exit report from a previous test, so
// we need to handle that, but no other exceptions are expected.
//
// The thread is assumed to be wait-async'd on |eport|. While we could just
// wait on the |thread| for the appropriate signal, the signal will also be
// sent to |eport| which our caller would then have to deal with. Keep things
// simpler by doing all waiting via |eport|. It also makes us exercise doing
// things this way, which is generally what debuggers will do.
bool wait_thread_suspended(zx_handle_t proc, zx_handle_t thread, zx_handle_t eport) {
BEGIN_HELPER;
zx_koid_t tid = tu_get_koid(thread);
while (true) {
zx_port_packet_t packet;
zx_status_t status = zx_port_wait(eport, zx_deadline_after(ZX_SEC(1)), &packet);
if (status == ZX_ERR_TIMED_OUT) {
// This shouldn't really happen unless the system is really loaded.
// Just flag it and try again. The watchdog will catch failures.
unittest_printf("%s: timed out???\n", __func__);
continue;
}
ASSERT_EQ(status, ZX_OK);
if (ZX_PKT_IS_SIGNAL_REP(packet.type)) {
ASSERT_EQ(packet.key, tid);
if (packet.signal.observed & ZX_THREAD_SUSPENDED)
break;
ASSERT_TRUE(packet.signal.observed & ZX_THREAD_RUNNING);
} else {
ASSERT_TRUE(ZX_PKT_IS_EXCEPTION(packet.type));
zx_koid_t report_tid = packet.exception.tid;
ASSERT_NE(report_tid, tid);
ASSERT_EQ(packet.type, (uint32_t)ZX_EXCP_THREAD_EXITING);
// Note the thread may be completely gone by now.
zx_handle_t other_thread;
zx_status_t status =
zx_object_get_child(proc, report_tid, ZX_RIGHT_SAME_RIGHTS, &other_thread);
if (status == ZX_OK) {
// And even if it's not gone it may be dead now.
status = zx_task_resume_from_exception(other_thread, eport, 0);
if (status == ZX_ERR_BAD_STATE)
ASSERT_TRUE(tu_thread_is_dying_or_dead(other_thread));
else
ASSERT_EQ(status, ZX_OK);
tu_handle_close(other_thread);
}
}
}
// Verify thread is suspended
zx_info_thread_t info = tu_thread_get_info(thread);
ASSERT_EQ(info.state, ZX_THREAD_STATE_SUSPENDED);
ASSERT_EQ(info.wait_exception_port_type, ZX_EXCEPTION_PORT_TYPE_NONE);
END_HELPER;
}
static int phdr_info_callback(dl_phdr_info* info, size_t size, void* data) {
dl_phdr_info* key = static_cast<dl_phdr_info*>(data);
if (info->dlpi_addr == key->dlpi_addr) {
*key = *info;
return 1;
}
return 0;
}
// Fetch the [inclusive] range of the executable segment of the vdso.
bool get_vdso_exec_range(uintptr_t* start, uintptr_t* end) {
BEGIN_HELPER;
char msg[128];
uintptr_t prop_vdso_base;
zx_status_t status =
zx_object_get_property(zx_process_self(), ZX_PROP_PROCESS_VDSO_BASE_ADDRESS,
&prop_vdso_base, sizeof(prop_vdso_base));
snprintf(msg, sizeof(msg), "zx_object_get_property failed: %d", status);
ASSERT_EQ(status, 0, msg);
dl_phdr_info info;
info.dlpi_addr = prop_vdso_base;
int ret = dl_iterate_phdr(&phdr_info_callback, &info);
ASSERT_EQ(ret, 1, "dl_iterate_phdr didn't see vDSO?");
uintptr_t vdso_code_start = 0;
size_t vdso_code_len = 0;
for (unsigned i = 0; i < info.dlpi_phnum; ++i) {
if (info.dlpi_phdr[i].p_type == PT_LOAD && (info.dlpi_phdr[i].p_flags & PF_X)) {
vdso_code_start = info.dlpi_addr + info.dlpi_phdr[i].p_vaddr;
vdso_code_len = info.dlpi_phdr[i].p_memsz;
break;
}
}
ASSERT_NE(vdso_code_start, 0u, "vDSO has no code segment?");
ASSERT_NE(vdso_code_len, 0u, "vDSO has no code segment?");
*start = vdso_code_start;
*end = vdso_code_start + vdso_code_len - 1;
END_HELPER;
}