/* Support for dumping and reloading various pieces of GDB's internal state.
   Copyright 1992 Free Software Foundation, Inc.
   Contributed by Cygnus Support, using pieces from other GDB modules.

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., 675 Mass Ave, Cambridge, MA 02139, USA.  */

/* This file provides support for dumping and then later reloading various
   portions of gdb's internal state.  It was originally implemented to
   support a need for mapping in an image of gdb's symbol table from an
   external file, where this image was created by an external program, such
   as an incremental linker.  However, it was generalized to enable future
   support for dumping and reloading various other useful pieces of gdb's
   internal state.

   State files have a fairly simple form which is intended to be easily
   extensible.  The basic format is:

	<file-header> <state-data> <form-tree>

   Where:

        file-header	A simple file-header containing a magic number
			so that gdb (and other readers) can quickly
			determine what kind of file this is, and a file
			offset to the root of the form-tree.

	state-data	The "raw" state-data that is referenced by nodes
			in the form-tree.

	form-tree	A tree of arbitrarily sized nodes containing
			information about gdb's internal state, and
			possibly referencing data in the state-data section
			of the file.  Resembles DWARF in some respects.

   When writing a state file, a hole is left for the file-header at the
   beginning of the file, the state data is written immediately after the
   file header (while storing the file offsets and sizes back into the
   internal form-tree along the way), the form-tree itself is written
   at the end of the file, and then the file header is written by seeking
   back to the beginning of the file.  This order is required because
   the form tree contains file offsets and sizes in the state data portion
   of the file, and the file header contains the file offset to the start
   of the form tree.

   Readers simply open the file, validate the magic number, seek to the
   root of the form-tree, and walk the tree looking for the information that
   they are interested in (and ignoring things that they aren't, or don't
   understand).

   */


#include "defs.h"
#include "symtab.h"
#include "bfd.h"
#include "symfile.h"
#include "state.h"

#ifndef SEEK_SET
#define SEEK_SET 0
#endif

#ifndef SEEK_END
#define SEEK_END 2
#endif

/* Inside the state file, the form-tree consists of a series of
   form-tree entries (FTE's).  The parent/child/sibling relationships
   are implied by the ordering and by an explicit sibling reference
   in FTE's that have siblings.

   Specifically, given two sequential FTE's, say A and B, if B immediately
   follows A, and A does not have a sibling reference to B, then B is
   the first child of A.  Otherwise B must be a sibling of A and A must
   have a sibling reference for it.

   Each FTE is simply an array of long integers, with at least three
   members.  This form was chosen over a packed data form for simplicity
   in access, not having to worry about the relative sizes of the different
   integers (short, int, long), and not having to worry about alignment
   constraints.  Also in the name of simplicity, every FTE has a sibling
   reference slot reserved for it, even if there are no siblings.

   The first value in an FTE is the size of the FTE in bytes, including
   the size value itself.  The second entry contains a tag which indicates
   the type of the FTE.  The third entry is a sibling reference, which either
   refers to a valid sibling node or is zero.  Following is zero or more
   attributes, each of which consists of one or more long values. */

/* Tag names and codes. */

#define TAG_padding	0x0000		/* Padding */
#define TAG_objfile	0x0001		/* Dumped objfile */

/* Form names, codes, and macros. */

#define FORM_ABSREF	0x01		/* Next long is absolute file offset */
#define FORM_RELREF	0x02		/* Next long is relative file offset */
#define FORM_IVAL	0x03		/* Next long is int value */
#define FORM_ADDR	0x04		/* Next long is mem addr */

#define FORM_MASK	0xFF
#define FORM_X(atr)	((atr) & FORM_MASK)

/* Attribute names and codes. */

#define AT_sibling	(0x0100 | FORM_RELREF)	/* Reference to sibling node */
#define AT_name		(0x0200 | FORM_ABSREF)	/* Reference to a string */
#define AT_offset	(0x0300 | FORM_ABSREF)	/* Reference to generic data */
#define AT_size		(0x0400 | FORM_IVAL)
#define AT_addr		(0x0500 | FORM_ADDR)
#define AT_aux_addr	(0x0600 | FORM_ADDR)

/* */

static void
load_symbols PARAMS ((FILE *));

static void
dump_state_command PARAMS ((char *, int));

