From: Hannes Domani Date: Mon, 26 Jan 2026 18:34:16 +0000 (+0100) Subject: gdbserver: Add aarch64-windows support X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8b601b78f71e6ae7b5aed7c0f8ac30d469f16325;p=thirdparty%2Fbinutils-gdb.git gdbserver: Add aarch64-windows support Approved-By: Tom Tromey --- diff --git a/gdbserver/configure.srv b/gdbserver/configure.srv index 7de03929bc2..7bdd92e3f82 100644 --- a/gdbserver/configure.srv +++ b/gdbserver/configure.srv @@ -61,6 +61,12 @@ case "${gdbserver_host}" in ipa_obj="${ipa_obj} linux-aarch64-tdesc-ipa.o" ipa_obj="${ipa_obj} arch/aarch64-ipa.o" ;; + aarch64-*-mingw*) srv_regobj="" + srv_tgtobj="arch/aarch64.o nat/aarch64-hw-point.o" + srv_tgtobj="${srv_tgtobj} win32-low.o win32-aarch64-low.o" + srv_tgtobj="${srv_tgtobj} nat/windows-nat.o" + srv_mingw=yes + ;; aarch64*-*-netbsd*) srv_regobj="" srv_tgtobj="netbsd-low.o netbsd-aarch64-low.o fork-child.o" srv_tgtobj="${srv_tgtobj} nat/fork-inferior.o" diff --git a/gdbserver/win32-aarch64-low.cc b/gdbserver/win32-aarch64-low.cc new file mode 100644 index 00000000000..ac8c117d6e7 --- /dev/null +++ b/gdbserver/win32-aarch64-low.cc @@ -0,0 +1,455 @@ +/* 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 . */ + +#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 (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 +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::full + | WindowsContext::floating + | WindowsContext::debug + | WindowsContext::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 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, +}; diff --git a/gdbserver/win32-low.cc b/gdbserver/win32-low.cc index ceee69db223..b05f6af6b56 100644 --- a/gdbserver/win32-low.cc +++ b/gdbserver/win32-low.cc @@ -909,6 +909,13 @@ fake_breakpoint_event (void) windows_process.current_event.u.Exception.ExceptionRecord.ExceptionCode = EXCEPTION_BREAKPOINT; + /* On aarch64, hardware breakpoints also get EXCEPTION_BREAKPOINT, + but they can be recognized with ExceptionInformation. */ + windows_process.current_event.u.Exception.ExceptionRecord.NumberParameters + = 1; + windows_process.current_event.u.Exception.ExceptionRecord + .ExceptionInformation[0] = 0; + for_each_thread (suspend_one_thread); }