/*
 * Copyright (c) 2004 Marcel Moolenaar
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "defs.h"
#include "command.h"
#include "elf-bfd.h"
#include "filenames.h"
#include "gdbcore.h"
#include "gdbthread.h"
#include "gdbsupport/gdb_obstack.h"
#include "inferior.h"
#include "objfiles.h"
#include "osabi.h"
#include "process-stratum-target.h"
#include "solib.h"
#include "target.h"
#include "value.h"
#include "readline/tilde.h"
#include "gdbsupport/buildargv.h"
#include "gdbsupport/pathstuff.h"
#include "gdbsupport/gdb_tilde_expand.h"

#include <sys/user.h>
#include <fcntl.h>
#include <kvm.h>

#include "kgdb.h"

static CORE_ADDR stoppcbs;
static LONGEST pcb_size;

static std::string vmcore;

struct fbsd_vmcore_ops
{
  /* Supply registers for a pcb to a register cache.  */
  void (*supply_pcb)(struct regcache *, CORE_ADDR) = nullptr;

  /* Return address of pcb for thread running on a CPU. */
  CORE_ADDR (*cpu_pcb_addr)(u_int) = nullptr;
};

/* Per-architecture data key.  */
static const registry<gdbarch>::key<struct fbsd_vmcore_ops> fbsd_vmcore_data;

static struct fbsd_vmcore_ops *
get_fbsd_vmcore_ops (struct gdbarch *gdbarch)
{
  struct fbsd_vmcore_ops *ops = fbsd_vmcore_data.get (gdbarch);
  if (ops == nullptr)
    ops = fbsd_vmcore_data.emplace (gdbarch);
  return ops;
}

/* Set the function that supplies registers from a pcb
   for architecture GDBARCH to SUPPLY_PCB.  */

void
fbsd_vmcore_set_supply_pcb (struct gdbarch *gdbarch,
			    void (*supply_pcb) (struct regcache *,
						CORE_ADDR))
{
  struct fbsd_vmcore_ops *ops = get_fbsd_vmcore_ops (gdbarch);
  ops->supply_pcb = supply_pcb;
}

/* Set the function that returns the address of the pcb for a thread
   running on a CPU for
   architecture GDBARCH to CPU_PCB_ADDR.  */

void
fbsd_vmcore_set_cpu_pcb_addr (struct gdbarch *gdbarch,
			      CORE_ADDR (*cpu_pcb_addr) (u_int))
{
  struct fbsd_vmcore_ops *ops = get_fbsd_vmcore_ops (gdbarch);
  ops->cpu_pcb_addr = cpu_pcb_addr;
}

static CORE_ADDR kernstart;
static kvm_t *kvm;
int kgdb_quiet;

static ptid_t
fbsd_vmcore_ptid(int tid)
{
	if (kvm == NULL)
		/*
		 * The remote target stores the 'tid' in the lwp
		 * field.
		 */
		return ptid_t(inferior_ptid.pid(), tid, 0);

	/*
	 * This follows the model described in bsd-kvm.c except that
	 * in kernel tids are used as the tid of the ptid instead of a
	 * process ID.
	 */
	return ptid_t(1, 1, tid);
}

#define	MSGBUF_SEQ_TO_POS(size, seq)	((seq) % (size))

static void
kgdb_dmesg(void)
{
	CORE_ADDR bufp;
	int size, rseq, wseq;
	gdb_byte c;

	/*
	 * Display the unread portion of the message buffer. This gives the
	 * user a some initial data to work from.
	 */
	if (kgdb_quiet)
		return;
	try {
		bufp = parse_and_eval_address("msgbufp->msg_ptr");
		size = parse_and_eval_long("msgbufp->msg_size");
		rseq = parse_and_eval_long("msgbufp->msg_rseq");
		wseq = parse_and_eval_long("msgbufp->msg_wseq");
	} catch (const gdb_exception_error &e) {
		return;
	}
	if (size == 0)
		return;
	rseq = MSGBUF_SEQ_TO_POS(size, rseq);
	wseq = MSGBUF_SEQ_TO_POS(size, wseq);
	if (rseq == wseq)
		return;

	printf("\nUnread portion of the kernel message buffer:\n");
	while (rseq < wseq) {
		read_memory(bufp + rseq, &c, 1);
		putchar(c);
		rseq++;
		if (rseq == size)
			rseq = 0;
	}
	if (c != '\n')
		putchar('\n');
	putchar('\n');
}

#define	KERNEL_INTERP		"/red/herring"