static void
load_state_command PARAMS ((char *, int));

#ifdef HAVE_MMAP

static void
write_header PARAMS ((sfd *));

static void
write_formtree PARAMS ((sfd *));

static void
write_objfile_state PARAMS ((sfd *));

static void
free_subtree PARAMS ((struct formnode *));

static void
size_subtree PARAMS ((struct formnode *));

#endif

struct formnode *formtree = NULL;

/* ARGSUSED */
static void
load_symbols (statefile)
     FILE *statefile;
{

#if 0
  /* Discard old symbols.  FIXME: This is essentially symbol_file_command's
     body when there is no name.  Make it a common function that is
     called from each place. */

  if (symfile_objfile)
    {
      free_objfile (symfile_objfile);
    }
  symfile_objfile = NULL;
#endif

#if 0 && defined (HAVE_MMAP)
  if (mtop > mbase)
    {
      warning ("internal error: mbase (%08x) != mtop (%08x)",
	       mbase, mtop);
      munmap (mbase, mtop - mbase);
    }
#endif	/* HAVE_MMAP */

  /* Getting new symbols may change our opinion about what is frameless. */

  reinit_frame_cache ();

}

#ifdef HAVE_MMAP

/* Allocate a form node */

static struct formnode *
alloc_formnode ()
{
  struct formnode *fnp;

  fnp = (struct formnode *) xmalloc (sizeof (struct formnode));
  (void) memset (fnp, 0, sizeof (struct formnode));
  fnp -> sibling = formtree;
  formtree = fnp;
  return (fnp);
}

/* Recursively walk a form-tree from the specified node, freeing
   nodes from the bottom up.  The concept is pretty simple, just free
   all the child nodes, then all the sibling nodes, then the node
   itself. */

static void
free_subtree (fnp)
     struct formnode *fnp;
{
  if (fnp != NULL)
    {
      free_subtree (fnp -> child);
      free_subtree (fnp -> sibling);
      if (fnp -> nodedata != NULL)
	{
	  free (fnp -> nodedata);
	}
      free (fnp);
    }
}

/* Recursively walk a form-tree from the specified node, computing the
   size of each subtree from the bottom up.

   At each node, the file space that will be consumed by the subtree
   rooted in that node is the sum of all the subtrees rooted in each
   child node plus the size of the node itself.

   Thus for each node, we size the child subtrees, add to that our
   size, contribute this size towards the size of any parent node, and
   then ask any of our siblings to do the same.

   Also, once we know the size of any subtree rooted at this node, we
   can initialize the offset to the sibling node (if any).

   Since every form-tree node must have valid nodedata at this point,
   we detect and report a warning for any node that doesn't. */

static void
size_subtree (fnp)
     struct formnode *fnp;
{
  long *lp;

  if (fnp != NULL)
    {
      if (fnp -> nodedata == NULL)
	{
	  warning ("internal error -- empty form node");
	}
      else
	{
	  size_subtree (fnp -> child);
	  fnp -> treesize += *(long *) fnp -> nodedata;
	  if (fnp -> parent != NULL)
	    {
	      fnp -> parent -> treesize += fnp -> treesize;
	    }
	  if (fnp -> sibling)
	    {
	      size_subtree (fnp -> sibling);
	      lp = (long *) (fnp -> nodedata + 2 * sizeof (long));
	      *lp = fnp -> treesize;
	    }
	}
    }
}

/* Recursively walk a form-tree from the specified node, writing
   nodes from the top down. */

static void
write_subtree (fnp, asfd)
     struct formnode *fnp;
     sfd *asfd;
{
  if (fnp != NULL)
    {
      if (fnp -> nodedata != NULL)
	{
	  fwrite (fnp -> nodedata, *(long *) fnp -> nodedata, 1, asfd -> fp);
	}
      write_subtree (fnp -> child, asfd);
      write_subtree (fnp -> sibling, asfd);
    }
}

/* Free the entire current formtree.  Called via do_cleanups, regardless
   of whether there is an error or not. */

static void
free_formtree ()
{
  free_subtree (formtree);
  formtree = NULL;
}

/* Write out the file header.  Generally this is done last, even though
   it is located at the start of the file, since we need to have file
   offset to where the annotated form tree was written, and it's size. */

