[binutils-gdb] Add aarch64-windows support

Hannes Domani ssbssa@sourceware.org
Fri Jan 23 19:19:21 GMT 2026


https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;h=3e0e90ac1e407c5a23f8c823679004043e38848f

commit 3e0e90ac1e407c5a23f8c823679004043e38848f
Author: Hannes Domani <ssbssa@yahoo.de>
Date:   Fri Jan 23 20:07:04 2026 +0100

    Add aarch64-windows support
    
    This makes most debugging work, except unwinding doesn't always work
    from inside dlls where no debug info is available, because SEH unwinding
    is not implemented.
    
    The number of available hardware breakpoints is taken from
    ID_AA64DFR0_EL1 (registry key "CP 4028").
    As for hardware watchpoints, even though ARM64_MAX_WATCHPOINTS is 2,
    testing showed that only 1 ever works, so it's fixed to that value.
    
    Reviewed-By: Eli Zaretskii <eliz@gnu.org>
    Approved-By: Tom Tromey <tom@tromey.com>

Diff:
---
 gdb/Makefile.in            |   3 +
 gdb/NEWS                   |   2 +
 gdb/aarch64-windows-nat.c  | 333 +++++++++++++++++++++++++++++++++++++++++++++
 gdb/aarch64-windows-tdep.c |  70 ++++++++++
 gdb/coff-pe-read.c         |   6 +-
 gdb/configure.host         |   3 +
 gdb/configure.nat          |   4 +
 gdb/configure.tgt          |   5 +
 gdb/nat/windows-nat.c      |   5 +
 gdb/nat/windows-nat.h      |   2 +
 10 files changed, 431 insertions(+), 2 deletions(-)

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index b564048f9fa..fc76925344f 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -735,6 +735,7 @@ ALL_64_TARGET_OBS = \
 	aarch64-newlib-tdep.o \
 	aarch64-ravenscar-thread.o \
 	aarch64-tdep.o \
+	aarch64-windows-tdep.o \
 	alpha-bsd-tdep.o \
 	alpha-linux-tdep.o \
 	alpha-netbsd-tdep.o \
@@ -1784,6 +1785,8 @@ ALLDEPFILES = \
 	aarch64-newlib-tdep.c \
 	aarch64-ravenscar-thread.c \
 	aarch64-tdep.c \
+	aarch64-windows-nat.c \
+	aarch64-windows-tdep.c \
 	aix-thread.c \
 	alpha-bsd-nat.c \
 	alpha-bsd-tdep.c \
diff --git a/gdb/NEWS b/gdb/NEWS
index f50568e7f0c..fa6e7ca6121 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -70,6 +70,8 @@
 
 GNU/Linux/MicroBlaze (gdbserver) microblazeel-*linux*
 
+AArch64 MinGW                   aarch64-*-mingw*
+
 * New commands
 
 set local-environment
