/* VxWorks linear memory coredump target, for GDB. 

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 2 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, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */

#include "defs.h"
#include "frame.h"
#include "inferior.h"
#include "target.h"
#include "gdbcmd.h"
#include "language.h"
#include "symfile.h"
#include "objfiles.h"

#include <sys/types.h>
#include <unistd.h>

#include <sys/param.h>
#include <fcntl.h>
#include "gdb_string.h"

#include "gdbcore.h"
#include "symtab.h"

#include <ctype.h>
#include "gdb_stat.h"
#ifndef O_BINARY
#define O_BINARY 0
#endif

/* Prototypes for local functions */

extern int info_verbose;
extern int errno;

/* memory start and end limits.  these should really be variable XXX */

#define VX_MEM_START 0x8000000
#define VX_MEM_END 0x8400000

CORE_ADDR vx_dump_start = VX_MEM_START;
CORE_ADDR vx_dump_end   = VX_MEM_END;

int vx_core_dump_fd = -1;

/* Forward decl */

extern struct target_ops vx_dump_ops;

struct thread_info		/* from thread.c XXX */
{
  struct thread_info *next;
  int pid;			/* Actual process id */
  int num;			/* Convenient handle */
  CORE_ADDR prev_pc;		/* State from wait_for_inferior */
  CORE_ADDR prev_func_start;
  char *prev_func_name;
  struct breakpoint *step_resume_breakpoint;
  struct breakpoint *through_sigtramp_breakpoint;
  CORE_ADDR step_range_start;
  CORE_ADDR step_range_end;
  CORE_ADDR step_frame_address;
  int trap_expected;
  int handling_longjmp;
  int another_trap;
};

extern struct thread_info *thread_list;

/* ARGSUSED */
static void
vx_dump_close (quitting)
     int quitting;
{
  close(vx_core_dump_fd);
  vx_core_dump_fd = -1;
}

void
vx_task_gather()
{
  struct minimal_symbol *ms;
  int tid;
  int next;
  int tmp;

  ms = lookup_minimal_symbol("activeQHead",0,0);
  if (!ms)
    {
      printf_unfiltered("vx_task_gather: can't get activeQHead\n");
      return;
    }
  vx_dump_xfer_memory(ms->ginfo.value.address, &tid, 4, 0, 0);
  printf_unfiltered("vx_task_gather: activeQHead 0x%x\n", tid);

  vx_dump_xfer_memory(tid, &next, 4, 0, 0);

  tid -= 32;

  inferior_pid = tid;
  flush_cached_frames ();
  registers_changed ();
  stop_pc = read_pc();
  select_frame(get_current_frame(), 0);

  add_thread(tid);

  while (next != 0)
    {
      tid = next - 32;
      add_thread(tid);
      if (vx_dump_xfer_memory(next, &tmp, 4, 0, 0) == 0)
	break;
      next = tmp;
    }
}

/*  Process the first arg in ARGS as the new exec file.

    Note that we have to explicitly ignore additional args, since we can
    be called from file_command(), which also calls symbol_file_command()
    which can take multiple args. */

void
vx_dump_file_command (args, from_tty)
     char *args;
     int from_tty;
{
  char **argv;
  char *filename;
  int fd;

  target_preopen (from_tty);

  /* Remove any previous exec file.  */
  unpush_target (&vx_dump_ops);

  /* Now open and digest the file the user requested, if any.  */

  if (args)
    {
      /* Scan through the args and pick up the first non option arg
	 as the filename.  */

      argv = buildargv (args);
      if (argv == NULL)
	nomem (0);

      make_cleanup (freeargv, (char *) argv);

      for (; (*argv != NULL) && (**argv == '-'); argv++) {;}
      if (*argv == NULL)
	error ("no vxworks core dump file name was specified");

      filename = tilde_expand (*argv);
      make_cleanup (free, filename);
      
      fd = open (filename,  O_RDONLY|O_BINARY, 0);
      if (fd < 0)
	perror_with_name (filename);

      vx_core_dump_fd = fd;

      printf_unfiltered("core dump file <%s> opened\n", filename);
      validate_files ();
      push_target (&vx_dump_ops);
      vx_task_gather(); 
    }
  else if (from_tty)
    printf_unfiltered ("No vxworks core dump file now.\n");
}