static void
write_header (asfd)
     sfd *asfd;
{
  fseek (asfd -> fp, 0L, SEEK_SET);
  fwrite ((char *) &asfd -> hdr, sizeof (asfd -> hdr), 1, asfd -> fp);
}

/* Write out the annotated form tree.  We should already have written out
   the state data, and noted the file offsets and sizes in each node of
   the form tree that references part of the state data.

   The form tree can be written anywhere in the file where there is room
   for it.  Since there is always room at the end of the file, we write
   it there.  We also need to record the file offset to the start of the
   form tree, and it's size, for future use when writing the file header.

   In order to compute the sibling references, we need to know, at
   each node, how much space will be consumed when all of that node's
   children nodes have been written.  Thus we walk the tree, computing
   the sizes of the subtrees from the bottom up.  At any node, the
   offset from the start of that node to the start of the sibling node
   is simply the size of the node plus the size of the subtree rooted
   in that node. */

static void
write_formtree (asfd)
     sfd *asfd;
{
  size_subtree (formtree);
  fseek (asfd -> fp, 0L, SEEK_END);
  asfd -> hdr.sf_ftoff = ftell (asfd -> fp);
  write_subtree (formtree, asfd);
  asfd -> hdr.sf_ftsize = ftell (asfd -> fp) - asfd -> hdr.sf_ftoff;
}

/* Note that we currently only support having one objfile with dumpable
   state. */

static void
write_objfile_state (asfd)
     sfd *asfd;
{
  struct objfile *objfile;
  struct formnode *fnp;
  PTR base;
  PTR breakval;
  long *lp;
  unsigned int ftesize;
  long ftebuf[64];
  long foffset;

  /* First walk through the objfile list looking for the first objfile
     that is dumpable. */

  for (objfile = object_files; objfile != NULL; objfile = objfile -> next)
    {
      if (objfile -> flags & OBJF_DUMPABLE)
	{
	  break;
	}
    }

  if (objfile == NULL)
    {
      warning ("no dumpable objfile was found");
    }
  else
    {
      fnp = alloc_formnode ();
      lp = ftebuf;

      lp++;			/* Skip FTE size slot, filled in at the end. */
      *lp++ = TAG_objfile;	/* This is an objfile FTE */
      *lp++ = 0;		/* Zero the sibling reference slot. */

      /* Build an AT_name attribute for the objfile's name, and write
	 the name into the state data. */

      *lp++ = AT_name;
      *lp++ = (long) ftell (asfd -> fp);
      fwrite (objfile -> name, strlen (objfile -> name) + 1, 1, asfd -> fp);

      /* Build an AT_addr attribute for the virtual address to which the
	 objfile data is mapped (and needs to be remapped when read in). */

      base = mmap_base ();
      *lp++ = AT_addr;
      *lp++ = (long) base;

      /* Build an AT_aux_addr attribute for the address of the objfile
	 structure itself, within the dumpable data.  When we read the objfile
	 back in, we use this address as the pointer the "struct objfile". */

      *lp++ = AT_aux_addr;
      *lp++ = (long) objfile;

      /* Reposition in state file to next paging boundry so we can mmap the
	 dumpable objfile data when we reload it. */

      foffset = (long) mmap_page_align ((PTR) ftell (asfd -> fp));
      fseek (asfd -> fp, foffset, SEEK_SET);

      /* Build an AT_offset attribute for the offset in the state file to
	 the start of the dumped objfile data. */

      *lp++ = AT_offset;
      *lp++ = (long) ftell (asfd -> fp);

      /* Build an AT_size attribute for the size of the dumped objfile data. */

      breakval = mmap_sbrk (0);
      *lp++ = AT_size;
      *lp++ = breakval - base;

      /* Write the dumpable data. */ 

      fwrite ((char *) base, breakval - base, 1, asfd -> fp);

      /* Now finish up the FTE by filling in the size slot based on
	 how much of the ftebuf we have used, allocate some memory for
	 it hung off the form tree node, and copy it there. */

      ftebuf[0] = (lp - ftebuf) * sizeof (ftebuf[0]);
      fnp -> nodedata = (char *) xmalloc (ftebuf[0]);
      memcpy (fnp -> nodedata, ftebuf, ftebuf[0]);
    }
}