diff --git a/gdb/aarch64-windows-nat.c b/gdb/aarch64-windows-nat.c
new file mode 100644
index 00000000000..4ae2c758f15
--- /dev/null
+++ b/gdb/aarch64-windows-nat.c
@@ -0,0 +1,333 @@
+/* Native-dependent code for Windows AArch64.
+
+   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 "windows-nat.h"
+#include "regcache.h"
+#include "gdbarch.h"
+#include "inferior.h"
+
+#include "aarch64-nat.h"
+
+using namespace windows_nat;
+
+struct aarch64_windows_per_inferior : public windows_per_inferior
+{
+  aarch64_debug_reg_state dr_state;
+};
+
+struct aarch64_windows_nat_target final
+  : public aarch64_nat_target<windows_nat_target>
+{
+  std::vector<CORE_ADDR> stopped_data_addresses () override;
+  bool stopped_by_watchpoint () override;
+
+  void initialize_windows_arch (bool attaching) override;
+  void cleanup_windows_arch () override;
+
+  void fill_thread_context (windows_thread_info *th) override;
+
+  void thread_context_continue (windows_thread_info *th, int killed) override;
+  void thread_context_step (windows_thread_info *th) override;
+
+  void fetch_one_register (struct regcache *regcache,
+			   windows_thread_info *th, int r) override;
+  void store_one_register (const struct regcache *regcache,
+			   windows_thread_info *th, int r) override;
+
+  bool is_sw_breakpoint (const EXCEPTION_RECORD *er) const override;
+};
+
+/* The current process.  */
+static aarch64_windows_per_inferior aarch64_windows_process;
+
+#define context_offset(x) (offsetof (CONTEXT, x))
+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
+
+/* Implement the "stopped_data_addresses" target_ops method.  */
+
+std::vector<CORE_ADDR>
+aarch64_windows_nat_target::stopped_data_addresses ()
+{
+  if (aarch64_windows_process.siginfo_er.ExceptionCode != EXCEPTION_BREAKPOINT
+      || aarch64_windows_process.siginfo_er.NumberParameters != 2)
+    return {};
+
+  const CORE_ADDR addr_trap
+    = (CORE_ADDR) aarch64_windows_process.siginfo_er.ExceptionInformation[1];
+
+  struct aarch64_debug_reg_state *state
+    = aarch64_get_debug_reg_state (inferior_ptid.pid ());
+  return aarch64_stopped_data_addresses (state, addr_trap);
+}
+
+/* Implement the "stopped_by_watchpoint" target_ops method.  */
+
+bool
+aarch64_windows_nat_target::stopped_by_watchpoint ()
+{
+  return !stopped_data_addresses ().empty ();
+}
+
+/* See windows-nat.h.  */
+
+void
+aarch64_windows_nat_target::initialize_windows_arch (bool attaching)
+{
+  memset (&aarch64_windows_process.dr_state, 0,
+	  sizeof (aarch64_windows_process.dr_state));
+
+  aarch64_windows_process.mappings = aarch64_mappings;
+}
+
+/* See windows-nat.h.  */
+
+void
+aarch64_windows_nat_target::cleanup_windows_arch ()
+{
+  aarch64_remove_debug_reg_state (inferior_ptid.pid ());
+}
+
+/* See windows-nat.h.  */
+
+void
+aarch64_windows_nat_target::fill_thread_context (windows_thread_info *th)
+{
+  CONTEXT *context = &th->context;
+
+  context->ContextFlags = WindowsContext<decltype(context)>::all;
+  CHECK (get_thread_context (th->h, context));
+}
+
+/* See windows-nat.h.  */
+
+void
+aarch64_windows_nat_target::thread_context_continue (windows_thread_info *th,
+						     int killed)
+{
+  CONTEXT *context = &th->context;
+
+  if (th->debug_registers_changed)
+    {
+      context->ContextFlags |= WindowsContext<decltype(context)>::debug;
+      for (int i = 0; i < aarch64_num_bp_regs; i++)
+	{
+	  context->Bvr[i] = aarch64_windows_process.dr_state.dr_addr_bp[i];
+	  context->Bcr[i] = aarch64_windows_process.dr_state.dr_ctrl_bp[i];
+	}
+      for (int i = 0; i < aarch64_num_wp_regs; i++)
+	{
+	  context->Wvr[i] = aarch64_windows_process.dr_state.dr_addr_wp[i];
+	  context->Wcr[i] = aarch64_windows_process.dr_state.dr_ctrl_wp[i];
+	}
+      th->debug_registers_changed = false;
+    }
+
+  if (context->ContextFlags)
+    {
+      DWORD ec = 0;
+
+      if (GetExitCodeThread (th->h, &ec)
+	  && ec == STILL_ACTIVE)
+	{
+	  BOOL status = set_thread_context (th->h, context);
+
+	  if (!killed)
+	    CHECK (status);
+	}
+      context->ContextFlags = 0;
+    }
+}
+
+/* See windows-nat.h.  */
+
+void
+aarch64_windows_nat_target::thread_context_step (windows_thread_info *th)
+{
+  th->context.Cpsr |= 0x200000;
+}
+
+/* See windows-nat.h.  */
+
+void
+aarch64_windows_nat_target::fetch_one_register (struct regcache *regcache,
+						windows_thread_info *th, int r)
+{
+  gdb_assert (r >= 0);
+  gdb_assert (!th->reload_context);
+
+  char *context_ptr = (char *) &th->context;
+  char *context_offset = context_ptr + aarch64_windows_process.mappings[r];
+  struct gdbarch *gdbarch = regcache->arch ();
+
+  gdb_assert (!gdbarch_read_pc_p (gdbarch));
+  gdb_assert (gdbarch_pc_regnum (gdbarch) >= 0);
+  gdb_assert (!gdbarch_write_pc_p (gdbarch));
+
+  if (th->stopped_at_software_breakpoint
+      && !th->pc_adjusted
+      && r == gdbarch_pc_regnum (gdbarch))
+    {
+      uint64_t value;
+      memcpy (&value, context_offset, 8);
+      value -= gdbarch_decr_pc_after_break (gdbarch);
+      memcpy (context_offset, &value, 8);
+      /* Make sure we only rewrite the PC a single time.  */
+      th->pc_adjusted = true;
+    }
+  regcache->raw_supply (r, context_offset);
+}
+
+/* See windows-nat.h.  */
+
+void
+aarch64_windows_nat_target::store_one_register (const struct regcache *regcache,
+						windows_thread_info *th, int r)
+{
+  gdb_assert (r >= 0);
+
+  char *context_ptr = (char *) &th->context;
+
+  regcache->raw_collect (r, context_ptr + aarch64_windows_process.mappings[r]);
+}
+
+/* See windows-nat.h.  */
+
+bool
+aarch64_windows_nat_target::is_sw_breakpoint (const EXCEPTION_RECORD *er) const
+{
+  /* 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);
+}
+
+/* Notify all threads that the debug registers changed.  */
+
+void
+aarch64_notify_debug_reg_change (ptid_t ptid,
+				 int is_watchpoint, unsigned int idx)
+{
+  struct aarch64_debug_reg_state *state
+    = aarch64_get_debug_reg_state (inferior_ptid.pid ());
+  aarch64_windows_process.dr_state = *state;
+
+  for (auto &th : aarch64_windows_process.thread_list)
+    th->debug_registers_changed = true;
+}
+
+INIT_GDB_FILE (aarch64_windows_nat)
+{
+  aarch64_initialize_hw_point ();
+
+  /* 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;
+
+  /* The target is not a global specifically to avoid a C++ "static
+     initializer fiasco" situation.  */
+  add_inf_child_target (new aarch64_windows_nat_target);
+
+  windows_process = &aarch64_windows_process;
+}
diff --git a/gdb/aarch64-windows-tdep.c b/gdb/aarch64-windows-tdep.c
new file mode 100644
index 00000000000..e7312d296ae
--- /dev/null
+++ b/gdb/aarch64-windows-tdep.c
@@ -0,0 +1,70 @@
+/* Target-dependent code for Windows AArch64.
+
+   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 "gdbarch.h"
+#include "aarch64-tdep.h"
+#include "arch-utils.h"
+#include "regset.h"
+#include "windows-tdep.h"
+
+/* Windows uses the various BRK instruction variants for special operations,
+   and BRK #0xf000 triggers a breakpoint exception in the debugger.  */
+constexpr gdb_byte aarch64_windows_breakpoint[] = {0x00, 0x00, 0x3e, 0xd4};
+
+typedef BP_MANIPULATION (aarch64_windows_breakpoint) aarch64_w_breakpoint;
+
+/* gdbarch initialization for Windows on AArch64.  */
+
+static void
+aarch64_windows_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
+{
+  set_gdbarch_ps_regnum (gdbarch, AARCH64_CPSR_REGNUM);
+
+  set_gdbarch_breakpoint_kind_from_pc (gdbarch,
+				       aarch64_w_breakpoint::kind_from_pc);
+  set_gdbarch_sw_breakpoint_from_kind (gdbarch,
+				       aarch64_w_breakpoint::bp_from_kind);
+
+  /* Usually the arm BRK instruction triggers with the PC still at the
+     instruction, but Windows increments the PC before notifying the
+     debugger.  */
+  set_gdbarch_decr_pc_after_break (gdbarch, 4);
+
+  windows_init_abi (info, gdbarch);
+}
+
+static gdb_osabi
+aarch64_windows_osabi_sniffer (bfd *abfd)
+{
+  const char *target_name = bfd_get_target (abfd);
+
+  if (!streq (target_name, "pei-aarch64-little"))
+    return GDB_OSABI_UNKNOWN;
+
+  return GDB_OSABI_WINDOWS;
+}
+
+INIT_GDB_FILE (aarch64_windows_tdep)
+{
+  gdbarch_register_osabi (bfd_arch_aarch64, 0, GDB_OSABI_WINDOWS,
+			  aarch64_windows_init_abi);
+
+  gdbarch_register_osabi_sniffer (bfd_arch_aarch64, bfd_target_coff_flavour,
+				  aarch64_windows_osabi_sniffer);
+}
diff --git a/gdb/coff-pe-read.c b/gdb/coff-pe-read.c
index f78f825fa12..5073a4b0c4b 100644
--- a/gdb/coff-pe-read.c
+++ b/gdb/coff-pe-read.c
@@ -339,7 +339,8 @@ read_pe_exported_syms (minimal_symbol_reader &reader,
   is_pe64 = (strcmp (target, "pe-x86-64") == 0
 	     || strcmp (target, "pei-x86-64") == 0
 	     || strcmp (target, "pe-aarch64") == 0
-	     || strcmp (target, "pei-aarch64") == 0);
+	     || strcmp (target, "pei-aarch64") == 0
+	     || strcmp (target, "pei-aarch64-little") == 0);
   is_pe32 = (strcmp (target, "pe-i386") == 0
 	     || strcmp (target, "pei-i386") == 0
 	     || strcmp (target, "pe-arm-wince-little") == 0
@@ -638,7 +639,8 @@ pe_text_section_offset (struct bfd *abfd)
   is_pe64 = (strcmp (target, "pe-x86-64") == 0
 	     || strcmp (target, "pei-x86-64") == 0
 	     || strcmp (target, "pe-aarch64") == 0
-	     || strcmp (target, "pei-aarch64") == 0);
+	     || strcmp (target, "pei-aarch64") == 0
+	     || strcmp (target, "pei-aarch64-little") == 0);
   is_pe32 = (strcmp (target, "pe-i386") == 0
 	     || strcmp (target, "pei-i386") == 0
 	     || strcmp (target, "pe-arm-wince-little") == 0
diff --git a/gdb/configure.host b/gdb/configure.host
index fdd651987e3..e83b944c0b8 100644
--- a/gdb/configure.host
+++ b/gdb/configure.host
@@ -81,6 +81,9 @@ case "${host}" in
 
 aarch64*-*-linux*)	gdb_host=linux ;;
 aarch64*-*-freebsd*)	gdb_host=fbsd ;;