int
vx_dump_xfer_memory (memaddr, myaddr, len, write, target)
     CORE_ADDR memaddr;
     char *myaddr;
     int len;
     int write;
     struct target_ops *target;
{
    int i;
    char *cp;
    char swap[4];

    if ((len % 2) != 0 ) {
	printf_unfiltered("len %d must be divisable by 2\n",len);
	return 0;
    }
    if (memaddr < vx_dump_start || memaddr > vx_dump_end)
    {
	printf_unfiltered("address 0x%x is out of range\n", memaddr);
	return 0;
    }
    if (lseek(vx_core_dump_fd, memaddr - vx_dump_start, SEEK_SET) == -1)
    {
	printf_unfiltered("vx_dump_xfer_memory: lseek error, errno=%d\n",
			  errno);
	return 0;
    }
    if (read(vx_core_dump_fd, myaddr, len) == -1)
    {
	printf_unfiltered("vx_dump_xfer_memory: read error, errno=%d\n",
			  errno);
	return 0;
    }

#if (HOST_BYTE_ORDER != BIG_ENDIAN)

    /*
     * fix byte order for little endian hosts (linux/386)
     */

    i = 0;
    cp = (char *) myaddr;
    while (i < len) {
	if (len > 2) {
	    /* 4 bytes */
	    swap[0] = *(cp + 3);
	    swap[1] = *(cp + 2);
	    swap[2] = *(cp + 1);
	    swap[3] = *(cp + 0);
	    bcopy(swap, myaddr, 4);
	} else {
	    /* 2 bytes */
	    swap[0] = *(cp + 1);
	    swap[1] = *(cp + 0);
	    bcopy(swap, myaddr, 2);
	}
    }
#endif

    return len;
}

char *
vxworks_task_status (int tid)
{
  int status = 0;
  static char status_string[80]; /* XXX */
  char *cp = status_string;

  vx_dump_xfer_memory(tid + 0x3c, &status, 4, 0, 0);
  status &= 0xf;

  bzero(status_string, sizeof (status_string));

  if (status == 0)
    {
      *cp = 'R';		/* ready */
      return cp;
    }

  if (status & 1)
    {
      *cp = 'S';		/* suspended */
      cp++;
    }
  if (status & 2)
    {
      *cp = 'P';		/* pending */
      cp++;
    }
  if (status & 4)
    {
      *cp = 'd';		/* delayed */
      cp++;
    }
  if (status & 8)
    {
      *cp = 'D';		/* dead */
      cp++;
    }

  return status_string;
}

int
vxworks_task_error_code (int tid)
{
  int error_code = 0;

  vx_dump_xfer_memory(tid + 0x84, &error_code, 4, 0, 0);
  return error_code;
}

static void
vxworks_thread_switch (pid)
     int pid;
{
  if (pid == inferior_pid)
    return;

  inferior_pid = pid;
  flush_cached_frames ();
  registers_changed ();
  stop_pc = read_pc();
  select_frame (get_current_frame (), 0);
}

static char *
vxworks_task_name (int tid)
{
  int name_loc = 0;
  static char name[40];			/* XXX */

  bzero(name, sizeof (name));
  vx_dump_xfer_memory(tid + 0x34, &name_loc, 4, 0, 0);
  vx_dump_xfer_memory(name_loc, name, sizeof(name), 0, 0);

  return name;
}