static void
load_state_command (arg_string, from_tty)
     char *arg_string;
     int from_tty;
{
  char *filename;
  char **argv;
  FILE *fp;
  struct cleanup *cleanups;
  
  dont_repeat ();

  if (arg_string == NULL)
    {
      error ("load-state takes a file name and optional state specifiers");
    }
  else if ((argv = buildargv (arg_string)) == NULL)
    {
      fatal ("virtual memory exhausted.", 0);
    }
  cleanups = make_cleanup (freeargv, argv);

  filename = tilde_expand (*argv);
  make_cleanup (free, filename);

  if ((fp = fopen (filename, "r")) == NULL)
    {
      perror_with_name (filename);
    }
  make_cleanup (fclose, fp);
  immediate_quit++;

  while (*++argv != NULL)
    {
      if (strcmp (*argv, "symbols") == 0)
	{
	  if (from_tty
	      && !query ("load symbol table state from file \"%s\"? ",
			 filename))
	    {
	      error ("Not confirmed.");
	    }
	  load_symbols (fp);
	}
      else
	{
	  error ("unknown state specifier '%s'", *argv);
	}
    }
  immediate_quit--;
  do_cleanups (cleanups);
}

/* ARGSUSED */
static void
dump_state_command (arg_string, from_tty)
     char *arg_string;
     int from_tty;
{
  char *filename;
  char **argv;
  sfd *asfd;
  struct cleanup *cleanups;
  
  dont_repeat ();

  if (arg_string == NULL)
    {
      error ("dump-state takes a file name and state specifiers");
    }
  else if ((argv = buildargv (arg_string)) == NULL)
    {
      fatal ("virtual memory exhausted.", 0);
    }
  cleanups = make_cleanup (freeargv, argv);

  filename = tilde_expand (*argv);
  make_cleanup (free, filename);

  /* Now attempt to create a fresh state file. */

  if ((asfd = sfd_fopen (filename, "w")) == NULL)
    {
      perror_with_name (filename);
    }
  make_cleanup (sfd_fclose, asfd);
  make_cleanup (free_formtree, NULL);
  immediate_quit++;

  /* Now that we have an open and initialized state file, seek to the
     proper offset to start writing state data and the process the
     arguments.  For each argument, write the state data and initialize
     a form-tree node for each piece of state data. */

  fseek (asfd -> fp, sizeof (sf_hdr), SEEK_SET);
  while (*++argv != NULL)
    {
      if (strcmp (*argv, "objfile") == 0)
	{
	  write_objfile_state (asfd);
	}
      else
	{
	  error ("unknown state specifier '%s'", *argv);
	}

    }

  /* We have written any state data.  All that is left to do now is
     write the form-tree and the file header. */

  write_formtree (asfd);
  write_header (asfd);

  immediate_quit--;
  do_cleanups (cleanups);
}

static char *
find_fte_by_walk (thisfte, endfte, tag)
     char *thisfte;
     char *endfte;
     long tag;
{
  char *found = NULL;
  char *nextfte;
  long thistag;
  long thissize;
  long siboffset;

  while (thisfte < endfte)
    {
      if ((thistag = *(long *)(thisfte + sizeof (long))) == tag)
	{
	  found = thisfte;
	  break;
	}
      else
	{
	  thissize =  *(long *)(thisfte);
	  siboffset = *(long *)(thisfte + (2 * sizeof (long)));
	  nextfte = thisfte + (siboffset != 0 ? siboffset : thissize);
	  found = find_fte_by_walk (thisfte + thissize, nextfte, tag);
	  thisfte = nextfte;
	}
    }
  return (found);
}

/* Walk the form-tree looking for a specific FTE type.  Returns the first
   one found that matches the specified tag. */

static char *
find_fte (asfd, tag)
     sfd *asfd;
     long tag;
{
  char *ftbase;
  char *ftend;
  char *ftep;
  char *found = NULL;

  if (fseek (asfd -> fp, asfd -> hdr.sf_ftoff, SEEK_SET) == 0)
    {
      ftbase = xmalloc (asfd -> hdr.sf_ftsize);
      ftend = ftbase + asfd -> hdr.sf_ftsize;
      if (fread (ftbase, asfd -> hdr.sf_ftsize, 1, asfd -> fp) == 1)
	{
	  ftep = find_fte_by_walk (ftbase, ftend, tag);
	  if (ftep != NULL)
	    {
	      found = xmalloc (*(long *)ftep);
	      memcpy (found, ftep, (int) *(long *)ftep);
	    }
	}
      free (ftbase);
    }
  return (found);
}

