aboutsummaryrefslogtreecommitdiff
path: root/gdb/state.c
blob: 64f86d6bb9f3f1e9a22eb54f26811c6fceff986b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
/* 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, FOPEN_RB)) == NULL)
    {
      perror_with_name (filename);
    }
  make_cleanup (fclose, fp);
  immediate_quit++;

  while (*++argv != NULL)
    {
      if (STREQ (*argv, "symbols"))
	{
	  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, FOPEN_WB)) == 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 (STREQ (*argv, "objfile"))
	{
	  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 */

}