| // 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 <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 %d\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); |
| |
| #endif |
| |
| #undef DUMP_NAMED_REG |
| } |
| |
| void dump_inferior_regs(zx_handle_t thread) { |
| zx_thread_state_general_regs_t regs; |
| read_inferior_gregs(thread, ®s); |
| dump_gregs(thread, ®s); |
| } |
| |
| // 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; |
| } |
| |
| 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}; |
| zx_handle_t handles[4]; |
| uint32_t handle_ids[4]; |
| for (int fd = 0; fd < 3; ++fd) { |
| ASSERT_EQ(fdio_fd_clone(fd, &handles[fd]), ZX_OK); |
| handle_ids[fd] = PA_HND(PA_FD, fd); |
| } |
| handles[3] = channel2; |
| handle_ids[3] = PA_USER0; |
| |
| printf("Creating process \"%s\"\n", name); |
| springboard_t* sb = tu_launch_init(zx_job_default(), name, std::size(argv), argv, 0, NULL, |
| 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"); |
| } |