/* 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;

  void fill_thread_context (windows_thread_info *th) override;
  void invalidate_thread_context (windows_thread_info *th) override;
};

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 thread_context_continue (windows_thread_info *th, int killed) override;
  void thread_context_step (windows_thread_info *th, bool step) 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 ()
{
  windows_thread_info *th = aarch64_windows_process.find_thread (inferior_ptid);
  if (th == nullptr
      || th->last_event.dwDebugEventCode != EXCEPTION_DEBUG_EVENT)
    return {};

  EXCEPTION_RECORD &er = th->last_event.u.Exception.ExceptionRecord;
  if (er.ExceptionCode != EXCEPTION_BREAKPOINT
      || er.NumberParameters != 2)
    return {};

  const CORE_ADDR addr_trap = (CORE_ADDR) 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 nat/windows-nat.h.  */

void
aarch64_windows_per_inferior::fill_thread_context (windows_thread_info *th)
{
  CONTEXT *context = &th->context;

  if (context->ContextFlags == 0)
    {
      context->ContextFlags = WindowsContext<decltype(context)>::all;
      CHECK (get_thread_context (th->h, context));
    }
}

/* See windows-nat.h.  */

void
aarch64_windows_per_inferior::invalidate_thread_context (windows_thread_info *th)
{
  CONTEXT *context = &th->context;
  context->ContextFlags = 0;
}

/* 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,
						 bool enable)
{
  if (enable)
    th->context.Cpsr |= 0x200000;
  else
    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);

  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);
  gdb_assert (th->context.ContextFlags != 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;

  windows_debug_registers_changed_all_threads ();
}

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;
}