enum gdb_osabi
fbsd_kernel_osabi_sniffer(bfd *abfd)
{
	asection *s;
	bfd_byte buf[sizeof(KERNEL_INTERP)];
	bfd_byte *bufp;

	/* First, determine if this is a FreeBSD/ELF binary. */
	switch (elf_elfheader(abfd)->e_ident[EI_OSABI]) {
	case ELFOSABI_FREEBSD:
		break;
	case ELFOSABI_NONE: {
		enum gdb_osabi osabi = GDB_OSABI_UNKNOWN;

		for (asection *sect : gdb_bfd_sections (abfd))
		  generic_elf_osabi_sniff_abi_tag_sections (abfd, sect, &osabi);

		/*
		 * aarch64 and RISC-V kernels don't have the right
		 * note tag for kernels so just look for /red/herring
		 * anyway.
		 */
		if (osabi == GDB_OSABI_UNKNOWN &&
		    ((elf_elfheader(abfd)->e_machine == EM_AARCH64) ||
		    (elf_elfheader(abfd)->e_machine == EM_RISCV)))
			break;
		if (osabi != GDB_OSABI_FREEBSD)
			return (GDB_OSABI_UNKNOWN);
		break;
	}
	default:
		return (GDB_OSABI_UNKNOWN);
	}

	/* FreeBSD ELF kernels have an interpreter path of "/red/herring". */
	bufp = buf;
	s = bfd_get_section_by_name(abfd, ".interp");
	if (s != NULL && bfd_section_size(s) == sizeof(buf) &&
	    bfd_get_full_section_contents(abfd, s, &bufp) &&
	    memcmp(buf, KERNEL_INTERP, sizeof(buf)) == 0)
		return (GDB_OSABI_FREEBSD_KERNEL);

	return (GDB_OSABI_UNKNOWN);
}

/* The FreeBSD libkvm target.  */

static const target_info fbsd_kvm_target_info = {
  "vmcore",
  N_("Kernel core dump file"),
  N_("Use a vmcore file as a target.\n\
If no filename is specified, /dev/mem is used to examine the running kernel.\n\
target vmcore [-w] [filename]")
};

class fbsd_kvm_target final : public process_stratum_target
{
public:
  fbsd_kvm_target () = default;

  const target_info &info () const override
  { return fbsd_kvm_target_info; }

  void close () override;

  void fetch_registers (struct regcache *, int) override;
  enum target_xfer_status xfer_partial (enum target_object object,
					const char *annex,
					gdb_byte *readbuf,
					const gdb_byte *writebuf,
					ULONGEST offset, ULONGEST len,
					ULONGEST *xfered_len) override;

  void files_info () override;
  bool thread_alive (ptid_t ptid) override;
  std::string pid_to_str (ptid_t) override;
  const char *extra_thread_info (thread_info *) override;

  bool has_all_memory () override { return false; }
  bool has_memory () override;
  bool has_stack () override;
  bool has_registers () override;
  bool has_execution (inferior *inf) override { return false; }
};

/* Target ops for libkvm interface.  */
static fbsd_kvm_target fbsd_kvm_ops;

#ifdef HAVE_KVM_OPEN2
static int
kgdb_resolve_symbol(const char *name, kvaddr_t *kva)
{
	struct bound_minimal_symbol ms;

	ms = lookup_minimal_symbol (name, NULL, NULL);
	if (ms.minsym == NULL)
		return (1);
	*kva = ms.value_address ();
	return (0);
}
#endif

