blob: 1e75e42816201f78e8554e8f0bf537aa1282039e [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 "inferior-control.h"
#include <elf.h>
#include <inttypes.h>
#include <lib/fdio/fd.h>
#include <link.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <zircon/assert.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 <iterator>
#include <fbl/algorithm.h>
#include <test-utils/test-utils.h>
#include <zxtest/zxtest.h>
#include "inferior.h"
#include "utils.h"
void dump_gregs(zx_handle_t thread_handle, const zx_thread_state_general_regs_t* regs) {
printf("Registers for thread %#x:\n", thread_handle);
#define DUMP_NAMED_REG(name) \
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++) {
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);
#elif defined(__riscv)
DUMP_NAMED_REG(pc);
DUMP_NAMED_REG(ra);
DUMP_NAMED_REG(sp);
DUMP_NAMED_REG(gp);
DUMP_NAMED_REG(tp);
DUMP_NAMED_REG(t0);
DUMP_NAMED_REG(t1);
DUMP_NAMED_REG(t2);
DUMP_NAMED_REG(s0);
DUMP_NAMED_REG(s1);
DUMP_NAMED_REG(a0);
DUMP_NAMED_REG(a1);
DUMP_NAMED_REG(a2);
DUMP_NAMED_REG(a3);
DUMP_NAMED_REG(a4);
DUMP_NAMED_REG(a5);
DUMP_NAMED_REG(a6);
DUMP_NAMED_REG(a7);
DUMP_NAMED_REG(s2);
DUMP_NAMED_REG(s3);
DUMP_NAMED_REG(s4);
DUMP_NAMED_REG(s5);
DUMP_NAMED_REG(s6);
DUMP_NAMED_REG(s7);
DUMP_NAMED_REG(s8);
DUMP_NAMED_REG(s9);
DUMP_NAMED_REG(s10);
DUMP_NAMED_REG(s11);
DUMP_NAMED_REG(t3);
DUMP_NAMED_REG(t4);
DUMP_NAMED_REG(t5);
DUMP_NAMED_REG(t6);
#else
#error "what machine?"
#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 != ZX_OK) {
ZX_ASSERT(vaddr != 0);
printf("zx_process_read_memory failed at %#" PRIxPTR "\n", vaddr);
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;
}
void setup_inferior(const char* name, springboard_t** out_sb, zx_handle_t* out_inferior,
zx_handle_t* out_channel) {
zx_handle_t channel1, channel2;
ASSERT_EQ(zx_channel_create(0, &channel1, &channel2), ZX_OK);
const char* test_child_path = g_program_path;
const char* const argv[] = {test_child_path, name};
const char* const envp[] = {"LD_DEBUG=1"};
// Only pass stdout and stderr.
zx_handle_t handles[3];
uint32_t handle_ids[3];
ASSERT_EQ(fdio_fd_clone(STDOUT_FILENO, &handles[0]), ZX_OK);
handle_ids[0] = PA_HND(PA_FD, STDOUT_FILENO);
ASSERT_EQ(fdio_fd_clone(STDERR_FILENO, &handles[1]), ZX_OK);
handle_ids[1] = PA_HND(PA_FD, STDERR_FILENO);
handles[2] = channel2;
handle_ids[2] = PA_USER0;
printf("Creating process \"%s\"\n", name);
springboard_t* sb = tu_launch_init(zx_job_default(), name, std::size(argv), argv, std::size(envp),
envp, std::size(handles), handles, handle_ids);
// Note: |inferior| is a borrowed handle here.
zx_handle_t inferior = springboard_get_process_handle(sb);
ASSERT_NE(inferior, ZX_HANDLE_INVALID, "can't get process handle");
zx_info_handle_basic_t process_info;
zx_status_t status = zx_object_get_info(inferior, ZX_INFO_HANDLE_BASIC, &process_info,
sizeof(process_info), nullptr, nullptr);
ASSERT_EQ(status, ZX_OK);
printf("Inferior pid = %llu\n", (long long)process_info.koid);
*out_sb = sb;
*out_inferior = inferior;
*out_channel = channel1;
}
// While this should perhaps take a springboard_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.
inferior_data_t* attach_inferior(zx_handle_t inferior, zx_handle_t port, 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.
size_t buffer_size = max_threads * sizeof(zx_koid_t);
zx_koid_t* thread_koids = reinterpret_cast<zx_koid_t*>(malloc(buffer_size));
size_t num_threads;
zx_status_t status = zx_object_get_info(inferior, ZX_INFO_PROCESS_THREADS, thread_koids,
buffer_size, &num_threads, nullptr);
if (status != ZX_OK)
tu_fatal(__func__, status);
// For now require |max_threads| to be big enough.
if (num_threads > max_threads)
tu_fatal(__func__, ZX_ERR_BUFFER_TOO_SMALL);
tu_object_wait_async(inferior, port, ZX_PROCESS_TERMINATED);
inferior_data_t* data = reinterpret_cast<inferior_data_t*>(malloc(sizeof(*data)));
data->threads = reinterpret_cast<thread_data_t*>(calloc(max_threads, sizeof(data->threads[0])));
data->inferior = inferior;
data->port = port;
status = zx_task_create_exception_channel(inferior, ZX_EXCEPTION_CHANNEL_DEBUGGER,
&data->exception_channel);
ZX_ASSERT(status == ZX_OK);
data->max_num_threads = max_threads;
// We don't need to listen for ZX_CHANNEL_PEER_CLOSED here because
// ZX_PROCESS_TERMINATED already tells us when the process terminates.
tu_object_wait_async(data->exception_channel, port, ZX_CHANNEL_READABLE);
// Notification of thread termination and suspension is delivered by
// signals. So that we can continue to only have to wait on |port|
// 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;
zx_status_t status =
zx_object_get_child(inferior, thread_koids[i], ZX_RIGHT_SAME_RIGHTS, &thread);
if (status == ZX_ERR_NOT_FOUND) {
thread = ZX_HANDLE_INVALID;
} else {
ZX_ASSERT(status == ZX_OK);
}
if (thread != ZX_HANDLE_INVALID) {
data->threads[j].tid = thread_koids[i];
data->threads[j].handle = thread;
tu_object_wait_async(thread, port, thread_signals);
++j;
}
}
free(thread_koids);
printf("Attached to inferior\n");
return data;
}
void expect_debugger_attached_eq(zx_handle_t inferior, bool expected, const char* msg) {
zx_info_process_t info;
// ZX_ASSERT returns false if the check fails.
ASSERT_EQ(zx_object_get_info(inferior, ZX_INFO_PROCESS, &info, sizeof(info), NULL, NULL), ZX_OK);
ASSERT_EQ((info.flags & ZX_INFO_PROCESS_FLAG_DEBUGGER_ATTACHED) != 0, expected, "%s", msg);
}
void detach_inferior(inferior_data_t* data, bool close_exception_channel) {
if (close_exception_channel) {
unbind_inferior(data);
}
for (size_t i = 0; i < data->max_num_threads; ++i) {
if (data->threads[i].handle != ZX_HANDLE_INVALID)
zx_handle_close(data->threads[i].handle);
}
free(data->threads);
free(data);
}
void unbind_inferior(inferior_data_t* data) {
zx_handle_close(data->exception_channel);
data->exception_channel = ZX_HANDLE_INVALID;
}
bool start_inferior(springboard_t* sb) {
tu_launch_fini(sb);
printf("Inferior started\n");
return true;
}
void shutdown_inferior(zx_handle_t channel, zx_handle_t inferior) {
printf("Shutting down inferior\n");
send_simple_request(channel, RQST_DONE);
tu_process_wait_signaled(inferior);
EXPECT_EQ(tu_process_get_return_code(inferior), kInferiorReturnCode, "");
}
// Wait for and read a packet on |port|.
void read_packet(zx_handle_t port, zx_port_packet_t* packet) {
printf("read_packet: waiting for signal on port %d\n", port);
ASSERT_EQ(zx_port_wait(port, ZX_TIME_INFINITE, packet), ZX_OK, "zx_port_wait failed");
if (ZX_PKT_IS_SIGNAL_ONE(packet->type)) {
printf("read_packet: got signal, observed 0x%x\n", packet->signal.observed);
} else {
// Leave it to the caller to digest these.
printf("read_packet: got other packet %d\n", packet->type);
}
}
void wait_thread_state(zx_handle_t proc, zx_handle_t thread, zx_handle_t port,
zx_signals_t wait_until) {
zx_info_handle_basic_t basic_info;
zx_status_t status = zx_object_get_info(thread, ZX_INFO_HANDLE_BASIC, &basic_info,
sizeof(basic_info), nullptr, nullptr);
ASSERT_EQ(status, ZX_OK);
zx_koid_t tid = basic_info.koid;
// The input state we're looking for must be one of the signals we're waiting for. More signals
// can be added later if needed.
zx_signals_t signals = ZX_THREAD_TERMINATED | ZX_THREAD_RUNNING | ZX_THREAD_SUSPENDED;
ASSERT_TRUE(signals & wait_until);
tu_object_wait_async(thread, port, signals);
while (true) {
zx_port_packet_t packet;
status = zx_port_wait(port, 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.
printf("%s timed out waiting for thread state.\n", __func__);
continue;
}
ASSERT_EQ(status, ZX_OK);
if (packet.key == tid) {
if (packet.signal.observed & wait_until)
break;
tu_object_wait_async(thread, port, signals);
}
// No action necessary if the packet was an exit exception from a previous test, the channel has
// already been closed so we just needed to pop the packet out of the port.
}
zx_info_thread_t info;
status = zx_object_get_info(thread, ZX_INFO_THREAD, &info, sizeof(info), nullptr, nullptr);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(info.wait_exception_channel_type, ZX_EXCEPTION_CHANNEL_TYPE_NONE);
}
// N.B. This runs on the wait-inferior thread.
void handle_thread_exiting(zx_handle_t inferior, const zx_exception_info_t* info,
zx::exception exception) {
zx::thread thread;
ASSERT_EQ(exception.get_thread(&thread), ZX_OK);
zx_info_thread_t thread_info;
zx_status_t status =
thread.get_info(ZX_INFO_THREAD, &thread_info, sizeof(thread_info), nullptr, nullptr);
ASSERT_EQ(status, ZX_OK);
// The thread could still transition to DEAD here (if the
// process exits), so check for either DYING or DEAD.
EXPECT_TRUE(thread_info.state == ZX_THREAD_STATE_DYING ||
thread_info.state == ZX_THREAD_STATE_DEAD);
// If the state is DYING it would be nice to check that the
// value of |info.wait_exception_channel_type| is DEBUGGER. Alas
// if the process has exited then the thread will get
// THREAD_SIGNAL_KILL which will cause exception handling to exit
// before we've told the thread to "resume" from ZX_EXCP_THREAD_EXITING.
// The thread is still in the DYING state but it is no longer
// in an exception. Thus |info.wait_exception_channel_type| can
// either be DEBUGGER or NONE.
EXPECT_TRUE(thread_info.wait_exception_channel_type == ZX_EXCEPTION_CHANNEL_TYPE_NONE ||
thread_info.wait_exception_channel_type == ZX_EXCEPTION_CHANNEL_TYPE_DEBUGGER);
// A thread is gone, but we only care about the process.
printf("wait-inf: thread %" PRIu64 " exited\n", info->tid);
}
// A simpler exception handler.
// All exceptions are passed on to |handler|.
static void wait_inferior_thread_worker(inferior_data_t* inferior_data,
wait_inferior_exception_handler_t* handler,
void* handler_arg) {
zx_handle_t inferior = inferior_data->inferior;
zx_info_handle_basic_t basic_info;
zx_status_t status = zx_object_get_info(inferior, ZX_INFO_HANDLE_BASIC, &basic_info,
sizeof(basic_info), nullptr, nullptr);
ASSERT_EQ(status, ZX_OK);
zx_koid_t pid = basic_info.koid;
zx_handle_t port = inferior_data->port;
while (true) {
zx_port_packet_t packet;
ASSERT_NO_FATAL_FAILURE(read_packet(port, &packet));
// Is the inferior gone?
if (packet.key == pid) {
if (packet.signal.observed & ZX_PROCESS_TERMINATED) {
return;
}
tu_object_wait_async(inferior, port, ZX_PROCESS_TERMINATED);
} else {
status = zx_object_get_info(inferior_data->exception_channel, ZX_INFO_HANDLE_BASIC,
&basic_info, sizeof(basic_info), nullptr, nullptr);
ASSERT_EQ(status, ZX_OK);
if (packet.key != basic_info.koid) {
zx_signals_t thread_signals = ZX_THREAD_TERMINATED;
if (packet.signal.observed & ZX_THREAD_RUNNING)
thread_signals |= ZX_THREAD_SUSPENDED;
if (packet.signal.observed & ZX_THREAD_SUSPENDED)
thread_signals |= ZX_THREAD_RUNNING;
zx_handle_t thread;
zx_status_t status =
zx_object_get_child(inferior, packet.key, ZX_RIGHT_SAME_RIGHTS, &thread);
if (status == ZX_ERR_NOT_FOUND) {
thread = ZX_HANDLE_INVALID;
} else {
ZX_ASSERT(status == ZX_OK);
}
if (thread == ZX_HANDLE_INVALID) {
continue;
}
tu_object_wait_async(thread, port, thread_signals);
}
}
// If this call has zxtest assertion failures, don't return
// immediately but allow the wait below to happen first.
handler(inferior_data, &packet, handler_arg);
zx_info_handle_basic_t basic_info;
zx_status_t status = zx_object_get_info(inferior_data->exception_channel, ZX_INFO_HANDLE_BASIC,
&basic_info, sizeof(basic_info), nullptr, nullptr);
ASSERT_EQ(status, ZX_OK);
if (packet.key == basic_info.koid) {
// Don't re-wait on READABLE until after handler() has read the
// exception out of the channel or it will trigger again
// immediately.
//
// We don't care about PEER_CLOSED here because we're already
// listening for PROCESS_TERMINATED which gives the same info.
tu_object_wait_async(inferior_data->exception_channel, port, ZX_CHANNEL_READABLE);
}
// Check whether the handler() call above had any zxtest assertion
// failures.
ASSERT_FALSE(CURRENT_TEST_HAS_FATAL_FAILURE());
}
}
struct wait_inferior_args_t {
inferior_data_t* inferior_data;
wait_inferior_exception_handler_t* handler;
void* handler_arg;
};
static int wait_inferior_thread_func(void* arg) {
wait_inferior_args_t* args = reinterpret_cast<wait_inferior_args_t*>(arg);
inferior_data_t* inferior_data = args->inferior_data;
wait_inferior_exception_handler_t* handler = args->handler;
void* handler_arg = args->handler_arg;
free(args);
wait_inferior_thread_worker(inferior_data, handler, handler_arg);
return CURRENT_TEST_HAS_FATAL_FAILURE() ? -1 : 0;
}
thrd_t start_wait_inf_thread(inferior_data_t* inferior_data,
wait_inferior_exception_handler_t* handler, void* handler_arg) {
wait_inferior_args_t* args = reinterpret_cast<wait_inferior_args_t*>(calloc(1, sizeof(*args)));
// The proc handle is loaned to the thread.
// The caller of this function owns and must close it.
args->inferior_data = inferior_data;
args->handler = handler;
args->handler_arg = handler_arg;
thrd_t wait_inferior_thread;
int ret = thrd_create_with_name(&wait_inferior_thread, wait_inferior_thread_func, args,
"wait-inf thread");
ZX_DEBUG_ASSERT(ret == thrd_success);
return wait_inferior_thread;
}
void join_wait_inf_thread(thrd_t wait_inf_thread) {
printf("Waiting for wait-inf thread\n");
int thread_rc;
int ret = thrd_join(wait_inf_thread, &thread_rc);
EXPECT_EQ(ret, thrd_success, "thrd_join failed");
EXPECT_EQ(thread_rc, 0, "unexpected wait-inf return");
printf("wait-inf thread done\n");
}