| // Copyright 2019 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 <lib/zx/thread.h> |
| #include <stddef.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <zircon/syscalls/debug.h> |
| #include <zircon/syscalls/port.h> |
| #include <zircon/types.h> |
| |
| #include <atomic> |
| |
| #include <fbl/algorithm.h> |
| #include <pretty/hexdump.h> |
| #include <test-utils/test-utils.h> |
| #include <unittest/unittest.h> |
| |
| #include "debugger.h" |
| #include "inferior-control.h" |
| #include "inferior.h" |
| #include "utils.h" |
| |
| namespace { |
| |
| constexpr uint64_t kMagicRegisterValue = 0x0123456789abcdefull; |
| #define MAGIC_REGISTER_VALUE_ASM "0x0123456789abcdef" |
| |
| // State that is maintained across the register access tests. |
| |
| struct reg_access_test_state_t { |
| // The PC of the first thread can't be validated until we can get the |
| // inferior's libc load address. Save it here for later validation. |
| zx_vaddr_t inferior_libc_entry_point; |
| |
| // The load addresses of libc and executable are obtained from the |
| // inferior after it has started. |
| zx_vaddr_t inferior_libc_load_addr; |
| zx_vaddr_t inferior_exec_load_addr; |
| }; |
| |
| typedef void(raw_thread_func_t)(void* arg1, void* arg2); |
| |
| // Worker thread entry point so that we can exercise the setting of register |
| // values. We want to grab the register values at the start of the thread to |
| // see if they were set correctly, but we can't (or at least shouldn't) make |
| // any assumptions about what libc's thread entry will do to them before we're |
| // able to see them. It's defined in pure assembly so that there are no |
| // issues with compiler-generated code's assumptions about the proper ABI |
| // setup, instrumentation, etc. |
| extern "C" [[noreturn]] void raw_capture_regs_thread_func(void* arg1, void* arg2, |
| raw_thread_func_t* func, |
| uint64_t magic_value); |
| __asm__( |
| ".pushsection .text.raw_capture_regs_thread_func,\"ax\",%progbits\n" |
| ".balign 4\n" |
| ".type raw_capture_regs_thread_func,%function\n" |
| "raw_capture_regs_thread_func:\n" |
| #ifdef __aarch64__ |
| " mov x8, #(" MAGIC_REGISTER_VALUE_ASM |
| " & 0xffff)\n" |
| " movk x8, #((" MAGIC_REGISTER_VALUE_ASM |
| " >> 16) & 0xffff), lsl #16\n" |
| " movk x8, #((" MAGIC_REGISTER_VALUE_ASM |
| " >> 32) & 0xffff), lsl #32\n" |
| " movk x8, #((" MAGIC_REGISTER_VALUE_ASM |
| " >> 48) & 0xffff), lsl #48\n" |
| " cmp x3, x8\n" |
| " bne 0f\n" |
| " br x2\n" |
| "0:brk #0\n" |
| #elif defined(__x86_64__) |
| " movabs $" MAGIC_REGISTER_VALUE_ASM |
| ", %rax\n" |
| " cmpq %rax, %rcx\n" |
| " jne 0f\n" |
| " jmp *%rdx\n" |
| "0:ud2\n" |
| #else |
| #error "what machine?" |
| #endif |
| ".size raw_capture_regs_thread_func, . - raw_capture_regs_thread_func\n" |
| ".popsection"); |
| |
| // Helper function to test register access when a thread starts. |
| |
| bool test_thread_start_register_access(reg_access_test_state_t* test_state, zx_handle_t inferior, |
| zx_koid_t tid) { |
| BEGIN_HELPER; |
| |
| zx::thread thread; |
| zx_status_t status = |
| zx_object_get_child(inferior, tid, ZX_RIGHT_SAME_RIGHTS, thread.reset_and_get_address()); |
| if (status == ZX_ERR_NOT_FOUND) { |
| thread.reset(); |
| } else { |
| ASSERT_EQ(status, ZX_OK); |
| } |
| ASSERT_TRUE(thread.is_valid()); |
| |
| zx_info_thread_t info; |
| status = thread.get_info(ZX_INFO_THREAD, &info, sizeof(info), nullptr, nullptr); |
| ASSERT_EQ(status, ZX_OK); |
| EXPECT_EQ(info.state, ZX_THREAD_STATE_BLOCKED_EXCEPTION, ""); |
| |
| zx_thread_state_general_regs_t regs; |
| read_inferior_gregs(thread.get(), ®s); |
| uint64_t pc = extract_pc_reg(®s); |
| |
| // If we're the first thread the pc should be the ELF entry point. |
| // If not the pc should be the thread's entry point. |
| zx_koid_t threads[1 + kNumExtraThreads]; |
| size_t num_threads; |
| status = zx_object_get_info(inferior, ZX_INFO_PROCESS_THREADS, threads, sizeof(threads), |
| &num_threads, nullptr); |
| if (num_threads == 1) { |
| // We don't know the inferior's load address yet so we can't do a full |
| // validation of the PC yet. Save it for later when we can. |
| test_state->inferior_libc_entry_point = pc; |
| } |
| |
| // Verify the initial values of all the other general regs. |
| zx_thread_state_general_regs_t expected_regs{}; |
| |
| // We don't know what these are, but they're non-zero. |
| // The rest are generally zero. |
| #if defined(__x86_64__) |
| expected_regs.rip = regs.rip; |
| expected_regs.rsp = regs.rsp; |
| expected_regs.rdi = regs.rdi; |
| expected_regs.rsi = regs.rsi; |
| #elif defined(__aarch64__) |
| expected_regs.pc = regs.pc; |
| expected_regs.sp = regs.sp; |
| expected_regs.r[0] = regs.r[0]; |
| expected_regs.r[1] = regs.r[1]; |
| #endif |
| |
| // These values we know with certainty. |
| // See arch_setup_uspace_iframe(). |
| #if defined(__x86_64__) |
| |
| #define X86_FLAGS_IF (1 << 9) |
| #define X86_FLAGS_IOPL_SHIFT (12) |
| expected_regs.rflags = (0 << X86_FLAGS_IOPL_SHIFT) | X86_FLAGS_IF; |
| |
| #elif defined(__aarch64__) |
| |
| #define ARM64_CPSR_MASK_SERROR (1UL << 8) |
| // TODO(dje): See TODO in arch_setup_uspace_iframe. |
| // cpsr is read as 0x0 but it's set as 0x100; |
| expected_regs.cpsr = regs.cpsr & ARM64_CPSR_MASK_SERROR; |
| |
| #endif |
| |
| EXPECT_EQ(memcmp(®s, &expected_regs, sizeof(regs)), 0); |
| |
| if (utest_verbosity_level >= 2) { |
| printf("Got:\n"); |
| hexdump8_ex(®s, sizeof(regs), 0); |
| printf("Expected:\n"); |
| hexdump8_ex(&expected_regs, sizeof(expected_regs), 0); |
| } |
| |
| // If this is one of the extra threads, redirect its entry point and |
| // set additional registers for the thread to pick up. |
| if (num_threads > 1) { |
| EXPECT_NE(test_state->inferior_exec_load_addr, 0); |
| zx_vaddr_t our_exec_load_addr = get_exec_load_addr(); |
| zx_vaddr_t raw_thread_func_addr = reinterpret_cast<zx_vaddr_t>(&raw_capture_regs_thread_func); |
| raw_thread_func_addr -= our_exec_load_addr; |
| raw_thread_func_addr += test_state->inferior_exec_load_addr; |
| #if defined(__x86_64__) |
| regs.rdx = regs.rip; |
| regs.rip = raw_thread_func_addr; |
| regs.rcx = kMagicRegisterValue; |
| #elif defined(__aarch64__) |
| regs.r[2] = regs.pc; |
| regs.pc = raw_thread_func_addr; |
| regs.r[3] = kMagicRegisterValue; |
| #endif |
| } |
| |
| write_inferior_gregs(thread.get(), ®s); |
| |
| END_HELPER; |
| } |
| |
| // N.B. This runs on the wait-inferior thread. |
| |
| bool thread_start_test_exception_handler_worker(inferior_data_t* data, |
| const zx_port_packet_t* packet, void* handler_arg) { |
| BEGIN_HELPER; |
| |
| auto test_state = reinterpret_cast<reg_access_test_state_t*>(handler_arg); |
| |
| zx_info_handle_basic_t basic_info; |
| zx_status_t status = zx_object_get_info(data->exception_channel, ZX_INFO_HANDLE_BASIC, |
| &basic_info, sizeof(basic_info), nullptr, nullptr); |
| ASSERT_EQ(status, ZX_OK); |
| zx_koid_t exception_channel_koid = basic_info.koid; |
| |
| if (packet->key != exception_channel_koid) { |
| zx_object_get_info(data->inferior, ZX_INFO_HANDLE_BASIC, &basic_info, sizeof(basic_info), |
| nullptr, nullptr); |
| ASSERT_EQ(status, ZX_OK); |
| zx_koid_t inferior_koid = basic_info.koid; |
| ASSERT_TRUE(packet->key != inferior_koid); |
| // Must be a signal on one of the threads. |
| // Here we're only expecting TERMINATED. |
| ASSERT_TRUE(packet->signal.observed & ZX_THREAD_TERMINATED); |
| } else { |
| zx::exception exception; |
| zx_exception_info_t info; |
| uint32_t num_bytes = sizeof(info); |
| uint32_t num_handles = 1; |
| status = zx_channel_read(data->exception_channel, 0, &info, exception.reset_and_get_address(), |
| num_bytes, num_handles, nullptr, nullptr); |
| ASSERT_EQ(status, ZX_OK); |
| |
| switch (info.type) { |
| case ZX_EXCP_THREAD_STARTING: |
| unittest_printf("wait-inf: thread %lu started\n", info.tid); |
| EXPECT_TRUE(test_thread_start_register_access(test_state, data->inferior, info.tid)); |
| break; |
| |
| case ZX_EXCP_THREAD_EXITING: |
| EXPECT_TRUE(handle_thread_exiting(data->inferior, &info, std::move(exception))); |
| break; |
| |
| default: { |
| char msg[128]; |
| snprintf(msg, sizeof(msg), "unexpected exception type: 0x%x", info.type); |
| ASSERT_TRUE(false, msg); |
| __UNREACHABLE; |
| } |
| } |
| } |
| |
| END_HELPER; |
| } |
| |
| // N.B. This runs on the wait-inferior thread. |
| |
| bool thread_start_test_exception_handler(inferior_data_t* data, const zx_port_packet_t* packet, |
| void* handler_arg) { |
| bool pass = thread_start_test_exception_handler_worker(data, packet, handler_arg); |
| |
| // If a test failed detach now so that a thread isn't left waiting in |
| // ZX_EXCP_THREAD_STARTING for a response. |
| if (!pass) { |
| unbind_inferior(data); |
| } |
| |
| return pass; |
| } |
| |
| } // namespace |
| |
| int capture_regs_thread_func(void* arg) { |
| auto thread_count_ptr = reinterpret_cast<std::atomic<int>*>(arg); |
| atomic_fetch_add(thread_count_ptr, 1); |
| unittest_printf("Extra thread started.\n"); |
| return 0; |
| } |
| |
| bool StoppedInThreadStartingRegAccessTest() { |
| BEGIN_TEST; |
| |
| springboard_t* sb; |
| zx_handle_t inferior, channel; |
| if (!setup_inferior(kTestInferiorChildName, &sb, &inferior, &channel)) |
| return false; |
| |
| // Attach to the inferior now because we want to see thread starting |
| // exceptions. |
| zx_handle_t port = ZX_HANDLE_INVALID; |
| EXPECT_EQ(zx_port_create(0, &port), ZX_OK); |
| size_t max_threads = 10; |
| inferior_data_t* inferior_data = attach_inferior(inferior, port, max_threads); |
| |
| // State we need to maintain across the handling of the various exceptions. |
| reg_access_test_state_t test_state{}; |
| |
| thrd_t wait_inf_thread = |
| start_wait_inf_thread(inferior_data, thread_start_test_exception_handler, &test_state); |
| EXPECT_NE(port, ZX_HANDLE_INVALID); |
| |
| if (!start_inferior(sb)) |
| return false; |
| |
| // The first test happens here as the main thread starts. |
| // This testing is done in |thread_start_test_exception_handler()|. |
| |
| // Make sure the program successfully started. |
| if (!verify_inferior_running(channel)) |
| return false; |
| |
| EXPECT_TRUE(get_inferior_load_addrs(channel, &test_state.inferior_libc_load_addr, |
| &test_state.inferior_exec_load_addr), |
| ""); |
| |
| // Now that we have the inferior's libc load address we can verify the |
| // executable's initial PC value (which is libc's entry point). |
| // The inferior executable is us, so we can compute its entry point by |
| // adding the offset of the entry point from our load address to the |
| // inferior's load address. |
| zx_vaddr_t expected_entry_point = test_state.inferior_libc_load_addr + get_libc_entry_point(); |
| EXPECT_EQ(test_state.inferior_libc_entry_point, expected_entry_point, ""); |
| |
| send_simple_request(channel, RQST_START_LOOPING_THREADS); |
| EXPECT_TRUE(recv_simple_response(channel, RESP_THREADS_STARTED), ""); |
| |
| // The remaining testing happens at this point as threads start. |
| // This testing is done in |thread_start_test_exception_handler()|. |
| |
| if (!shutdown_inferior(channel, inferior)) |
| return false; |
| |
| // Stop the waiter thread before closing the port that it's waiting on. |
| join_wait_inf_thread(wait_inf_thread); |
| |
| detach_inferior(inferior_data, true); |
| |
| zx_handle_close(port); |
| zx_handle_close(channel); |
| zx_handle_close(inferior); |
| |
| END_TEST; |
| } |
| |
| BEGIN_TEST_CASE(thread_start_tests) |
| RUN_TEST(StoppedInThreadStartingRegAccessTest) |
| END_TEST_CASE(thread_start_tests) |