blob: 7211c106200605a9c6a0014df7b7c8114adac1ed [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 <launchpad/launchpad.h>
#include <launchpad/vmo.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 <test-utils/test-utils.h>
#include <unittest/unittest.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, enum 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, enum 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 = data;
unittest_printf("received message %d\n", *msg);
END_HELPER;
}
typedef struct {
const char* name;
unsigned offset;
unsigned count;
unsigned size;
} regspec_t;
#ifdef __x86_64__
#define R(reg) { #reg, offsetof(zx_x86_64_general_regs_t, reg), 1, 64 }
static const regspec_t general_regs[] =
{
R(rax),
R(rbx),
R(rcx),
R(rdx),
R(rsi),
R(rdi),
R(rbp),
R(rsp),
R(r8),
R(r9),
R(r10),
R(r11),
R(r12),
R(r13),
R(r14),
R(r15),
R(rip),
R(rflags),
};
#undef R
#endif
#ifdef __aarch64__
#define R(reg) { #reg, offsetof(zx_arm64_general_regs_t, reg), 1, 64 }
static const regspec_t general_regs[] =
{
{ "r", offsetof(zx_arm64_general_regs_t, r), 30, 64 },
R(lr),
R(sp),
R(pc),
R(cpsr),
};
#undef R
#endif
void dump_gregs(zx_handle_t thread_handle, void* buf)
{
#if defined(__x86_64__) || defined(__aarch64__)
unittest_printf("Registers for thread %d\n", thread_handle);
for (unsigned i = 0; i < countof(general_regs); ++i) {
const regspec_t* r = &general_regs[i];
uint64_t val;
for (unsigned j = 0; j < r->count; ++j)
{
if (r->size == 32)
{
void* value_ptr = (char*) buf + r->offset + j * sizeof(uint32_t);
val = get_uint32(value_ptr);
}
else
{
void* value_ptr = (char*) buf + r->offset + j * sizeof(uint64_t);
val = get_uint64(value_ptr);
}
if (r->count == 1)
unittest_printf(" %8s %24ld 0x%lx\n", r->name, (long) val, (long) val);
else
unittest_printf(" %8s[%2u] %24ld 0x%lx\n", r->name, j, (long) val, (long) val);
}
}
#endif
}
void dump_arch_regs (zx_handle_t thread_handle, int regset, void* buf)
{
switch (regset)
{
case 0:
dump_gregs(thread_handle, buf);
break;
default:
break;
}
}
bool dump_inferior_regs(zx_handle_t thread)
{
BEGIN_HELPER;
zx_status_t status;
uint32_t num_regsets = get_uint32_property(thread, ZX_PROP_NUM_STATE_KINDS);
for (unsigned i = 0; i < num_regsets; ++i) {
uint32_t regset_size = 0;
status = zx_thread_read_state(thread, ZX_THREAD_STATE_REGSET0 + i, NULL, regset_size, &regset_size);
ASSERT_EQ(status, ZX_ERR_BUFFER_TOO_SMALL, "getting regset size failed");
void* buf = tu_malloc(regset_size);
status = zx_thread_read_state(thread, ZX_THREAD_STATE_REGSET0 + i, buf, regset_size, &regset_size);
// Regset reads can fail for legitimate reasons:
// ZX_ERR_NOT_SUPPORTED - the regset is not supported on this chip
// ZX_ERR_UNAVAILABLE - the regset may be currently unavailable
switch (status) {
case ZX_OK:
dump_arch_regs(thread, i, buf);
break;
case ZX_ERR_NOT_SUPPORTED:
unittest_printf("Regset %u not supported\n", i);
break;
case ZX_ERR_UNAVAILABLE:
unittest_printf("Regset %u unavailable\n", i);
break;
default:
ASSERT_EQ(status, ZX_OK, "getting regset failed");
}
free(buf);
}
END_HELPER;
}
uint32_t get_inferior_greg_buf_size(zx_handle_t thread)
{
// The general regs are defined to be in regset zero.
uint32_t regset_size = 0;
zx_status_t status = zx_thread_read_state(thread, ZX_THREAD_STATE_REGSET0, NULL, regset_size, &regset_size);
// It's easier to just terminate if this fails.
if (status != ZX_ERR_BUFFER_TOO_SMALL)
tu_fatal("get_inferior_greg_buf_size: zx_thread_read_state", status);
return regset_size;
}
// N.B. It is assumed |buf_size| is large enough.
void read_inferior_gregs(zx_handle_t thread, void* buf, unsigned buf_size)
{
// By convention the general regs are in regset 0.
zx_status_t status = zx_thread_read_state(thread, ZX_THREAD_STATE_REGSET0, buf, buf_size, &buf_size);
// 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 void* buf, unsigned buf_size)
{
// By convention the general regs are in regset 0.
zx_status_t status = zx_thread_write_state(thread, ZX_THREAD_STATE_REGSET0, buf, buf_size);
// It's easier to just terminate if this fails.
if (status != ZX_OK)
tu_fatal("write_inferior_gregs: zx_thread_write_state", status);
}
// This assumes |regno| is in an array of uint64_t values.
uint64_t get_uint64_register(zx_handle_t thread, size_t offset) {
unsigned greg_buf_size = get_inferior_greg_buf_size(thread);
char* buf = tu_malloc(greg_buf_size);
read_inferior_gregs(thread, buf, greg_buf_size);
uint64_t value = get_uint64(buf + offset);
free(buf);
return value;
}
// This assumes |regno| is in an array of uint64_t values.
void set_uint64_register(zx_handle_t thread, size_t offset, uint64_t value) {
unsigned greg_buf_size = get_inferior_greg_buf_size(thread);
char* buf = tu_malloc(greg_buf_size);
read_inferior_gregs(thread, buf, greg_buf_size);
set_uint64(buf + offset, value);
write_inferior_gregs(thread, buf, greg_buf_size);
free(buf);
}
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', '=', 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, countof(argv), argv, NULL,
countof(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.
zx_handle_t attach_inferior(zx_handle_t inferior)
{
zx_handle_t eport = tu_io_port_create();
tu_set_exception_port(inferior, eport, exception_port_key, ZX_EXCEPTION_PORT_DEBUGGER);
unittest_printf("Attached to inferior\n");
return eport;
}
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, enum 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, "");
enum message msg = 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_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(thread, ZX_RESUME_EXCEPTION);
tu_handle_close(thread);
if (status == ZX_ERR_BAD_STATE) {
// If the process has exited then the thread may have exited
// ExceptionHandlerExchange already. Check.
if (tu_process_has_exited(inferior))
return true;
}
ASSERT_EQ(status, ZX_OK, "zx_task_resume 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 receive an exception on |eport|.
bool read_exception(zx_handle_t eport, zx_handle_t inferior,
zx_port_packet_t* packet)
{
BEGIN_HELPER;
unittest_printf("Waiting for exception on eport %d\n", eport);
ASSERT_EQ(zx_port_wait(eport, ZX_TIME_INFINITE, packet, 0), ZX_OK, "zx_port_wait failed");
ASSERT_EQ(packet->key, exception_port_key, "bad report key");
unittest_printf("read_exception: got exception %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.
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, 0);
if (status == ZX_ERR_TIMED_OUT) {
// This shouldn't really happen unless the system is really loaded.
// Just flag it and try again.
unittest_printf("%s: timed out???\n", __func__);
continue;
}
ASSERT_EQ(status, ZX_OK, "");
ASSERT_EQ(packet.key, exception_port_key, "");
zx_koid_t report_tid = packet.exception.tid;
if (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) {
ASSERT_EQ(zx_task_resume(other_thread, ZX_RESUME_EXCEPTION), ZX_OK, "");
tu_handle_close(other_thread);
}
continue;
}
ASSERT_EQ(packet.type, (uint32_t) ZX_EXCP_THREAD_SUSPENDED, "");
break;
}
// 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(struct dl_phdr_info* info, size_t size,
void* data) {
struct dl_phdr_info* key = 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);
struct dl_phdr_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;
}