static void
info_vxworks_threads_command (arg, from_tty)
     char *arg;
     int from_tty;
{
  struct thread_info *tp;
  int current_pid = inferior_pid;

  /* Avoid coredumps which would happen if we tried to access a NULL
     selected_frame.  */
  if (!target_has_stack) error ("No stack.");

  for (tp = thread_list; tp; tp = tp->next)
    {
      if (! target_thread_alive (tp->pid))
 	{
	  tp->pid = -1;	/* Mark it as dead */
	  continue;
 	}

      if (tp->pid == current_pid)
	printf_filtered ("* ");
      else
	printf_filtered ("  ");

      printf_filtered ("%d %x <%s> [%s] (%x) ",
		       tp->num, tp->pid,
		       vxworks_task_name(tp->pid),
		       vxworks_task_status(tp->pid),
		       vxworks_task_error_code(tp->pid));

      vxworks_thread_switch (tp->pid);
      print_stack_frame (selected_frame, -1, 0);
    }

  vxworks_thread_switch (current_pid);
}

static void
vx_dump_files_info (t)
     struct target_ops *t;
{
}

int
vx_dump_thread_alive(int thread)
{
  return 1;
}

static void
vx_dump_fetch_registers (regno)
     int regno;
{
  int i;
  char regs[REGISTER_BYTES];
  int reg_addr;

  memset (regs, 0, REGISTER_BYTES);

  reg_addr = inferior_pid + 0x118 + 20; /* offset into WIND_TCB */
  vx_dump_xfer_memory(reg_addr, regs, REGISTER_BYTES, 0, 0);

  for (i = 0; i < NUM_REGS; i++)
    supply_register (i, &regs[REGISTER_BYTE(i)]);
}

/* If mourn is being called in all the right places, this could be say
   gdb internal error (since generic_mourn calls breakpoint_init_inferior). */

static int
ignore (addr, contents)
     CORE_ADDR addr;
     char *contents;
{
  return 0;
}


struct target_ops vx_dump_ops = {
  "vxcore",			/* to_shortname */
  "vxworks core dump interface",	/* to_longname */
  "Use a vxworks core dump file as a target.\n\
Specify the filename of the vxworks core dump file.", /* to_doc */
  vx_dump_file_command,	/* to_open */
  vx_dump_close,		/* to_close */
  find_default_attach,		/* to_attach */
  0,				/* to_detach */
  0,				/* to_resume */
  0,				/* to_wait */
  vx_dump_fetch_registers,	/* to_fetch_registers */
  0,				/* to_store_registers */
  0,				/* to_prepare_to_store */
  vx_dump_xfer_memory,	/* to_xfer_memory */
  vx_dump_files_info,		/* to_files_info */
  ignore,			/* to_insert_breakpoint */
  ignore,			/* to_remove_breakpoint */
  0,				/* to_terminal_init */
  0,				/* to_terminal_inferior */
  0,				/* to_terminal_ours_for_output */
  0,				/* to_terminal_ours */
  0,				/* to_terminal_info */
  0,				/* to_kill */
  0,				/* to_load */
  0,				/* to_lookup_symbol */
  find_default_create_inferior,	/* to_create_inferior */
  0,				/* to_mourn_inferior */
  0,				/* to_can_run */
  0,				/* to_notice_signals */
  vx_dump_thread_alive,	/* to_thread_alive */
  0,				/* to_stop */
  vx_dump_stratum,		/* to_stratum */
  0,				/* to_next */
  0,				/* to_has_all_memory */
  1,				/* to_has_memory */
  1,				/* to_has_stack */
  1,				/* to_has_registers */
  0,				/* to_has_execution */
  0,				/* to_sections */
  0,				/* to_sections_end */
  OPS_MAGIC,			/* to_magic */
};

void
_initialize_vx_dump()
{
  struct cmd_list_element *c;
  static struct cmd_list_element *task_cmd_list = NULL;
  extern struct cmd_list_element *cmdlist;

  c = add_cmd ("vx-dump-file", class_vxworks, vx_dump_file_command,
   "Use FILE as vxworks linear core dump.\n\
If FILE cannot be found as specified, your execution directory path\n\
is searched for a command of that name.\n\
No arg means have no vxworks core dump file.", &cmdlist);
  c->completer = filename_completer;
  add_target (&vx_dump_ops);

  add_info ("vxworks-threads", info_vxworks_threads_command,
	    "IDs of currently known vxworks threads.");
}

