| // 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 <link.h> |
| #include <zircon/status.h> |
| #include <zircon/syscalls/port.h> |
| |
| #include <test-utils/test-utils.h> |
| #include <unittest/unittest.h> |
| |
| #include "inferior-control.h" |
| #include "inferior.h" |
| #include "utils.h" |
| |
| namespace { |
| |
| // A ZX_EXCP_SW_BREAKPOINT requires some registers tune-up in order to be handled correctly |
| // depending on the architecture. This functions takes care of the correct setup of the program |
| // counter so that the exception can be resumed successfully. |
| zx_status_t cleanup_breakpoint(zx_handle_t thread) { |
| #if defined(__x86_64__) |
| // On x86, the pc is left at one past the s/w break insn, |
| // so there's nothing more we need to do. |
| return ZX_OK; |
| #elif defined(__aarch64__) |
| // Skip past the brk instruction. |
| zx_thread_state_general_regs_t regs = {}; |
| zx_status_t status = |
| zx_thread_read_state(thread, ZX_THREAD_STATE_GENERAL_REGS, ®s, sizeof(regs)); |
| if (status != ZX_OK) |
| return status; |
| |
| regs.pc += 4; |
| return zx_thread_write_state(thread, ZX_THREAD_STATE_GENERAL_REGS, ®s, sizeof(regs)); |
| #else |
| return ZX_ERR_NOT_SUPPORTED; |
| #endif |
| } |
| |
| struct dyn_break_on_load_state_t { |
| zx_handle_t process_handle = ZX_HANDLE_INVALID; |
| int dyn_load_count = 0; |
| }; |
| |
| bool dyn_break_on_load_test_handler(inferior_data_t* data, const zx_port_packet_t* packet, |
| void* handler_arg) { |
| auto* test_state = reinterpret_cast<dyn_break_on_load_state_t*>(handler_arg); |
| BEGIN_HELPER; |
| |
| // This test is supposed to only get an exception and nothing else. |
| 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); |
| ASSERT_EQ(basic_info.koid, packet->key); |
| |
| 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_SW_BREAKPOINT: { |
| unittest_printf("Got ld.so breakpoint.\n"); |
| test_state->dyn_load_count++; |
| |
| // Get the debug break address |
| uintptr_t r_debug_address; |
| zx_status_t status = |
| zx_object_get_property(test_state->process_handle, ZX_PROP_PROCESS_DEBUG_ADDR, |
| &r_debug_address, sizeof(r_debug_address)); |
| ASSERT_EQ(status, ZX_OK); |
| |
| size_t actual = 0; |
| r_debug dl_debug = {}; |
| status = zx_process_read_memory(test_state->process_handle, r_debug_address, &dl_debug, |
| sizeof(dl_debug), &actual); |
| ASSERT_EQ(status, ZX_OK); |
| ASSERT_EQ(actual, sizeof(dl_debug)); |
| |
| // Get the registers. |
| zx::thread thread; |
| status = exception.get_thread(&thread); |
| ASSERT_EQ(status, ZX_OK); |
| |
| zx_thread_state_general_regs_t regs = {}; |
| read_inferior_gregs(thread.get(), ®s); |
| |
| uint64_t rip = 0; |
| #if defined(__x86_64__) |
| // x64 will report the exception address after execution the software breakpoint instruction. |
| rip = regs.rip - 1; |
| #elif defined(__aarch64__) |
| rip = regs.pc; |
| #endif |
| |
| // The breakpoint should be exactly the same as informed by the dynamic loader. |
| ASSERT_EQ(rip, dl_debug.r_brk_on_load); |
| |
| ASSERT_EQ(cleanup_breakpoint(thread.get()), ZX_OK); |
| |
| break; |
| } |
| default: |
| unittest_printf("Unexpected exception %s (%u) on thread %lu\n", |
| tu_exception_to_string(info.type), info.type, info.tid); |
| break; |
| } |
| |
| uint32_t state = ZX_EXCEPTION_STATE_HANDLED; |
| status = exception.set_property(ZX_PROP_EXCEPTION_STATE, &state, sizeof(state)); |
| ASSERT_EQ(status, ZX_OK); |
| |
| END_HELPER; |
| } |
| |
| bool DynBreakOnLoadTest() { |
| BEGIN_TEST; |
| |
| springboard_t* sb; |
| zx_handle_t inferior, channel; |
| if (!setup_inferior(kTestDynBreakOnLoad, &sb, &inferior, &channel)) |
| return false; |
| |
| dyn_break_on_load_state_t test_state = {}; |
| test_state.process_handle = inferior; |
| |
| const uintptr_t kBreakOnLoad = 1; |
| zx_status_t status = zx_object_set_property(inferior, ZX_PROP_PROCESS_BREAK_ON_LOAD, |
| &kBreakOnLoad, sizeof(kBreakOnLoad)); |
| if (status != ZX_OK) { |
| fprintf(stderr, "Could not set dynamic linker break on load property: %s\n", |
| zx_status_get_string(status)); |
| ASSERT_EQ(status, ZX_OK); |
| } |
| |
| // 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 = 2; |
| inferior_data_t* inferior_data = attach_inferior(inferior, port, max_threads); |
| |
| thrd_t wait_inf_thread = |
| start_wait_inf_thread(inferior_data, dyn_break_on_load_test_handler, &test_state); |
| EXPECT_NE(port, ZX_HANDLE_INVALID); |
| |
| if (!start_inferior(sb)) |
| return false; |
| |
| // The remaining testing happens at this point as threads start. |
| // This testing is done in |dyn_break_on_load_test_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); |
| |
| // Verify how many loads there were. |
| ASSERT_EQ(test_state.dyn_load_count, 10); |
| |
| END_TEST; |
| } |
| |
| } // namespace |
| |
| BEGIN_TEST_CASE(dyn_break_on_load_tests) |
| RUN_TEST(DynBreakOnLoadTest); |
| END_TEST_CASE(dyn_break_on_load_tests) |