+aarch64*-*-mingw*)	gdb_host=mingw64a
+			gdb_host_obs=mingw-hdep.o
+			;;
 
 alpha*-*-linux*)	gdb_host=alpha-linux ;;
 alpha*-*-netbsdaout* | alpha*-*-knetbsdaout*-gnu)
diff --git a/gdb/configure.nat b/gdb/configure.nat
index 3ac0319328f..38dd4179511 100644
--- a/gdb/configure.nat
+++ b/gdb/configure.nat
@@ -77,6 +77,10 @@ case ${gdb_host} in
     cygwin*)
 	NATDEPFILES='x86-nat.o nat/x86-dregs.o windows-nat.o nat/windows-nat.o'
 	;;
+    mingw64a)
+	NATDEPFILES='aarch64-nat.o aarch64-windows-nat.o windows-nat.o \
+	nat/windows-nat.o nat/aarch64-hw-point.o'
+	;;
     mingw*)
 	NATDEPFILES='x86-nat.o nat/x86-dregs.o windows-nat.o nat/windows-nat.o'
 	;;
diff --git a/gdb/configure.tgt b/gdb/configure.tgt
index c0031e3f7a6..ba418653e86 100644
--- a/gdb/configure.tgt
+++ b/gdb/configure.tgt
@@ -155,6 +155,11 @@ aarch64*-*-linux*)
 			symfile-mem.o linux-record.o"
 	;;
 
