| /* Copyright (C) 2025-2026 Free Software Foundation, Inc. |
| |
| This file is part of GDB. |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/>. */ |
| |
| #include "win32-low.h" |
| #include "arch/aarch64.h" |
| #include "nat/aarch64-hw-point.h" |
| #include "tdesc.h" |
| |
| using namespace windows_nat; |
| |
| static struct aarch64_debug_reg_state debug_reg_state; |
| |
| /* The inferior's target description. This is a global because the |
| Windows ports support neither bi-arch nor multi-process. */ |
| static const_target_desc_up aarch64_tdesc; |
| |
| static void |
| update_debug_registers (thread_info *thread) |
| { |
| auto th = static_cast<windows_thread_info *> (thread->target_data ()); |
| |
| /* The actual update is done later just before resuming the lwp, |
| we just mark that the registers need updating. */ |
| th->debug_registers_changed = true; |
| } |
| |
| void |
| aarch64_notify_debug_reg_change (ptid_t ptid, |
| int is_watchpoint, unsigned int idx) |
| { |
| /* Only update the threads of this process. */ |
| current_process ()->for_each_thread (update_debug_registers); |
| } |
| |
| /* Breakpoint/watchpoint support. */ |
| |
| static int |
| aarch64_supports_z_point_type (char z_type) |
| { |
| switch (z_type) |
| { |
| case Z_PACKET_HW_BP: |
| case Z_PACKET_WRITE_WP: |
| case Z_PACKET_ACCESS_WP: |
| return 1; |
| default: |
| return 0; |
| } |
| } |
| |
| static int |
| aarch64_insert_point (enum raw_bkpt_type type, CORE_ADDR addr, |
| int len, struct raw_breakpoint *bp) |
| { |
| int ret; |
| enum target_hw_bp_type targ_type; |
| |
| /* Determine the type from the raw breakpoint type. */ |
| targ_type = raw_bkpt_type_to_target_hw_bp_type (type); |
| |
| if (targ_type != hw_execute) |
| { |
| if (aarch64_region_ok_for_watchpoint (addr, len)) |
| ret = aarch64_handle_watchpoint (targ_type, addr, len, |
| 1 /* is_insert */, current_thread->id, |
| &debug_reg_state); |
| else |
| ret = -1; |
| } |
| else |
| { |
| if (len == 3) |
| { |
| /* LEN is 3 means the breakpoint is set on a 32-bit thumb |
| instruction. Set it to 2 to correctly encode length bit |
| mask in hardware/watchpoint control register. */ |
| len = 2; |
| } |
| ret = aarch64_handle_breakpoint (targ_type, addr, len, |
| 1 /* is_insert */, current_thread->id, |
| &debug_reg_state); |
| } |
| |
| return ret; |
| } |
| |
| static int |
| aarch64_remove_point (enum raw_bkpt_type type, CORE_ADDR addr, |
| int len, struct raw_breakpoint *bp) |
| { |
| int ret; |
| enum target_hw_bp_type targ_type; |
| |
| /* Determine the type from the raw breakpoint type. */ |
| targ_type = raw_bkpt_type_to_target_hw_bp_type (type); |
| |
| /* Set up state pointers. */ |
| if (targ_type != hw_execute) |
| ret = |
| aarch64_handle_watchpoint (targ_type, addr, len, 0 /* is_insert */, |
| current_thread->id, &debug_reg_state); |
| else |
| { |
| if (len == 3) |
| { |
| /* LEN is 3 means the breakpoint is set on a 32-bit thumb |
| instruction. Set it to 2 to correctly encode length bit |
| mask in hardware/watchpoint control register. */ |
| len = 2; |
| } |
| ret = aarch64_handle_breakpoint (targ_type, addr, len, |
| 0 /* is_insert */, current_thread->id, |
| &debug_reg_state); |
| } |
| |
| return ret; |
| } |
| |
| static std::vector<CORE_ADDR> |
| aarch64_stopped_data_addresses () |
| { |
| if (windows_process.siginfo_er.ExceptionCode != EXCEPTION_BREAKPOINT || |
| windows_process.siginfo_er.NumberParameters != 2) |
| return {}; |
| |
| const CORE_ADDR addr_trap |
| = (CORE_ADDR) windows_process.siginfo_er.ExceptionInformation[1]; |
| |
| return aarch64_stopped_data_addresses (&debug_reg_state, addr_trap); |
| } |
| |
| static int |
| aarch64_stopped_by_watchpoint () |
| { |
| return !aarch64_stopped_data_addresses ().empty (); |
| } |
| |
| /* Implement win32_target_ops "initial_stuff" method. */ |
| |
| static void |
| aarch64_initial_stuff (process_info *proc) |
| { |
| proc->tdesc = aarch64_tdesc.get (); |
| |
| memset (&debug_reg_state, 0, sizeof (debug_reg_state)); |
| } |
| |
| /* Implement win32_target_ops "get_thread_context" method. */ |
| |
| static void |
| aarch64_get_thread_context (windows_thread_info *th) |
| { |
| CONTEXT *context = &th->context; |
| |
| context->ContextFlags = (WindowsContext<decltype(context)>::full |
| | WindowsContext<decltype(context)>::floating |
| | WindowsContext<decltype(context)>::debug |
| | WindowsContext<decltype(context)>::extended); |
| |
| BOOL ret = get_thread_context (th->h, context); |
| if (!ret) |
| { |
| DWORD e = GetLastError (); |
| error ("GetThreadContext failure %ld\n", (long) e); |
| } |
| } |
| |
| /* Implement win32_target_ops "prepare_to_resume" method. */ |
| |
| static void |
| aarch64_prepare_to_resume (windows_thread_info *th) |
| { |
| if (th->debug_registers_changed) |
| { |
| win32_require_context (th); |
| |
| CONTEXT *context = &th->context; |
| |
| for (int i = 0; i < aarch64_num_bp_regs; i++) |
| { |
| context->Bvr[i] = debug_reg_state.dr_addr_bp[i]; |
| context->Bcr[i] = debug_reg_state.dr_ctrl_bp[i]; |
| } |
| for (int i = 0; i < aarch64_num_wp_regs; i++) |
| { |
| context->Wvr[i] = debug_reg_state.dr_addr_wp[i]; |
| context->Wcr[i] = debug_reg_state.dr_ctrl_wp[i]; |
| } |
| |
| th->debug_registers_changed = false; |
| } |
| } |
| |
| /* Implement win32_target_ops "thread_added" method. */ |
| |
| static void |
| aarch64_thread_added (windows_thread_info *th) |
| { |
| th->debug_registers_changed = true; |
| } |
| |
| /* Implement win32_target_ops "single_step" method. */ |
| |
| static void |
| aarch64_single_step (windows_thread_info *th) |
| { |
| th->context.Cpsr |= 0x200000; |
| } |
| |
| /* An array of offset mappings into a Win32 Context structure. |
| This is a one-to-one mapping which is indexed by gdb's register |
| numbers. It retrieves an offset into the context structure where |
| the 4 byte register is located. |
| An offset value of -1 indicates that Win32 does not provide this |
| register in it's CONTEXT structure. In this case regptr will return |
| a pointer into a dummy register. */ |
| #define context_offset(x) (offsetof (CONTEXT, x)) |
| static const int aarch64_mappings[] = { |
| context_offset (X0), |
| context_offset (X1), |
| context_offset (X2), |
| context_offset (X3), |
| context_offset (X4), |
| context_offset (X5), |
| context_offset (X6), |
| context_offset (X7), |
| context_offset (X8), |
| context_offset (X9), |
| context_offset (X10), |
| context_offset (X11), |
| context_offset (X12), |
| context_offset (X13), |
| context_offset (X14), |
| context_offset (X15), |
| context_offset (X16), |
| context_offset (X17), |
| context_offset (X18), |
| context_offset (X19), |
| context_offset (X20), |
| context_offset (X21), |
| context_offset (X22), |
| context_offset (X23), |
| context_offset (X24), |
| context_offset (X25), |
| context_offset (X26), |
| context_offset (X27), |
| context_offset (X28), |
| context_offset (Fp), |
| context_offset (Lr), |
| context_offset (Sp), |
| context_offset (Pc), |
| context_offset (Cpsr), |
| context_offset (V[0]), |
| context_offset (V[1]), |
| context_offset (V[2]), |
| context_offset (V[3]), |
| context_offset (V[4]), |
| context_offset (V[5]), |
| context_offset (V[6]), |
| context_offset (V[7]), |
| context_offset (V[8]), |
| context_offset (V[9]), |
| context_offset (V[10]), |
| context_offset (V[11]), |
| context_offset (V[12]), |
| context_offset (V[13]), |
| context_offset (V[14]), |
| context_offset (V[15]), |
| context_offset (V[16]), |
| context_offset (V[17]), |
| context_offset (V[18]), |
| context_offset (V[19]), |
| context_offset (V[20]), |
| context_offset (V[21]), |
| context_offset (V[22]), |
| context_offset (V[23]), |
| context_offset (V[24]), |
| context_offset (V[25]), |
| context_offset (V[26]), |
| context_offset (V[27]), |
| context_offset (V[28]), |
| context_offset (V[29]), |
| context_offset (V[30]), |
| context_offset (V[31]), |
| context_offset (Fpsr), |
| context_offset (Fpcr), |
| }; |
| #undef context_offset |
| |
| static inline void |
| get_mappings (const int *&mappings, int &mappings_count) |
| { |
| mappings = aarch64_mappings; |
| mappings_count = sizeof (aarch64_mappings) / sizeof (aarch64_mappings[0]); |
| } |
| |
| /* Fetch register from gdbserver regcache data. */ |
| static void |
| aarch64_fetch_inferior_register (struct regcache *regcache, |
| windows_thread_info *th, int r) |
| { |
| const int *mappings; |
| int mappings_count; |
| get_mappings (mappings, mappings_count); |
| |
| char *context_ptr = (char *) &th->context; |
| char *context_offset; |
| if (r < mappings_count) |
| context_offset = context_ptr + mappings[r]; |
| else |
| gdb_assert_not_reached ("invalid register number %d", r); |
| |
| supply_register (regcache, r, context_offset); |
| } |
| |
| /* Store a new register value into the thread context of TH. */ |
| static void |
| aarch64_store_inferior_register (struct regcache *regcache, |
| windows_thread_info *th, int r) |
| { |
| const int *mappings; |
| int mappings_count; |
| get_mappings (mappings, mappings_count); |
| |
| char *context_ptr = (char *) &th->context; |
| char *context_offset; |
| if (r < mappings_count) |
| context_offset = context_ptr + mappings[r]; |
| else |
| gdb_assert_not_reached ("invalid register number %d", r); |
| |
| collect_register (regcache, r, context_offset); |
| } |
| |
| /* Windows uses the various BRK instruction variants for special operations, |
| and BRK #0xf000 triggers a breakpoint exception in the debugger. */ |
| static const unsigned char aarch64_breakpoint[] = {0x00, 0x00, 0x3e, 0xd4}; |
| #define aarch64_breakpoint_len 4 |
| |
| /* Implement win32_target_ops "arch_setup" method. */ |
| |
| static void |
| aarch64_arch_setup () |
| { |
| target_desc_up tdesc; |
| |
| /* Get ID_AA64DFR0_EL1 value (CP 4028) from registry. */ |
| aarch64_num_bp_regs = 0; |
| uint64_t cp4028; |
| DWORD cp4028_size = sizeof(cp4028); |
| if (RegGetValueA (HKEY_LOCAL_MACHINE, |
| "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", |
| "CP 4028", RRF_RT_REG_QWORD, NULL, &cp4028, &cp4028_size) |
| == ERROR_SUCCESS) |
| { |
| /* Bits 12-15 are the number of breakpoints, minus 1. */ |
| aarch64_num_bp_regs = ((cp4028 & 0xf000) >> 12) + 1; |
| if (aarch64_num_bp_regs > ARM64_MAX_BREAKPOINTS) |
| aarch64_num_bp_regs = ARM64_MAX_BREAKPOINTS; |
| } |
| |
| /* ARM64_MAX_WATCHPOINTS is 2, but only 1 works. */ |
| aarch64_num_wp_regs = 1; |
| |
| tdesc = aarch64_create_target_description ({}); |
| |
| std::vector<const char *> expedited_registers; |
| expedited_registers.push_back ("x29"); |
| expedited_registers.push_back ("sp"); |
| expedited_registers.push_back ("pc"); |
| expedited_registers.push_back (nullptr); |
| |
| init_target_desc (tdesc.get (), (const char **) expedited_registers.data (), |
| WINDOWS_OSABI); |
| aarch64_tdesc = std::move (tdesc); |
| } |
| |
| /* Implement win32_target_ops "num_regs" method. */ |
| |
| static int |
| aarch64_win32_num_regs () |
| { |
| int num_regs = sizeof (aarch64_mappings) / sizeof (aarch64_mappings[0]); |
| return num_regs; |
| } |
| |
| /* Implement win32_target_ops "get_pc" method. */ |
| |
| static CORE_ADDR |
| aarch64_win32_get_pc (struct regcache *regcache) |
| { |
| uint64_t pc; |
| |
| collect_register_by_name (regcache, "pc", &pc); |
| return (CORE_ADDR) pc; |
| } |
| |
| /* Implement win32_target_ops "set_pc" method. */ |
| |
| static void |
| aarch64_win32_set_pc (struct regcache *regcache, CORE_ADDR pc) |
| { |
| uint64_t newpc = pc; |
| |
| supply_register_by_name (regcache, "pc", &newpc); |
| } |
| |
| /* Implement win32_target_ops "is_sw_breakpoint" method. */ |
| |
| static bool |
| aarch64_is_sw_breakpoint (const EXCEPTION_RECORD *er) |
| { |
| /* On aarch64, hardware breakpoints also get EXCEPTION_BREAKPOINT, |
| but they can be recognized with ExceptionInformation. */ |
| return (er->ExceptionCode == EXCEPTION_BREAKPOINT |
| && er->NumberParameters == 1 |
| && er->ExceptionInformation[0] == 0); |
| } |
| |
| struct win32_target_ops the_low_target = { |
| aarch64_arch_setup, |
| aarch64_win32_num_regs, |
| aarch64_initial_stuff, |
| aarch64_get_thread_context, |
| aarch64_prepare_to_resume, |
| aarch64_thread_added, |
| aarch64_fetch_inferior_register, |
| aarch64_store_inferior_register, |
| aarch64_single_step, |
| aarch64_breakpoint, |
| aarch64_breakpoint_len, |
| aarch64_breakpoint_len, |
| aarch64_win32_get_pc, |
| aarch64_win32_set_pc, |
| aarch64_supports_z_point_type, |
| aarch64_insert_point, |
| aarch64_remove_point, |
| aarch64_stopped_by_watchpoint, |
| aarch64_stopped_data_addresses, |
| aarch64_is_sw_breakpoint, |
| }; |