/* Copyright (C) 2021 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 "defs.h"
#include "gcore-elf.h"
#include "elf-bfd.h"
#include "target.h"
#include "regcache.h"
#include "gdbarch.h"
#include "gdbthread.h"
#include "inferior.h"
#include "regset.h"
#include "gdbsupport/tdesc.h"

/* Structure for passing information from GCORE_COLLECT_THREAD_REGISTERS
   via an iterator to GCORE_COLLECT_REGSET_SECTION_CB. */

struct gcore_elf_collect_regset_section_cb_data
{
  gcore_elf_collect_regset_section_cb_data
	(struct gdbarch *gdbarch, const struct regcache *regcache,
	 bfd *obfd, ptid_t ptid, gdb_signal stop_signal,
	 gdb::unique_xmalloc_ptr<char> *note_data, int *note_size)
    : gdbarch (gdbarch), regcache (regcache), obfd (obfd),
      note_data (note_data), note_size (note_size),
      stop_signal (stop_signal)
  {
    /* The LWP is often not available for bare metal target, in which case
       use the tid instead.  */
    if (ptid.lwp_p ())
      lwp = ptid.lwp ();
    else
      lwp = ptid.tid ();
  }

  struct gdbarch *gdbarch;
  const struct regcache *regcache;
  bfd *obfd;
  gdb::unique_xmalloc_ptr<char> *note_data;
  int *note_size;
  unsigned long lwp;
  enum gdb_signal stop_signal;
  bool abort_iteration = false;
};

/* Callback for ITERATE_OVER_REGSET_SECTIONS that records a single
   regset in the core file note section.  */

static void
gcore_elf_collect_regset_section_cb (const char *sect_name,
				     int supply_size, int collect_size,
				     const struct regset *regset,
				     const char *human_name, void *cb_data)
{
  struct gcore_elf_collect_regset_section_cb_data *data
    = (struct gcore_elf_collect_regset_section_cb_data *) cb_data;
  bool variable_size_section = (regset != nullptr
				&& regset->flags & REGSET_VARIABLE_SIZE);

  gdb_assert (variable_size_section || supply_size == collect_size);

  if (data->abort_iteration)
    return;

  gdb_assert (regset != nullptr && regset->collect_regset != nullptr);

  /* This is intentionally zero-initialized by using std::vector, so
     that any padding bytes in the core file will show as 0.  */
  std::vector<gdb_byte> buf (collect_size);

  regset->collect_regset (regset, data->regcache, -1, buf.data (),
			  collect_size);

  /* PRSTATUS still needs to be treated specially.  */
  if (strcmp (sect_name, ".reg") == 0)
    data->note_data->reset (elfcore_write_prstatus
			    (data->obfd, data->note_data->release (),
			     data->note_size, data->lwp,
			     gdb_signal_to_host (data->stop_signal),
			     buf.data ()));
  else
    data->note_data->reset (elfcore_write_register_note
			    (data->obfd, data->note_data->release (),
			     data->note_size, sect_name, buf.data (),
			     collect_size));

  if (*data->note_data == nullptr)
    data->abort_iteration = true;
}

/* Records the register state of thread PTID out of REGCACHE into the note
   buffer represented by *NOTE_DATA and NOTE_SIZE.  OBFD is the bfd into
   which the core file is being created, and STOP_SIGNAL is the signal that
   cause thread PTID to stop.  */

static void
gcore_elf_collect_thread_registers
	(const struct regcache *regcache, ptid_t ptid, bfd *obfd,
	 gdb::unique_xmalloc_ptr<char> *note_data, int *note_size,
	 enum gdb_signal stop_signal)
{
  struct gdbarch *gdbarch = regcache->arch ();
  gcore_elf_collect_regset_section_cb_data data (gdbarch, regcache, obfd,
						 ptid, stop_signal,
						 note_data, note_size);
  gdbarch_iterate_over_regset_sections
    (gdbarch, gcore_elf_collect_regset_section_cb, &data, regcache);
}

/* See gcore-elf.h.  */

void
gcore_elf_build_thread_register_notes
  (struct gdbarch *gdbarch, struct thread_info *info, gdb_signal stop_signal,
   bfd *obfd, gdb::unique_xmalloc_ptr<char> *note_data, int *note_size)
{
  struct regcache *regcache
    = get_thread_arch_regcache (info->inf->process_target (),
				info->ptid, gdbarch);
  target_fetch_registers (regcache, -1);
  gcore_elf_collect_thread_registers (regcache, info->ptid, obfd,
				      note_data, note_size, stop_signal);
}

/* See gcore-elf.h.  */

void
gcore_elf_make_tdesc_note (bfd *obfd,
			   gdb::unique_xmalloc_ptr<char> *note_data,
			   int *note_size)
{
  /* Append the target description to the core file.  */
  const struct target_desc *tdesc = gdbarch_target_desc (target_gdbarch ());
  const char *tdesc_xml
    = tdesc == nullptr ? nullptr : tdesc_get_features_xml (tdesc);
  if (tdesc_xml != nullptr && *tdesc_xml != '\0')
    {
      /* Skip the leading '@'.  */
      if (*tdesc_xml == '@')
	++tdesc_xml;

      /* Include the null terminator in the length.  */
      size_t tdesc_len = strlen (tdesc_xml) + 1;

      /* Now add the target description into the core file.  */
      note_data->reset (elfcore_write_register_note (obfd,
						     note_data->release (),
						     note_size,
						     ".gdb-tdesc", tdesc_xml,
						     tdesc_len));
    }
}