| // 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 <elf.h> |
| #include <inttypes.h> |
| #include <link.h> |
| #include <stdarg.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 "inferior.h" |
| #include "inferior-control.h" |
| #include "utils.h" |
| |
| constexpr uint64_t kExceptionPortKey = 0x6b6579; // "key" |
| |
| 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, ®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; |
| } |
| |
| // 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 = g_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 = reinterpret_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, kExceptionPortKey, ZX_EXCEPTION_PORT_DEBUGGER); |
| tu_object_wait_async(inferior, eport, ZX_PROCESS_TERMINATED); |
| |
| inferior_data_t* data = reinterpret_cast<inferior_data_t*>(tu_malloc(sizeof(*data))); |
| data->threads = reinterpret_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; |
| } |
| |
| bool expect_debugger_attached_eq(zx_handle_t inferior, bool expected, const char* msg) { |
| BEGIN_HELPER; |
| |
| 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.debugger_attached, expected, msg); |
| |
| END_HELPER; |
| } |
| |
| void detach_inferior(inferior_data_t* data, bool unbind_eport) { |
| if (unbind_eport) { |
| unbind_inferior(data->inferior); |
| } |
| 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); |
| } |
| |
| void unbind_inferior(zx_handle_t inferior) { |
| tu_set_exception_port(inferior, ZX_HANDLE_INVALID, kExceptionPortKey, |
| ZX_EXCEPTION_PORT_DEBUGGER); |
| } |
| |
| 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 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_simple_request(channel, RQST_DONE); |
| |
| tu_process_wait_signaled(inferior); |
| EXPECT_EQ(tu_process_get_return_code(inferior), kInferiorReturnCode, ""); |
| |
| END_HELPER; |
| } |
| |
| // Wait for and read an exception/signal on |eport|. |
| |
| bool read_packet(zx_handle_t eport, zx_port_packet_t* packet) { |
| BEGIN_HELPER; |
| |
| unittest_printf("read_packet: 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, kExceptionPortKey); |
| unittest_printf("read_packet: got exception 0x%x\n", packet->type); |
| } else if (ZX_PKT_IS_SIGNAL_ONE(packet->type)) { |
| unittest_printf("read_packet: got signal, observed 0x%x\n", |
| packet->signal.observed); |
| } else { |
| // Leave it to the caller to digest these. |
| unittest_printf("read_packet: got other packet %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); |
| |
| zx_signals_t signals = ZX_THREAD_TERMINATED | ZX_THREAD_RUNNING | ZX_THREAD_SUSPENDED; |
| tu_object_wait_async(thread, eport, signals); |
| |
| 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__); |
| tu_object_wait_async(thread, eport, signals); |
| continue; |
| } |
| ASSERT_EQ(status, ZX_OK); |
| if (ZX_PKT_IS_SIGNAL_ONE(packet.type)) { |
| ASSERT_EQ(packet.key, tid); |
| if (packet.signal.observed & ZX_THREAD_SUSPENDED) |
| break; |
| ASSERT_TRUE(packet.signal.observed & ZX_THREAD_RUNNING); |
| tu_object_wait_async(thread, eport, signals); |
| } 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; |
| } |
| |
| // This returns a bool as it's a unittest "helper" routine. |
| // N.B. This runs on the wait-inferior thread. |
| |
| bool handle_thread_exiting(zx_handle_t inferior, zx_handle_t port, const zx_port_packet_t* packet) { |
| BEGIN_HELPER; |
| |
| zx_koid_t tid = packet->exception.tid; |
| zx_handle_t thread; |
| zx_status_t status = zx_object_get_child(inferior, tid, ZX_RIGHT_SAME_RIGHTS, &thread); |
| // If the process has exited then the kernel may have reaped the |
| // thread already. Check. |
| if (status == ZX_OK) { |
| zx_info_thread_t info = tu_thread_get_info(thread); |
| // The thread could still transition to DEAD here (if the |
| // process exits), so check for either DYING or DEAD. |
| EXPECT_TRUE(info.state == ZX_THREAD_STATE_DYING || info.state == ZX_THREAD_STATE_DEAD); |
| // If the state is DYING it would be nice to check that the |
| // value of |info.wait_exception_port_type| is DEBUGGER. Alas |
| // if the process has exited then the thread will get |
| // THREAD_SIGNAL_KILL which will cause |
| // UserThread::ExceptionHandlerExchange 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_port_type| can |
| // either be DEBUGGER or NONE. |
| EXPECT_TRUE(info.wait_exception_port_type == ZX_EXCEPTION_PORT_TYPE_NONE || |
| info.wait_exception_port_type == ZX_EXCEPTION_PORT_TYPE_DEBUGGER); |
| tu_handle_close(thread); |
| } else { |
| EXPECT_EQ(status, ZX_ERR_NOT_FOUND); |
| EXPECT_TRUE(tu_process_has_exited(inferior)); |
| } |
| unittest_printf("wait-inf: thread %" PRIu64 " exited\n", tid); |
| // A thread is gone, but we only care about the process. |
| if (!resume_inferior(inferior, port, tid)) |
| return false; |
| |
| END_HELPER; |
| } |
| |
| // A simpler exception handler. |
| // All exceptions are passed on to |handler|. |
| // Returns false if a test fails. |
| // Otherwise waits for the inferior to exit and returns true. |
| |
| static bool 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_koid_t pid = tu_get_koid(inferior); |
| zx_handle_t eport = inferior_data->eport; |
| |
| while (true) { |
| zx_port_packet_t packet; |
| if (!read_packet(eport, &packet)) |
| return false; |
| |
| // Is the inferior gone? |
| if (ZX_PKT_IS_SIGNAL_ONE(packet.type)) { |
| if (packet.key == pid) { |
| if (packet.signal.observed & ZX_PROCESS_TERMINATED) { |
| return true; |
| } |
| tu_object_wait_async(inferior, eport, ZX_PROCESS_TERMINATED); |
| } else { |
| zx_signals_t thread_signals = ZX_THREAD_TERMINATED; |
| if (packet.signal.observed & ZX_THREAD_RUNNING) |
| thread_signals |= ZX_THREAD_SUSPENDED; |
| else if (packet.signal.observed & ZX_THREAD_SUSPENDED) |
| thread_signals |= ZX_THREAD_RUNNING; |
| zx_handle_t thread = tu_process_get_thread(inferior, packet.key); |
| if (thread == ZX_HANDLE_INVALID) { |
| continue; |
| } |
| tu_object_wait_async(thread, eport, thread_signals); |
| } |
| } |
| |
| if (!handler(inferior, eport, &packet, handler_arg)) |
| return false; |
| } |
| } |
| |
| 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); |
| |
| bool pass = wait_inferior_thread_worker(inferior_data, handler, handler_arg); |
| |
| return pass ? 0 : -1; |
| } |
| |
| 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*>(tu_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; |
| tu_thread_create_c11(&wait_inferior_thread, wait_inferior_thread_func, args, "wait-inf thread"); |
| return wait_inferior_thread; |
| } |
| |
| bool join_wait_inf_thread(thrd_t wait_inf_thread) { |
| BEGIN_HELPER; |
| |
| unittest_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"); |
| unittest_printf("wait-inf thread done\n"); |
| |
| END_HELPER; |
| } |