struct objfile *
objfile_from_statefile (asfd)
     sfd *asfd;
{
  struct objfile *objfile = NULL;
  char *ftep;
  long *thisattr;
  long *endattr;
  PTR base;
  long foffset;
  long mapsize;

  ftep = find_fte (asfd, TAG_objfile);
  thisattr = (long *) (ftep + 3 * sizeof (long));
  endattr = (long *) (ftep + *(long *)ftep);
  while (thisattr < endattr)
    {
      switch (*thisattr++)
	{
	  case AT_name:
	    /* Ignore for now */
	    thisattr++;
	    break;
	  case AT_addr:
	    base = (PTR) *thisattr++;
	    break;
	  case AT_aux_addr:
	    objfile = (struct objfile *) *thisattr++;
	    break;
	  case AT_offset:
	    foffset = *thisattr++;
	    break;
	  case AT_size:
	    mapsize = *thisattr++;
	    break;
	}
    }
  if (mmap_remap (base, mapsize, (int) fileno (asfd -> fp), foffset) != base)
    {
      print_sys_errmsg (asfd -> filename, errno);
      error ("mapping failed");
    }

  return (objfile);
}

#else

struct objfile *
objfile_from_statefile (asfd)
     sfd *asfd;
{
  error ("this version of gdb doesn't support reloading symtabs from state files");
}

#endif	/* HAVE_MMAP */

/* Close a state file, freeing all memory that was used by the state
   file descriptor, closing the raw file pointer, etc. */

void
sfd_fclose (asfd)
     sfd *asfd;
{
  if (asfd != NULL)
    {
      if (asfd -> fp != NULL)
	{
	  fclose (asfd -> fp);
	}
      if (asfd -> filename != NULL)
	{
	  free (asfd -> filename);
	}
      free (asfd);
    }
}

/* Given the name of a possible statefile, and flags to use to open it,
   try to open the file and prepare it for use.

   If the flags contain 'r', then we want to read an existing state
   file, so attempt to read in the state file header and determine if this
   is a valid state file.  If not, return NULL.

   Returns a pointer to a properly initialized state file descriptor if
   successful. */

sfd *
sfd_fopen (name, flags)
     char *name;
     char *flags;
{
  int success = 0;
  sfd *asfd;

  asfd = (sfd *) xmalloc (sizeof (sfd));
  (void) memset (asfd, 0, sizeof (sfd));
  asfd -> filename = xmalloc (strlen (name) + 1);
  (void) strcpy (asfd -> filename, name);

  if ((asfd -> fp = fopen (asfd -> filename, flags)) != NULL)
    {
      /* We have the file, now see if we are reading an existing file
	 or writing to a new file.  We don't currently support "rw". */
      if (strchr (flags, 'r') != NULL)
	{
	  if (fread ((char *) &asfd -> hdr, sizeof (asfd -> hdr), 1,
		     asfd -> fp) == 1)
	    {
	      if (SF_GOOD_MAGIC (asfd))
		{
		  success = 1;
		}
	    }
	}
      else
	{
	  /* This is a new state file.  Initialize various things. */
	  asfd -> hdr.sf_mag0 = SF_MAG0;
	  asfd -> hdr.sf_mag1 = SF_MAG1;
	  asfd -> hdr.sf_mag2 = SF_MAG2;
	  asfd -> hdr.sf_mag3 = SF_MAG3;
	  success = 1;
	}
    }

  if (!success)
    {
      sfd_fclose (asfd);
      asfd = NULL;
    }
  return (asfd);
  
}


void
_initialize_state ()
{

#ifdef HAVE_MMAP

  add_com ("load-state", class_support, load_state_command,
   "Load some saved gdb state from FILE.\n\
Select and load some portion of gdb's saved state from the specified file.\n\
The dump-state command may be used to save various portions of gdb's\n\
internal state.");

  add_com ("dump-state", class_support, dump_state_command,
   "Dump some of gdb's state to FILE.\n\
Select and dump some portion of gdb's internal state to the specified file.\n\
The load-state command may be used to reload various portions of gdb's\n\
internal state from the file.");

#endif	/* HAVE_MMAP */

}