static void
fbsd_kvm_target_open (const char *args, int from_tty)
{
	struct fbsd_vmcore_ops *ops = get_fbsd_vmcore_ops (current_inferior ()->arch ());
	char kvm_err[_POSIX2_LINE_MAX];
	struct inferior *inf;
	struct cleanup *old_chain;
	struct kthr *kt;
	kvm_t *nkvm;
	const char *kernel;
	std::string filename;
	LONGEST osreldate;
	bool writeable;

	if (ops == NULL || ops->supply_pcb == NULL || ops->cpu_pcb_addr == NULL)
		error ("ABI doesn't support a vmcore target");

	target_preopen (from_tty);
	kernel = get_exec_file (0);
	if (kernel == NULL)
		error ("Can't open a vmcore without a kernel");

	writeable = false;
	if (args != NULL) {
		gdb_argv built_argv (args);

		for (char **argv = built_argv.get (); *argv != NULL; argv++) {
			if (**argv == '-') {
				if (strcmp (*argv, "-w") == 0)
					writeable = true;
				else
					error (_("Invalid argument"));
			} else {
				if (!filename.empty ())
					error (_("Invalid argument"));

				filename = gdb_tilde_expand (*argv);
				if (!IS_ABSOLUTE_PATH (filename))
					filename = gdb_abspath (filename.c_str ());
			}
		}
	}

#ifdef HAVE_KVM_OPEN2
	nkvm = kvm_open2(kernel, filename.c_str (),
	    writeable ? O_RDWR : O_RDONLY, kvm_err, kgdb_resolve_symbol);
#else
	nkvm = kvm_openfiles(kernel, filename.c_str (), NULL,
	    writeable ? O_RDWR : O_RDONLY, kvm_err);
#endif
	if (nkvm == NULL) {
		error ("Failed to open vmcore: %s", kvm_err);
	}

	/* Don't free the filename now and close any previous vmcore. */
	current_inferior ()->unpush_target (&fbsd_kvm_ops);

#ifdef HAVE_KVM_DISP
	/* Relocate kernel objfile if needed. */
	struct objfile *symfile_objfile =
	  current_program_space->symfile_object_file;
	if (symfile_objfile != nullptr &&
	    (bfd_get_file_flags(symfile_objfile->obfd.get ()) &
	      (EXEC_P | DYNAMIC)) != 0) {
		CORE_ADDR displacement = kvm_kerndisp(nkvm);
		if (displacement != 0) {
			section_offsets new_offsets (symfile_objfile->section_offsets.size (),
			    displacement);
			objfile_relocate(symfile_objfile, new_offsets);
		}
	}
#endif

	kvm = nkvm;
	vmcore = std::move(filename);
	target_unpush_up unpusher;
	inf = current_inferior();
	inf->push_target (&fbsd_kvm_ops);

	if (inf->pid == 0) {
		inferior_appeared(inf, 1);
		inf->fake_pid_p = 1;
	}

	/*
	 * Determine the first address in KVA.  Newer kernels export
	 * VM_MAXUSER_ADDRESS and the first kernel address can be
	 * determined by adding one.  Older kernels do not provide a
	 * symbol that is valid on all platforms, but kernbase is close
	 * for most platforms.
	 */
	try {
		kernstart = parse_and_eval_address("vm_maxuser_address") + 1;
	} catch (const gdb_exception_error &e) {
		kernstart = kgdb_lookup("kernbase");
	}

	osreldate = parse_and_eval_long("osreldate");

	/*
	 * Look up symbols needed for stoppcbs handling, but don't
	 * fail if they aren't present.
	 */
	stoppcbs = kgdb_lookup("stoppcbs");
	if (osreldate >= 1400088) {
		/* stoppcbs is now a pointer rather than an array. */
		try {
			stoppcbs = read_memory_typed_address(stoppcbs,
			    builtin_type(current_inferior ()->arch())->builtin_data_ptr);
		} catch (const gdb_exception_error &e) {
			stoppcbs = 0;
		}
	}

	try {
		pcb_size = parse_and_eval_long("pcb_size");
	} catch (const gdb_exception_error &e) {
		pcb_size = 0;
	}

	if (pcb_size == 0) {
		try {
			pcb_size = parse_and_eval_long("sizeof(struct pcb)");
		} catch (const gdb_exception_error &e) {
#ifdef HAVE_KVM_OPEN2
			if (kvm_native(nkvm))
				pcb_size = sizeof(struct pcb);
			else
				pcb_size = 0;
#else
			pcb_size = sizeof(struct pcb);
#endif
		}
	}

	kgdb_dmesg();

	kt = kgdb_thr_init(ops->cpu_pcb_addr);
	thread_info *curthr = nullptr;
	while (kt != NULL) {
		thread_info *thr = add_thread_silent(&fbsd_kvm_ops,
		    fbsd_vmcore_ptid(kt->tid));
		if (kt == curkthr)
			curthr = thr;
		kt = kgdb_thr_next(kt);
	}
	switch_to_thread (curthr);

	unpusher.release ();

	post_create_inferior (from_tty);

	target_fetch_registers (get_thread_regcache (curthr), -1);

	reinit_frame_cache ();
	print_stack_frame (get_selected_frame (NULL), 0, SRC_AND_LOC, 1);
}

void
fbsd_kvm_target::close()
{

	if (kvm != NULL) {
		switch_to_no_thread ();
		exit_inferior (current_inferior ());

		clear_solib (current_program_space);
		if (kvm_close(kvm) != 0)
			warning("cannot close \"%s\": %s", vmcore.c_str (),
			    kvm_geterr(kvm));
		kvm = NULL;
		vmcore.clear ();
	}

}

#if 0
static void
kgdb_trgt_detach(struct target_ops *ops, const char *args, int from_tty)
{

	if (args)
		error ("Too many arguments");
	unpush_target(&kgdb_trgt_ops);
	reinit_frame_cache();
	if (from_tty)
		gdb_printf("No vmcore file now.\n");
}
#endif