+aarch64-*-mingw*)
+        # Target: MingW/aarch64
+	gdb_target_obs="aarch64-windows-tdep.o windows-tdep.o"
+        ;;
+
 alpha*-*-linux*)
 	# Target: Little-endian Alpha running Linux
 	gdb_target_obs="alpha-linux-tdep.o \
diff --git a/gdb/nat/windows-nat.c b/gdb/nat/windows-nat.c
index 2d0c7ddc2ca..a74bab75238 100644
--- a/gdb/nat/windows-nat.c
+++ b/gdb/nat/windows-nat.c
@@ -530,6 +530,7 @@ windows_process_info::add_dll (LPVOID load_addr)
   if (!ret)
     return;
 
+#if defined __i386__ || defined __x86_64__
   char system_dir[MAX_PATH];
   char syswow_dir[MAX_PATH];
   size_t system_dir_len = 0;
@@ -560,6 +561,7 @@ windows_process_info::add_dll (LPVOID load_addr)
 	}
 
     }
+#endif
   for (i = 1; i < (int) (cb_needed / sizeof (HMODULE)); i++)
     {
       MODULEINFO mi;
@@ -583,6 +585,8 @@ windows_process_info::add_dll (LPVOID load_addr)
 #else
       name = dll_name;
 #endif
+
+#if defined __i386__ || defined __x86_64__
       /* Convert the DLL path of 32bit processes returned by
 	 GetModuleFileNameEx from the 64bit system directory to the
 	 32bit syswow64 directory if necessary.  */
@@ -595,6 +599,7 @@ windows_process_info::add_dll (LPVOID load_addr)
 	  syswow_dll_path += name + system_dir_len;
 	  name = syswow_dll_path.c_str();
 	}
+#endif
 
       /* Record the DLL if either LOAD_ADDR is NULL or the address
 	 at which the DLL was loaded is equal to LOAD_ADDR.  */
diff --git a/gdb/nat/windows-nat.h b/gdb/nat/windows-nat.h
index c9cae999a70..5411c73ff97 100644
--- a/gdb/nat/windows-nat.h
+++ b/gdb/nat/windows-nat.h
@@ -474,7 +474,9 @@ struct WindowsContext<CONTEXT *>
   static constexpr DWORD full	  = CONTEXT_FULL;
   static constexpr DWORD all	  = (CONTEXT_FULL
 				     | CONTEXT_FLOATING_POINT
+#ifdef CONTEXT_SEGMENTS
 				     | CONTEXT_SEGMENTS
+#endif
 				     | CONTEXT_DEBUG_REGISTERS
 				     | CONTEXT_EXTENDED_REGISTERS);
 };


More information about the Gdb-cvs mailing list