| // 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 "test_suite_helpers.h" | |
| #if defined(__x86_64__) | |
| #include <zircon/hw/debug/x86.h> | |
| #elif defined(__aarch64__) | |
| #include <zircon/hw/debug/arm64.h> | |
| #endif | |
| #include <lib/fdio/spawn.h> | |
| #include <lib/zx/eventpair.h> | |
| #include <unistd.h> | |
| #include <zircon/exception.h> | |
| #include <zircon/processargs.h> | |
| #include <zircon/status.h> | |
| #include <zircon/syscalls/port.h> | |
| #include <vector> | |
| constexpr uint64_t kPortKey = 0x11232141234; | |
| ThreadSetup::~ThreadSetup() { | |
| int res = -1; | |
| /* FX_DCHECK(thrd_join(c_thread, &res) == thrd_success); */ | |
| /* FX_DCHECK(res == 0) << res; */ | |
| thrd_join(c_thread, &res); | |
| } | |
| std::unique_ptr<ThreadSetup> CreateTestSetup(ThreadSetup::Function func, void* user) { | |
| auto setup = std::make_unique<ThreadSetup>(); | |
| setup->test_running = true; | |
| setup->user = user; | |
| CHECK_OK(zx::event::create(0, &setup->event)); | |
| thrd_create(&setup->c_thread, func, setup.get()); | |
| setup->thread.reset(thrd_get_zx_handle(setup->c_thread)); | |
| // We wait until the thread has indicated us we can continue. | |
| CHECK_OK(setup->event.wait_one(kThreadToHarness, zx::time::infinite(), nullptr)); | |
| return setup; | |
| } | |
| std::pair<zx::port, zx::channel> CreateExceptionChannel(const zx::thread& thread, bool debugger) { | |
| zx::port port; | |
| CHECK_OK(zx::port::create(0, &port)); | |
| zx::channel exception_channel; | |
| uint32_t flags = debugger ? ZX_EXCEPTION_CHANNEL_DEBUGGER : 0; | |
| CHECK_OK(thread.create_exception_channel(flags, &exception_channel)); | |
| return std::make_pair<zx::port, zx::channel>(std::move(port), std::move(exception_channel)); | |
| } | |
| zx_thread_state_general_regs_t ReadGeneralRegs(const zx::thread& thread) { | |
| zx_thread_state_general_regs_t regs = {}; | |
| CHECK_OK(thread.read_state(ZX_THREAD_STATE_GENERAL_REGS, ®s, sizeof(regs))); | |
| return regs; | |
| } | |
| void WriteGeneralRegs(const zx::thread& thread, const zx_thread_state_general_regs_t& regs) { | |
| CHECK_OK(thread.write_state(ZX_THREAD_STATE_GENERAL_REGS, ®s, sizeof(regs))); | |
| } | |
| zx_thread_state_debug_regs_t ReadDebugRegs(const zx::thread& thread) { | |
| zx_thread_state_debug_regs_t regs = {}; | |
| CHECK_OK(thread.read_state(ZX_THREAD_STATE_DEBUG_REGS, ®s, sizeof(regs))); | |
| return regs; | |
| } | |
| std::optional<zx_port_packet_t> WaitOnPort(const zx::port& port, zx_signals_t signals, | |
| zx::time deadline) { | |
| // Wait till we get the HW exception. | |
| zx_port_packet_t packet; | |
| zx_status_t status = port.wait(deadline, &packet); | |
| if (status == ZX_ERR_TIMED_OUT) | |
| return std::nullopt; | |
| CHECK_OK(status); | |
| FX_DCHECK(packet.key == kPortKey); | |
| FX_DCHECK(packet.type == ZX_PKT_TYPE_SIGNAL_ONE); | |
| FX_DCHECK((packet.signal.observed & signals) != 0); | |
| return packet; | |
| } | |
| Exception GetException(const zx::channel& exception_channel) { | |
| Exception exception = {}; | |
| // Obtain the exception. | |
| CHECK_OK(exception_channel.read(0, &exception.info, exception.handle.reset_and_get_address(), | |
| sizeof(exception.info), 1, nullptr, nullptr)); | |
| /* exception.handle.get_process(&exception.process); */ | |
| CHECK_OK(exception.handle.get_thread(&exception.thread)); | |
| exception.regs = ReadGeneralRegs(exception.thread); | |
| #if defined(__x86_64__) | |
| exception.pc = exception.regs.rip; | |
| #elif defined(__aarch64__) | |
| exception.pc = exception.regs.pc; | |
| #else | |
| #error Undefined arch. | |
| #endif | |
| return exception; | |
| } | |
| std::optional<Exception> WaitForException(const zx::port& port, | |
| const zx::channel& exception_channel, zx::time deadline) { | |
| auto packet = WaitOnPort(port, ZX_CHANNEL_READABLE, deadline); | |
| if (!packet) | |
| return std::nullopt; | |
| return GetException(exception_channel); | |
| } | |
| void ResumeException(const zx::thread& thread, Exception&& exception, bool handled) { | |
| if (handled) { | |
| uint32_t state = ZX_EXCEPTION_STATE_HANDLED; | |
| CHECK_OK(exception.handle.set_property(ZX_PROP_EXCEPTION_STATE, &state, sizeof(state))); | |
| } | |
| exception.handle.reset(); | |
| } | |
| void WaitAsyncOnExceptionChannel(const zx::port& port, const zx::channel& exception_channel) { | |
| // Listen on the exception channel for the thread. | |
| // Wait on the exception channel. | |
| CHECK_OK(exception_channel.wait_async(port, kPortKey, ZX_CHANNEL_READABLE, 0)); | |
| } | |
| bool IsOnException(const zx::thread& thread) { | |
| // Get the thread info. | |
| zx_info_thread_t thread_info; | |
| CHECK_OK(thread.get_info(ZX_INFO_THREAD, &thread_info, sizeof(thread_info), nullptr, nullptr)); | |
| return thread_info.state == ZX_THREAD_STATE_BLOCKED_EXCEPTION; | |
| } | |
| HWExceptionType DecodeHWException(const zx::thread& thread, const Exception& exception) { | |
| if (exception.info.type != ZX_EXCP_HW_BREAKPOINT) | |
| return HWExceptionType::kNone; | |
| #if defined(__x86_64__) | |
| // TODO: Implement x64 side logic for this. | |
| return HWExceptionType::kNone; | |
| #elif defined(__aarch64__) | |
| auto debug_regs = ReadDebugRegs(thread); | |
| // The ESR register holds information about the last exception in the form of: | |
| // |31 26|25|24 0| | |
| // | EC |IL| ISS | | |
| // | |
| // Where: | |
| // - EC: Exception class field (what exception occurred). | |
| // - IL: Instruction length (whether the trap was 16-bit of 32-bit instruction). | |
| // - ISS: Instruction Specific Syndrome. The value is specific to each EC. | |
| uint32_t ec = debug_regs.esr >> 26; | |
| switch (ec) { | |
| case 0b110000: /* HW breakpoint from a lower level */ | |
| case 0b110001: /* HW breakpoint from same level */ | |
| return HWExceptionType::kHardware; | |
| case 0b110010: /* software step from lower level */ | |
| case 0b110011: /* software step from same level */ | |
| return HWExceptionType::kSingleStep; | |
| case 0b110100: /* HW watchpoint from a lower level */ | |
| case 0b110101: /* HW watchpoint from same level */ | |
| return HWExceptionType::kWatchpoint; | |
| default: | |
| return HWExceptionType::kNone; | |
| } | |
| #else | |
| #error Undefined arch. | |
| #endif | |
| return HWExceptionType::kNone; | |
| } | |
| zx::suspend_token Suspend(const zx::thread& thread) { | |
| // Check if the thread is on an exception. | |
| if (IsOnException(thread)) | |
| return {}; | |
| // Suspend the thread. | |
| zx::suspend_token suspend_token; | |
| CHECK_OK(thread.suspend(&suspend_token)); | |
| CHECK_OK(thread.wait_one(ZX_THREAD_SUSPENDED, zx::time::infinite(), nullptr)); | |
| return suspend_token; | |
| } | |
| // Install HW Breakpoint --------------------------------------------------------------------------- | |
| namespace { | |
| #if defined(__x86_64__) | |
| zx_thread_state_debug_regs_t HWBreakpointRegs(uint64_t address) { | |
| if (address == 0) | |
| return {}; | |
| zx_thread_state_debug_regs_t debug_regs = {}; | |
| debug_regs.dr7 = 0b1; | |
| debug_regs.dr[0] = address; | |
| return debug_regs; | |
| } | |
| #elif defined(__aarch64__) | |
| zx_thread_state_debug_regs_t HWBreakpointRegs(uint64_t address) { | |
| if (address == 0) | |
| return {}; | |
| zx_thread_state_debug_regs_t debug_regs = {}; | |
| auto& hw_bp = debug_regs.hw_bps[0]; | |
| hw_bp.dbgbcr = 1; // Activate it. | |
| hw_bp.dbgbvr = address; | |
| return debug_regs; | |
| } | |
| #else | |
| #error Unsupported arch. | |
| #endif | |
| void SetHWBreakpoint(const zx::thread& thread, uint64_t address) { | |
| zx::suspend_token suspend_token = Suspend(thread); | |
| // Install the HW breakpoint. | |
| auto debug_regs = HWBreakpointRegs(address); | |
| CHECK_OK(thread.write_state(ZX_THREAD_STATE_DEBUG_REGS, &debug_regs, sizeof(debug_regs))); | |
| // Resume the thread. | |
| suspend_token.reset(); | |
| } | |
| } // namespace | |
| void InstallHWBreakpoint(const zx::thread& thread, uint64_t address) { | |
| SetHWBreakpoint(thread, address); | |
| } | |
| void RemoveHWBreakpoint(const zx::thread& thread) { SetHWBreakpoint(thread, 0); } | |
| // Watchpoint -------------------------------------------------------------------------------------- | |
| namespace { | |
| #if defined(__x86_64__) | |
| #define SET_REG(num, reg, len, address, type) \ | |
| { \ | |
| X86_DBG_CONTROL_L##num##_SET((reg), 1); \ | |
| X86_DBG_CONTROL_RW##num##_SET((reg), (type)); \ | |
| X86_DBG_CONTROL_LEN##num##_SET((reg), (len)); \ | |
| regs.dr[(num)] = (address); \ | |
| } | |
| #define BYTES_1 0 | |
| #define BYTES_2 1 | |
| #define BYTES_4 3 | |
| #define BYTES_8 2 | |
| zx_thread_state_debug_regs_t WatchpointRegs(uint64_t address, uint32_t length, | |
| WatchpointType type) { | |
| zx_thread_state_debug_regs_t regs = {}; | |
| if (address == 0) | |
| return {}; | |
| uint64_t align_mask = 0; | |
| switch (length) { | |
| case 1: | |
| break; | |
| case 2: | |
| align_mask = static_cast<uint64_t>(~0b1); | |
| break; | |
| case 4: | |
| align_mask = static_cast<uint64_t>(~0b11); | |
| break; | |
| case 8: | |
| align_mask = static_cast<uint64_t>(~0b111); | |
| break; | |
| default: | |
| FX_NOTREACHED() << "Invalid length: " << length; | |
| break; | |
| } | |
| uint32_t type_val = (type == WatchpointType::kWrite) ? 0b01 : 0b11; | |
| if (length == 1) { | |
| SET_REG(0, ®s.dr7, BYTES_1, address, type_val); | |
| } else if (length == 2) { | |
| uint64_t aligned_address = address & static_cast<uint64_t>(~0b1); | |
| uint64_t diff = address - aligned_address; | |
| if (!diff) { | |
| SET_REG(0, ®s.dr7, BYTES_2, address, type_val); | |
| } else { | |
| SET_REG(0, ®s.dr7, BYTES_1, address, type_val); | |
| SET_REG(1, ®s.dr7, BYTES_1, address + 1, type_val); | |
| } | |
| } else if (length == 4) { | |
| uint64_t aligned_address = address & static_cast<uint64_t>(~0b11); | |
| uint64_t diff = address - aligned_address; | |
| switch (diff) { | |
| case 0: | |
| SET_REG(0, ®s.dr7, BYTES_4, address, type_val); | |
| break; | |
| case 1: | |
| case 3: | |
| SET_REG(0, ®s.dr7, BYTES_1, address, type_val); | |
| SET_REG(1, ®s.dr7, BYTES_2, address + 1, type_val); | |
| SET_REG(2, ®s.dr7, BYTES_1, address + 3, type_val); | |
| break; | |
| case 2: | |
| SET_REG(0, ®s.dr7, BYTES_2, address, type_val); | |
| SET_REG(1, ®s.dr7, BYTES_2, address + 2, type_val); | |
| break; | |
| default: | |
| FX_NOTREACHED() << "Invalid diff: " << diff; | |
| break; | |
| } | |
| } else if (length == 8) { | |
| uint64_t aligned_address = address & static_cast<uint64_t>(~0b111); | |
| uint64_t diff = address - aligned_address; | |
| /* FX_LOGS(INFO) << "Diff: " << diff; */ | |
| switch (diff) { | |
| case 0: | |
| SET_REG(0, ®s.dr7, BYTES_8, address, type_val); | |
| break; | |
| case 1: | |
| case 5: | |
| SET_REG(0, ®s.dr7, BYTES_1, address, type_val); | |
| SET_REG(1, ®s.dr7, BYTES_2, address + 1, type_val); | |
| SET_REG(2, ®s.dr7, BYTES_4, address + 3, type_val); | |
| SET_REG(3, ®s.dr7, BYTES_1, address + 7, type_val); | |
| break; | |
| case 2: | |
| case 6: | |
| SET_REG(0, ®s.dr7, BYTES_2, address, type_val); | |
| SET_REG(1, ®s.dr7, BYTES_4, address + 2, type_val); | |
| SET_REG(2, ®s.dr7, BYTES_2, address + 6, type_val); | |
| break; | |
| case 3: | |
| case 7: | |
| SET_REG(0, ®s.dr7, BYTES_1, address, type_val); | |
| SET_REG(1, ®s.dr7, BYTES_4, address + 1, type_val); | |
| SET_REG(2, ®s.dr7, BYTES_2, address + 5, type_val); | |
| SET_REG(3, ®s.dr7, BYTES_1, address + 7, type_val); | |
| break; | |
| case 4: | |
| SET_REG(0, ®s.dr7, BYTES_4, address, type_val); | |
| SET_REG(1, ®s.dr7, BYTES_4, address + 4, type_val); | |
| break; | |
| default: | |
| FX_NOTREACHED() << "Invalid diff: " << diff; | |
| break; | |
| } | |
| } else { | |
| FX_NOTREACHED() << "Invalid length: " << length; | |
| } | |
| return regs; | |
| } | |
| void PrintDebugRegs(const zx_thread_state_debug_regs_t& debug_state) { | |
| // DR6 | |
| printf("DR6: 0x%lx -> B0=%lu, B1=%lu, B2=%lu, B3=%lu, BD=%lu, BS=%lu, BT=%lu\n", debug_state.dr6, | |
| X86_DBG_STATUS_B0_GET(debug_state.dr6), X86_DBG_STATUS_B1_GET(debug_state.dr6), | |
| X86_DBG_STATUS_B2_GET(debug_state.dr6), X86_DBG_STATUS_B3_GET(debug_state.dr6), | |
| X86_DBG_STATUS_BD_GET(debug_state.dr6), X86_DBG_STATUS_BS_GET(debug_state.dr6), | |
| X86_DBG_STATUS_BT_GET(debug_state.dr6)); | |
| // DR7 | |
| printf( | |
| "DR7: 0x%lx -> L0=%lu, G0=%lu, L1=%lu, G1=%lu, L2=%lu, G2=%lu, L3=%lu, G4=%lu, LE=%lu, " | |
| "GE=%lu, GD=%lu\n", | |
| debug_state.dr7, X86_DBG_CONTROL_L0_GET(debug_state.dr7), | |
| X86_DBG_CONTROL_G0_GET(debug_state.dr7), X86_DBG_CONTROL_L1_GET(debug_state.dr7), | |
| X86_DBG_CONTROL_G1_GET(debug_state.dr7), X86_DBG_CONTROL_L2_GET(debug_state.dr7), | |
| X86_DBG_CONTROL_G2_GET(debug_state.dr7), X86_DBG_CONTROL_L3_GET(debug_state.dr7), | |
| X86_DBG_CONTROL_G3_GET(debug_state.dr7), X86_DBG_CONTROL_LE_GET(debug_state.dr7), | |
| X86_DBG_CONTROL_GE_GET(debug_state.dr7), X86_DBG_CONTROL_GD_GET(debug_state.dr7)); | |
| printf("R/W0=%lu, LEN0=%lu, R/W1=%lu, LEN1=%lu, R/W2=%lu, LEN2=%lu, R/W3=%lu, LEN3=%lu\n", | |
| X86_DBG_CONTROL_RW0_GET(debug_state.dr7), X86_DBG_CONTROL_LEN0_GET(debug_state.dr7), | |
| X86_DBG_CONTROL_RW1_GET(debug_state.dr7), X86_DBG_CONTROL_LEN1_GET(debug_state.dr7), | |
| X86_DBG_CONTROL_RW2_GET(debug_state.dr7), X86_DBG_CONTROL_LEN2_GET(debug_state.dr7), | |
| X86_DBG_CONTROL_RW3_GET(debug_state.dr7), X86_DBG_CONTROL_LEN3_GET(debug_state.dr7)); | |
| } | |
| #elif defined(__aarch64__) | |
| zx_thread_state_debug_regs_t WatchpointRegs(uint64_t address, uint32_t length, | |
| WatchpointType type) { | |
| if (address == 0) | |
| return {}; | |
| zx_thread_state_debug_regs_t debug_regs = {}; | |
| auto* wp = debug_regs.hw_wps + 0; | |
| // The instruction has to be 4 byte aligned. | |
| uint64_t aligned_address = address & static_cast<uint64_t>(~0b111); | |
| uint64_t diff = address - aligned_address; | |
| FX_DCHECK(diff <= 7); | |
| // Set the BAS value. | |
| uint8_t bas = 0; | |
| uint8_t extra_bas = 0; | |
| for (uint32_t i = 0; i < length; i++) { | |
| uint32_t index = i + diff; | |
| // We cannot go the beyond the BAS boundary. | |
| if (index > 7) { | |
| extra_bas |= (1 << (index - 8)); | |
| continue; | |
| } | |
| bas |= (1 << index); | |
| } | |
| wp->dbgwvr = aligned_address; | |
| uint32_t lsc = (type == WatchpointType::kWrite) ? 0b10 : 0b11; | |
| ARM64_DBGWCR_E_SET(&wp->dbgwcr, 1); | |
| ARM64_DBGWCR_LSC_SET(&wp->dbgwcr, lsc); | |
| ARM64_DBGWCR_BAS_SET(&wp->dbgwcr, bas); | |
| if (extra_bas) { | |
| wp = debug_regs.hw_wps + 1; | |
| uint64_t extra_address = aligned_address + 8; | |
| wp->dbgwvr = extra_address; | |
| ARM64_DBGWCR_E_SET(&wp->dbgwcr, 1); | |
| ARM64_DBGWCR_LSC_SET(&wp->dbgwcr, lsc); | |
| ARM64_DBGWCR_BAS_SET(&wp->dbgwcr, extra_bas); | |
| } | |
| return debug_regs; | |
| } | |
| void PrintDebugRegs(const zx_thread_state_debug_regs_t& debug_state) { | |
| printf("HW breakpoints:\n"); | |
| for (uint32_t i = 0; i < ARM64_MAX_HW_BREAKPOINTS; i++) { | |
| uint32_t dbgbcr = debug_state.hw_bps[i].dbgbcr; | |
| uint64_t dbgbvr = debug_state.hw_bps[i].dbgbvr; | |
| if (!ARM64_DBGBCR_E_GET(dbgbcr)) | |
| continue; | |
| printf( | |
| "%02u. DBGBVR: 0x%lx, " | |
| "DBGBCR: E=%d, PMC=%d, BAS=%d, HMC=%d, SSC=%d, LBN=%d, BT=%d\n", | |
| i, dbgbvr, (int)(dbgbcr & ARM64_DBGBCR_E), | |
| (int)((dbgbcr & ARM64_DBGBCR_PMC_MASK) >> ARM64_DBGBCR_PMC_SHIFT), | |
| (int)((dbgbcr & ARM64_DBGBCR_BAS_MASK) >> ARM64_DBGBCR_BAS_SHIFT), | |
| (int)((dbgbcr & ARM64_DBGBCR_HMC_MASK) >> ARM64_DBGBCR_HMC_SHIFT), | |
| (int)((dbgbcr & ARM64_DBGBCR_SSC_MASK) >> ARM64_DBGBCR_SSC_SHIFT), | |
| (int)((dbgbcr & ARM64_DBGBCR_LBN_MASK) >> ARM64_DBGBCR_LBN_SHIFT), | |
| (int)((dbgbcr & ARM64_DBGBCR_BT_MASK) >> ARM64_DBGBCR_BT_SHIFT)); | |
| } | |
| printf("HW watchpoints:\n"); | |
| for (uint32_t i = 0; i < ARM64_MAX_HW_WATCHPOINTS; i++) { | |
| uint32_t dbgwcr = debug_state.hw_wps[i].dbgwcr; | |
| uint64_t dbgwvr = debug_state.hw_wps[i].dbgwvr; | |
| if (!ARM64_DBGWCR_E_GET(dbgwcr)) | |
| continue; | |
| printf( | |
| "%02u. DBGWVR: 0x%lx, DBGWCR: " | |
| "E=%d, PAC=%d, LSC=%d, BAS=0x%x, HMC=%d, SSC=%d, LBN=%d, WT=%d, MASK=0x%x\n", | |
| i, dbgwvr, (int)(dbgwcr & ARM64_DBGWCR_E_MASK), | |
| (int)((dbgwcr & ARM64_DBGWCR_PAC_MASK) >> ARM64_DBGWCR_PAC_SHIFT), | |
| (int)((dbgwcr & ARM64_DBGWCR_LSC_MASK) >> ARM64_DBGWCR_LSC_SHIFT), | |
| (unsigned int)((dbgwcr & ARM64_DBGWCR_BAS_MASK) >> ARM64_DBGWCR_BAS_SHIFT), | |
| (int)((dbgwcr & ARM64_DBGWCR_HMC_MASK) >> ARM64_DBGWCR_HMC_SHIFT), | |
| (int)((dbgwcr & ARM64_DBGWCR_SSC_MASK) >> ARM64_DBGWCR_SSC_SHIFT), | |
| (int)((dbgwcr & ARM64_DBGWCR_LBN_MASK) >> ARM64_DBGWCR_LBN_SHIFT), | |
| (int)((dbgwcr & ARM64_DBGWCR_WT_MASK) >> ARM64_DBGWCR_WT_SHIFT), | |
| (unsigned int)((dbgwcr & ARM64_DBGWCR_MSK_MASK) >> ARM64_DBGWCR_MSK_SHIFT)); | |
| } | |
| } | |
| #else | |
| #error Unsupported arch. | |
| #endif | |
| void SetWatchpoint(const zx::thread& thread, uint64_t address, uint32_t length, | |
| WatchpointType type) { | |
| zx::suspend_token suspend_token = Suspend(thread); | |
| // Install the HW breakpoint. | |
| auto debug_regs = WatchpointRegs(address, length, type); | |
| static bool a = false; | |
| if (a) { | |
| printf("-----------------------------------------------------------\n"); | |
| PrintDebugRegs(debug_regs); | |
| printf("-----------------------------------------------------------\n"); | |
| } | |
| CHECK_OK(thread.write_state(ZX_THREAD_STATE_DEBUG_REGS, &debug_regs, sizeof(debug_regs))); | |
| // Resume the thread. | |
| suspend_token.reset(); | |
| } | |
| } // namespace | |
| void InstallWatchpoint(const zx::thread& thread, uint64_t address, uint32_t length, | |
| WatchpointType type) { | |
| SetWatchpoint(thread, address, length, type); | |
| } | |
| void RemoveWatchpoint(const zx::thread& thread) { | |
| SetWatchpoint(thread, 0, 0, WatchpointType::kWrite); | |
| } | |
| std::optional<Exception> SingleStep(const zx::thread& thread, const zx::port& port, | |
| const zx::channel& exception_channel, | |
| std::optional<Exception> exception) { | |
| { | |
| zx_thread_state_single_step_t value = 1; | |
| zx::suspend_token suspend_token = Suspend(thread); | |
| CHECK_OK(thread.write_state(ZX_THREAD_STATE_SINGLE_STEP, &value, sizeof(value))); | |
| WaitAsyncOnExceptionChannel(port, exception_channel); | |
| ResumeException(thread, std::move(*exception)); | |
| } | |
| exception = WaitForException(port, exception_channel, | |
| zx::deadline_after(zx::msec(kExceptionWaitTimeout))); | |
| FX_DCHECK(exception.has_value()) << "No exception!"; | |
| FX_DCHECK(exception->info.type == ZX_EXCP_HW_BREAKPOINT) | |
| << "Got: " << zx_exception_get_string(exception->info.type); | |
| FX_DCHECK(DecodeHWException(thread, exception.value()) == HWExceptionType::kSingleStep); | |
| { | |
| zx_thread_state_single_step_t value = 0; | |
| zx::suspend_token suspend_token = Suspend(thread); | |
| CHECK_OK(thread.write_state(ZX_THREAD_STATE_SINGLE_STEP, &value, sizeof(value))); | |
| } | |
| return exception; | |
| } | |
| // Process Spawning -------------------------------------------------------------------------------- | |
| zx_status_t LaunchProcess(const zx::job& job, const std::string& name, | |
| const std::vector<std::string>& argv, Process* out) { | |
| // fdio_spawn_etc expects argv to be a nullptr-terminated array. | |
| std::vector<const char*> normalized_argv; | |
| normalized_argv.reserve(argv.size() + 1); | |
| for (const std::string& arg : argv) { | |
| normalized_argv.push_back(arg.c_str()); | |
| } | |
| normalized_argv.push_back(nullptr); | |
| zx::channel mine, theirs; | |
| CHECK_OK(zx::channel::create(0, &mine, &theirs)); | |
| // clang-format off | |
| fdio_spawn_action_t actions[] = { | |
| // Set the process name. | |
| {.action = FDIO_SPAWN_ACTION_SET_NAME, .name = {.data = name.c_str()}}, | |
| // Pass in a special channel handle that they can obtain via |zx_take_startup_handle|. | |
| // See |InitSubProcess|. | |
| {.action = FDIO_SPAWN_ACTION_ADD_HANDLE, .h = {.id = PA_HND(PA_USER0, 0), .handle = theirs.release()}}, | |
| // Clone stdout/err/in. | |
| {.action = FDIO_SPAWN_ACTION_CLONE_FD, .fd = {.local_fd = STDOUT_FILENO, .target_fd = STDOUT_FILENO}}, | |
| {.action = FDIO_SPAWN_ACTION_CLONE_FD, .fd = {.local_fd = STDIN_FILENO, .target_fd = STDIN_FILENO}}, | |
| {.action = FDIO_SPAWN_ACTION_CLONE_FD, .fd = {.local_fd = STDERR_FILENO, .target_fd = STDERR_FILENO}}, | |
| }; | |
| // clang-format on | |
| zx::process process; | |
| char err_msg[FDIO_SPAWN_ERR_MSG_MAX_LENGTH]; | |
| zx_status_t status = fdio_spawn_etc(job.get(), FDIO_SPAWN_CLONE_ALL, normalized_argv.front(), | |
| normalized_argv.data(), nullptr, std::size(actions), actions, | |
| process.reset_and_get_address(), err_msg); | |
| if (status != ZX_OK) | |
| return status; | |
| *out = {}; | |
| out->name = name; | |
| out->handle = std::move(process); | |
| out->comm_channel = std::move(mine); | |
| return ZX_OK; | |
| } | |
| zx_status_t InitSubProcess(zx::channel* out) { | |
| zx::channel channel{zx_take_startup_handle(PA_HND(PA_USER0, 0))}; | |
| if (!channel.is_valid()) | |
| return ZX_ERR_BAD_STATE; | |
| *out = std::move(channel); | |
| return ZX_OK; | |
| } | |
| zx_status_t WaitOnChannelReadable(const zx::channel& channel, zx::time deadline) { | |
| return channel.wait_one(ZX_FIFO_READABLE, zx::deadline_after(zx::sec(1)), nullptr); | |
| } | |
| zx_status_t SignalClient(const zx::eventpair& event) { | |
| CHECK_OK(event.signal(kClientToServer, 0)); | |
| return event.signal_peer(0, kServerToClient); | |
| } | |
| zx_status_t SignalServer(const zx::eventpair& event) { | |
| CHECK_OK(event.signal(kServerToClient, 0)); | |
| return event.signal_peer(0, kClientToServer); | |
| } | |
| zx_status_t WaitForClient(const zx::eventpair& event, zx::time deadline) { | |
| return event.wait_one(kClientToServer, deadline, 0); | |
| } | |
| zx_status_t WaitForServer(const zx::eventpair& event, zx::time deadline) { | |
| return event.wait_one(kServerToClient, deadline, 0); | |
| } |