const char *
fbsd_kvm_target::extra_thread_info(thread_info *ti)
{

	return (kgdb_thr_extra_thread_info(ti->ptid.tid()));
}

bool
fbsd_kvm_target::has_memory ()
{
  return (kvm != NULL);
}

bool
fbsd_kvm_target::has_stack ()
{
  return (kvm != NULL);
}

bool
fbsd_kvm_target::has_registers ()
{
  return (kvm != NULL);
}

void
fbsd_kvm_target::files_info()
{

	gdb_printf ("\t`%s', ", vmcore.c_str ());
	gdb_stdout->wrap_here (8);
	gdb_printf ("file type %s.\n", "FreeBSD kernel vmcore");
}

std::string
fbsd_kvm_target::pid_to_str(ptid_t ptid)
{
  return string_printf (_("Thread %ld"), ptid.tid ());
}

bool
fbsd_kvm_target::thread_alive(ptid_t ptid)
{
	return (kgdb_thr_lookup_tid(ptid.tid()) != NULL);
}

void
fbsd_kvm_target::fetch_registers(struct regcache *regcache, int regnum)
{
	struct fbsd_vmcore_ops *ops = get_fbsd_vmcore_ops (regcache->arch ());
	struct kthr *kt;

	if (ops->supply_pcb == NULL)
		return;
	kt = kgdb_thr_lookup_tid(regcache->ptid().tid());
	if (kt == NULL)
		return;
	ops->supply_pcb(regcache, kt->pcb);
}

enum target_xfer_status
fbsd_kvm_target::xfer_partial(enum target_object object,
		       const char *annex, gdb_byte *readbuf,
		       const gdb_byte *writebuf,
		       ULONGEST offset, ULONGEST len, ULONGEST *xfered_len)
{
	ssize_t nbytes;

	gdb_assert(kvm != NULL);
	switch (object) {
	case TARGET_OBJECT_MEMORY:
		nbytes = len;
		if (readbuf != NULL)
#ifdef HAVE_KVM_OPEN2
			nbytes = kvm_read2(kvm, offset, readbuf, len);
#else
			nbytes = kvm_read(kvm, offset, readbuf, len);
#endif
		if (writebuf != NULL && len > 0)
			nbytes = kvm_write(kvm, offset, writebuf, len);
		if (nbytes < 0)
			return TARGET_XFER_E_IO;
		if (nbytes == 0)
			return TARGET_XFER_EOF;
		*xfered_len = nbytes;
		return TARGET_XFER_OK;
	default:
		return TARGET_XFER_E_IO;
	}
}

static void
kgdb_switch_to_thread(const char *arg, int tid)
{
  struct thread_info *tp = fbsd_kvm_ops.find_thread (fbsd_vmcore_ptid (tid));
  if (tp == NULL)
    error ("invalid tid");
  thread_select (arg, tp);
}

static void
kgdb_set_proc_cmd (const char *arg, int from_tty)
{
	CORE_ADDR addr;
	struct kthr *thr;

	if (!arg)
		error_no_arg ("proc address for the new context");

	if (kvm == NULL)
		error ("only supported for core file target");

	addr = parse_and_eval_address (arg);

	if (addr < kernstart) {
		thr = kgdb_thr_lookup_pid((int)addr);
		if (thr == NULL)
			error ("invalid pid");
	} else {
		thr = kgdb_thr_lookup_paddr(addr);
		if (thr == NULL)
			error("invalid proc address");
	}
	kgdb_switch_to_thread(arg, thr->tid);
}

static void
kgdb_set_tid_cmd (const char *arg, int from_tty)
{
	CORE_ADDR addr;
	struct kthr *thr;

	if (!arg)
		error_no_arg ("TID or thread address for the new context");

	addr = (CORE_ADDR) parse_and_eval_address (arg);

	if (kvm != NULL && addr >= kernstart) {
		thr = kgdb_thr_lookup_taddr(addr);
		if (thr == NULL)
			error("invalid thread address");
		addr = thr->tid;
	}
	kgdb_switch_to_thread(arg, addr);
}

void _initialize_kgdb_target ();
void
_initialize_kgdb_target ()
{

	add_target(fbsd_kvm_target_info, fbsd_kvm_target_open,
	    filename_completer);

	add_com ("proc", class_obscure, kgdb_set_proc_cmd,
	   "Set current process context");
	add_com ("tid", class_obscure, kgdb_set_tid_cmd,
	   "Set current thread context");
}

CORE_ADDR
kgdb_trgt_stop_pcb(u_int cpuid)
{

	if (stoppcbs == 0 || pcb_size == 0)
		return 0;

	return (stoppcbs + pcb_size * cpuid);
}
