[comp.sys.att] Looking for "undump" for SysV

ferencz@cwsys3.cwru.Edu (Don Ferencz) (01/24/89)

Hello, world:

I have been looking (high and low) for a UNIX SysV.3 version of
undump (or an equivalent a.out <-> core combining program).  I
have a rather generic one and have been unsuccessful in porting
it over...those things (data, tex, bss) tend to be rather
system specific!  I am running System V 3.0 on a AT&T 3B2/310,
and would like to get an "undumped" TeX image.  If anyone can
point me in the right direction, that would be well appreciated!

Thanks in advance!

===========================================================================
| Don Ferencz                       |  "And in the end/                   |
| ferencz@cwsys3.cwru.EDU           |   The love you take/                |
| Department of Systems Engineering |   Is equal to the love you make."   |
| Case Western Reserve University   |       -- The Beatles                |
===========================================================================

wilker@batcomputer.tn.cornell.edu (Clarence W. Wilkerson Jr.) (01/24/89)

The other way to go is to just set up a shell script:
eg. latex would be called to execute virtex &lplain.
On a Sun there was very little difference in speed in this
versus the "undumped" variety. Plus , virtex+ lplain is
smaller than undumped version, and only one copy of virtex
is required, even if you're using plain, latex and amstex.
Plus it's a lot easier to upgrade the .fmt files.

wescott@sauron.Columbia.NCR.COM (Mike Wescott) (01/24/89)

In article <404@cwjcc.CWRU.Edu> ferencz@cwsys3.cwru.Edu (Don Ferencz) writes:
> I have been looking (high and low) for a UNIX SysV.3 version of
> undump (or an equivalent a.out <-> core combining program).

As one poster suggested, it may not be that much of an advantage for TeX.

If you still want something, try unexec() out of GNU emacs.  Instead of
dumping core, set up your program to catch SIGQUIT and call unexec()
to directly produce an executable.

-- 
	-Mike Wescott
	 mike.wescott@ncrcae.Columbia.NCR.COM

joost@nixvia.UUCP (joost helberg) (01/27/89)

UNDUMP for coff-files:

#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
#	Makefile
#	scanargs.c
#	undump.c
#	undump.1
# This archive created: Fri Jan 27 14:31:58 1989
export PATH; PATH=/bin:$PATH
if test -f 'Makefile'
then
	echo shar: will not over-write existing file "'Makefile'"
else
cat << \SHAR_EOF > 'Makefile'
#/*
# *      @(#)Makefile	1.2     88/05/27     eg4
# */
BINDIR=/usr/local

all undump: undump.o scanargs.o
	cc -O -o undump undump.o scanargs.o

clean:
	rm *.o undump

install: undump
	cp undump $(BINDIR)
SHAR_EOF
fi # end of overwriting check
if test -f 'scanargs.c'
then
	echo shar: will not over-write existing file "'scanargs.c'"
else
cat << \SHAR_EOF > 'scanargs.c'
/*
		Version 7 compatible
	Argument scanner, scans argv style argument list.

	Some stuff is a kludge because sscanf screws up

	Gary Newman - 10/4/1979 - Ampex Corp.

	Modified by Spencer W. Thomas, Univ. of Utah, 5/81 to
	add args introduced by 	a flag, add qscanargs call,
	allow empty flags.

	Compiling with QUICK defined generates 'qscanargs' ==
	scanargs w/o floating point support; avoids huge size
	of scanf.

	If you make improvements we'd like to get them too.
	Jay Lepreau	lepreau@utah-20, decvax!{harpo,randvax}!utah-cs!lepreau
	Spencer Thomas	thomas@utah-20, decvax!{harpo,randvax}!utah-cs!thomas

	(There seems to be a bug here in that if the last option you have
	is a flag, and the user enters args incorrectly, sometimes the usage
	message printed will miss the null and go flying off thru core...)
							--jay for spencer
*/
#include <stdio.h>
#include <ctype.h>

#define YES 1
#define NO 0
#define ERROR(msg)  {fprintf(stderr, "msg\n"); goto error; }

char	*prformat();

#ifndef	QUICK
scanargs (argc, argv, format, arglist)
#else
qscanargs (argc, argv, format, arglist)
#endif
int     argc;
char  **argv;
char   *format;
int     arglist[];
{
#ifndef	QUICK
    _scanargs (argc, argv, format, &arglist);
#else
    _qscanargs (argc, argv, format, &arglist);
#endif
}

#ifndef	QUICK
_scanargs (argc, argv, format, arglist)
#else
_qscanargs (argc, argv, format, arglist)
#endif
int     argc;
char  **argv;
char   *format;
int    *arglist[];
{
    register    check;			/* check counter to be sure all argvs
					   are processed */
    register char  *cp;
    register    cnt;
    char    tmpflg;			/* temp flag */
    char    c;
    char    numnum;			/* number of numbers already processed
					   */
    char    numstr;			/* count # of strings already
					   processed */
    char    tmpcnt;			/* temp count of # things already
					   processed */
    char    required;
    char    exflag;			/* when set, one of a set of exclusive
					   flags is set */
    char    excnt;			/* count of which exclusive flag is
					   being processed */
    char   *ncp;			/* remember cp during flag scanning */
#ifndef	QUICK
    char   *cntrl;			/* control string for scanf's */
    char    junk[2];			/* junk buffer for scanf's */

    cntrl = "% %1s";			/* control string initialization for
					   scanf's */
#endif
    check = numnum = numstr = 0;
    cp = format;
    while (*cp)
    {
	required = NO;
	switch (*(cp++))
	{
	    default: 			/* all other chars */
		break;
	    case '!': 			/* required argument */
		required = YES;
	    case '%': 			/* not required argument */
		switch (tmpflg = *(cp++))
		{
		    case '-': 		/* argument is flag */
		    /* go back to label */
			ncp = cp-1;	/* remember */
			cp -= 3;
			for (excnt = exflag = 0
				; *cp != ' ' && !(*cp=='-' &&(cp[-1]=='!'||cp[-1]=='%'));
				(--cp, excnt++))
			{
			    for (cnt = 1; cnt < argc; cnt++)
			    {
			    /* flags all start with - */
				if (*argv[cnt] == '-' && !isdigit(argv[cnt][1]))
				    if (*(argv[cnt] + 1) == *cp)
				    {
					if (*(argv[cnt] + 2) != 0)
					    ERROR (extra flags ignored);
					if (exflag)
					    ERROR (more than one exclusive flag chosen);
					exflag++;
					required = NO;
					check += cnt;
					**arglist |=
					    (1 << excnt);
					break;
				    }
			    }
			}
			if (required)
			    ERROR (flag argument missing);
			cp = ncp;
			if (!exflag)	/* if no flags scanned, skip */
			{
			    while (*++cp != ' ' && *cp)
				if (*cp == '!' || *cp == '%')
				    arglist++;
			}
			else
			    cp++;	/* skip over - */
			while (*cp == ' ')
			    cp++;
			arglist++;
			break;
		    case 's': 		/* char string */
		    case 'd': 		/* decimal # */
		    case 'o': 		/* octal # */
		    case 'x': 		/* hexadecimal # */
#ifndef	QUICK
		    case 'f': 		/* floating # */
#endif
		    case 'D': 		/* long decimal # */
		    case 'O': 		/* long octal # */
		    case 'X': 		/* long hexadecimal # */
#ifndef	QUICK
		    case 'F': 		/* double precision floating # */
#endif
			tmpcnt = tmpflg == 's' ? numstr : numnum;
			for (cnt = 1; cnt < argc; cnt++)
			{
			    if (tmpflg == 's')/* string */
			    {
				if ((c = *argv[cnt]) == '-')
				    continue;
				if (c >= '0' && c <= '9')
				    continue;
				if (tmpcnt-- != 0)
				    continue;
				**arglist = (int) argv[cnt];
				check += cnt;
				numstr++;
				required = NO;
				break;
			    }
			    if (*argv[cnt] == '-')
			    {
				if(!isdigit (*(argv[cnt] + 1)))
				    continue;
			    }
			    else if (!isdigit(*argv[cnt]))
				continue;
			    if (tmpcnt-- != 0)/* skip to new one */
				continue;
#ifndef	QUICK
			/* kludge for sscanf */
			    if ((tmpflg == 'o' || tmpflg == 'O')
				    && *argv[cnt] > '7')
				ERROR (Bad numeric argument);
			    cntrl[1] = tmpflg;/* put in conversion */
			    if (sscanf (argv[cnt], cntrl, *arglist
					,junk) != 1)
#else
			    if (numcvt(argv[cnt], tmpflg, *arglist) != 1)
#endif
				ERROR (Bad numeric argument);
			    check += cnt;
			    numnum++;
			    required = NO;
			    break;
			}
			if (required)
			    switch (tmpflg)
			    {
				case 'x':
				case 'X':
				    ERROR (missing hexadecimal argument);
				case 's':
				    ERROR (missing string argument);
				case 'o':
				case 'O':
				    ERROR (missing octal argument);
				case 'd':
				case 'D':
				    ERROR (missing decimal argument);
				case 'f':
				case 'F':
				    ERROR (missing floating argument);
			    }
			arglist++;
			while (*cp == ' ')
			    cp++;
			break;
		    default: 		/* error */
			fprintf (stderr, "error in call to scanargs\n");
			return (0);
		}
	}
    }
/*  Count up empty flags */
    for (cnt=1; cnt<argc; cnt++)
	if (argv[cnt][0] == '-' && argv[cnt][1] == 0)
	    check += cnt;
 /* sum from 1 to N = n*(n+1)/2 used to count up checks */
    if (check != (((argc - 1) * argc) / 2))
	ERROR (extra arguments not processed);
	    return (1);

error:
    fprintf (stderr, "usage : ");
    if (*(cp = format) != ' ')
	while (putc (*cp++, stderr) != ' ');
    else
	fprintf (stderr, "?? ");
    while (*cp == ' ')
	cp++;
    prformat (cp, NO);
    return 0;
}

char *
prformat (format, recurse)
char   *format;
{
    register char  *cp;
    char    required;

    cp = format;
    if (recurse)
	putc (' ', stderr);
    required = NO;
    while (*cp)
    {
	if (recurse && *cp == ' ')
	    break;
	switch (*cp)
	{
	    default:
		cp++;
		break;
	    case '!':
		required = YES;
	    case '%':
		switch (*++cp)
		{
		    case '-': 		/* flags */
			if (!required)
			{
			    putc ('[', stderr);
			    putc ('-', stderr);
			}
			else
			{
			    putc ('-', stderr);
			    putc ('{', stderr);
			}
			cp = format;
			while (*cp != '%' && *cp != '!')
			    putc (*cp++, stderr);
			if (required)
			    putc ('}', stderr);
			cp += 2;	/* skip !- or %- */
			if (*cp != ' ')
			    cp = prformat (cp, YES);
					/* this is a recursive call */
			if (!required)
			    putc (']', stderr);
			break;
		    case 's': 		/* char string */
		    case 'd': 		/* decimal # */
		    case 'o': 		/* octal # */
		    case 'x': 		/* hexadecimal # */
		    case 'f': 		/* floating # */
		    case 'D': 		/* long decimal # */
		    case 'O': 		/* long octal # */
		    case 'X': 		/* long hexadecimal # */
		    case 'F': 		/* double precision floating # */
			if (!required)
			    putc ('[', stderr);
			for (; format < cp - 1; format++)
			    putc (*format, stderr);
			if (!required)
			    putc (']', stderr);
			break;
		    default:
			break;
		}
		required = NO;
		format = ++cp;
		putc (' ', stderr);
	}
    }
    if (!recurse)
	putc ('\n', stderr);
    return (cp);
}

#ifdef	QUICK
numcvt(str, conv, val)
register char *str;
char conv;
int *val;
{
    int base, neg = 0;
    register unsigned int d;
    long retval = 0;
    register char *digits;
    extern char *index();
    if (conv == 'o' || conv == 'O')
	base = 8;
    else if (conv == 'd' || conv == 'D')
	base = 10;
    else if (conv == 'x' || conv == 'X')
	base = 16;
    else
	return 0;

    if (*str == '-')
    {
	neg = 1;
	str++;
    }
    while (*str)
    {
	if (*str >= '0' && *str < '0'+base)
	    d = *str - '0';
	else if (base == 16 && *str >= 'a' && *str <= 'f')
	    d = 10 + *str - 'a';
	else if (base == 16 && *str >= 'A' && *str <= 'F')
	    d = 10 + *str - 'A';
	else
	    return 0;
	retval = retval*base + d;
	str++;
    }
    if (neg)
	retval = -retval;
    if (conv == 'D' || conv == 'O' || conv == 'X')
	*(long *) val = retval;
    else
	*val = (int) retval;
    return 1;
}
#endif	QUICK

SHAR_EOF
fi # end of overwriting check
if test -f 'undump.c'
then
	echo shar: will not over-write existing file "'undump.c'"
else
cat << \SHAR_EOF > 'undump.c'
/*
 *      %W%     %E%     %Q%
 */

/*
 * This program was advertised on unix-wizards.  I have had such a large
 * response I'm sending it out to the world.
 * 
 * Here is the source.  It works fine under 4.1bsd, I see no fundamental
 * reason why it shouldn't work on an 11. (Except possibly small format
 * changes in exec header or user structure.)  No documentation yet.
 * Usage is
 *        undump new-a.out-file [old-a.out-file] [core-file]
 * where old-a.out-file and core-file default to "a.out" and "core",
 * respectively.  Probably should have an option to not require
 * old-a.out-file if the core came from a 407 file.
 * 
 * It doesn't preserve open files, and the program is re-entered at main
 * when you run it.  It's used locally to dump a lisp and restart it.
 * 
 * It requires a local subroutine called scanargs, somewhat similar to
 * getopt (I think).  You should be able to easily get around this, though.
 * =Spencer
 *
 * Changed for COFF format by Piet van Oostrum (piet@ruuinfvax.uucp)
 * 18-Aug-1987
 *
 */


/*
 * undump.c - Convert a core file to an a.out.
 *
 * Author:	Spencer W. Thomas
 * 		Computer Science Dept.
 * 		University of Utah
 * Date:	Wed Feb 17 1982
 * Copyright (c) 1982 Spencer W. Thomas
 *
 * Usage:
 * undump new-a.out [a.out] [core]
 */

#include <sys/types.h>
#include <signal.h>
#include <sys/dir.h>
#include <sys/sysmacros.h>
#include <stdio.h>
#include <sys/param.h>
#include <sys/user.h>
#include <sys/stat.h>
#include <a.out.h>
#include <core.h>

#define UPAGES		2 /*  size of u-structure in clicks */
#define OMAGIC 0150
#define PSIZE	    2048 /* click-size */

struct user u;
static struct filehdr f_hdr;
static struct aouthdr f_ohdr;
static struct scnhdr thdr, dhdr, bhdr;
long	OldSym;

main(argc, argv)
char **argv;
{
    char *new_name, *a_out_name = "a.out", *core_name = "core";
    int new, a_out, core;

    if (scanargs(argc, argv, "undump new-a.out!s a.out%s core%s",
	    &new_name, &a_out_name, &core_name)
		    != 1)
	exit(1);

    if ((a_out = open(a_out_name, 0)) <0)
    {
	perror(a_out_name);
	exit(1);
    }
    if ((core = open(core_name, 0)) <0)
    {
	perror(core_name);
	exit(1);
    }
    if ((new = creat(new_name, 0666)) <0)
    {
	perror(new_name);
	exit(1);
    }

    read_u(core);
    make_hdr(new, a_out);
    copy_text(new, a_out);
    copy_data(new, core);
    copy_sym(new, a_out);
    close(new);
    close(core);
    close(a_out);
    mark_x(new_name);
}

/*
 * read the u structure from the core file.
 */
read_u(core)
int core;
{
    if (read (core, &u, sizeof u) != sizeof u )
    {
	perror("Couldn't read user structure from core file");
	exit(1);
    }
}

/*
 * Make the header in the new a.out from the header in the old one
 * modified by the new data size.
 */
make_hdr(new, a_out)
     int new, a_out;
{
  auto struct scnhdr scntemp;		/* Temporary section header */
  register int scns;
  
  if (read (a_out, &f_hdr, sizeof (f_hdr)) != sizeof (f_hdr)
      || f_hdr.f_opthdr == 0
      || read (a_out, &f_ohdr, sizeof (f_ohdr)) != sizeof (f_ohdr))
    {
      perror("Couldn't read header from a.out file");
      exit(1);
    }
  
  /*
  if BADMAG(f_ohdr)
    {
      fprintf(stderr, "a.out file doesn't have legal magic number\n");
      exit(1);
    }
	*/ /* not done for a while JAH */
  /*
  if (f_ohdr.magic != u.u_exdata.ux_mag ||
      f_ohdr.tsize != u.u_exdata.ux_tsize ||
      f_ohdr.dsize != u.u_exdata.ux_dsize ||
      f_ohdr.entry != u.u_exdata.ux_entloc)
    {
      fprintf(stderr, "Core file didn't come from this a.out\n");
      exit(1);
    }
	 *//* not done for a while */
  f_hdr.f_timdat = time(0);
  printf("Data segment size was %u", f_ohdr.dsize);
  f_ohdr.dsize = ctob(u.u_dsize);
  f_ohdr.bsize = 0;			/* all data is inited now! */
  printf(" now is %u\n", f_ohdr.dsize);
  
  /* Loop through section headers, copying them in */
  for (scns = f_hdr.f_nscns; scns > 0; scns--) {
    if (read (a_out, &scntemp, sizeof (scntemp)) != sizeof (scntemp))
      {
	perror ("Couldn't read section header from a.out file");
	exit(1);
      }
    if (scntemp.s_scnptr > 0L)
      {
      }
    if (strcmp (scntemp.s_name, ".text") == 0)
      {
	thdr = scntemp;
      }
    else if (strcmp (scntemp.s_name, ".data") == 0)
      {
	dhdr = scntemp;
      }
    else if (strcmp (scntemp.s_name, ".bss") == 0)
      {
	bhdr = scntemp;
      }
  }	    
  
  dhdr.s_size = f_ohdr.dsize;
  bhdr.s_size = 0L;
  bhdr.s_scnptr = 0L;
  
  OldSym = f_hdr.f_symptr;

  if (f_hdr.f_symptr > 0L)
    {
      f_hdr.f_symptr = dhdr.s_scnptr + dhdr.s_size;
    }
  
  if (thdr.s_lnnoptr > 0L) 
    {
      thdr.s_lnnoptr += 0;  /* ihave no soultion (i.e time) for that! JAH */
    }
  
  if (write(new, &f_hdr, sizeof f_hdr) != sizeof f_hdr
      || write(new, &f_ohdr, sizeof f_ohdr) != sizeof f_ohdr
      || write (new, &thdr, sizeof (thdr)) != sizeof (thdr)
      || write (new, &dhdr, sizeof (dhdr)) != sizeof (dhdr)
      || write (new, &bhdr, sizeof (bhdr)) != sizeof (bhdr))
    {
      perror("Couldn't write header to new a.out file");
      exit(1);
    }
  
  return (0);
}

/*
 * Copy the text from the a.out to the new a.out
 */
copy_text(new, a_out)
     int new, a_out;
{
  char page[PSIZE];
  int txtcnt = f_ohdr.tsize;
  
  lseek (new, thdr.s_scnptr, 0);
  lseek(a_out, thdr.s_scnptr, 0);
  if (f_ohdr.magic == OMAGIC)
    {
      printf("a.out file is not shared text, getting text from core file\n");
      return;
    }
  while (txtcnt >= PSIZE)
    {
      if (read(a_out, page, PSIZE) != PSIZE)
	{
	  perror("Read failure on a.out text");
	  exit(1);
	}
      if (write(new, page, PSIZE) != PSIZE)
	{
	  perror("Write failure in text segment");
	  exit(1);
	}
      txtcnt -= PSIZE;
    }
  if (txtcnt)
    {
      if (read(a_out, page, txtcnt) != txtcnt)
	{
	  perror("Read failure on a.out text");
	  exit(1);
	}
      if (write(new, page, txtcnt) != txtcnt)
	{
	  perror("Write failure in text segment");
	  exit(1);
	}
    }
}

/*
 * copy the data from the core file to the new a.out
 */
copy_data(new, core)
     int new, core;
{
  char page[PSIZE];
  int datacnt = f_ohdr.dsize;
  lseek (new, dhdr.s_scnptr, 0);
  
  if (f_ohdr.magic == OMAGIC)
    datacnt += f_ohdr.tsize;
  lseek(core, ctob(UPAGES), 0);
  while (datacnt >= PSIZE)
    {
      if (read(core, page, PSIZE) != PSIZE)
	{
	  perror("Read failure on core data");
	  exit(1);
	}
      if (write(new, page, PSIZE) != PSIZE)
	{
	  perror("Write failure in data segment");
	  exit(1);
	}
      datacnt -= PSIZE;
    }
  if (datacnt)
    {
      if (read(core, page, datacnt) != datacnt)
	{
	  perror("Read failure on core data");
	  exit(1);
	}
      if (write(new, page, datacnt) != datacnt)
	{
	  perror("Write failure in data segment");
	  exit(1);
	}
    }
}

/*
 * Copy the relocation information and symbol table from the a.out to the new
 */
copy_sym(new, a_out)
     int new, a_out;
{
  char page[PSIZE];
  int n;
  
  lseek(a_out, OldSym, 0);	/* skip over data segment */
  while ((n = read(a_out, page, PSIZE)) > 0)
    {
      if (write(new, page, n) != n)
	{
	  perror("Error writing symbol table to new a.out");
	  fprintf(stderr, "new a.out should be ok otherwise\n");
	  return;
	}
    }
  if (n < 0)
    {
      perror("Error reading symbol table from a.out");
      fprintf(stderr, "new a.out should be ok otherwise\n");
    }
}

/*
 * After succesfully building the new a.out, mark it executable
 */
mark_x(name)
char *name;
{
    struct stat sbuf;
    int um;

    um = umask(777);
    umask(um);
    if (stat(name, &sbuf) == -1)
    {
	perror ("Can't stat new a.out");
	fprintf(stderr, "Setting protection to %o\n", 0777 & ~um);
	sbuf.st_mode = 0777;
    }
    sbuf.st_mode |= 0111 & ~um;
    if (chmod(name, sbuf.st_mode) == -1)
	perror("Couldn't change mode of new a.out to executable");

}
SHAR_EOF
fi # end of overwriting check
if test -f 'undump.1'
then
	echo shar: will not over-write existing file "'undump.1'"
else
cat << \SHAR_EOF > 'undump.1'
.TH UNDUMP 1 "University of Utah"
.SH NAME
undump \- convert a core dump to an executable a.out file
.SH SYNOPSIS
undump new-a.out-file [old-a.out-file] [core-file]
.SH DESCRIPTION
Undump takes a core dump file and the executable "a.out" file which
caused it and produces a new executable file with all static
variables initialised to the values they held at the time of the
core dump.  It is primarily useful for programs which take a long time
to initialise themselves, e.g. Emacs.  The idea is to go through all of
the initialisations and then create a core dump (e.g. with the abort()
call).  One then uses undump to make a new executable file with all of it
done.  This usually implies the use of a global flag variable which says
whether or not initialisation has been done.
.PP
Undump's arguments, old-a.out-file and core-file, default to "a.out" and
"core", respectively.
.PP
A few things to keep in mind about undump:
.IP
It doesn't preserve open files.
.IP
The program will be re-entered at the beginning of main(), not at the point
where the core dump occurred.
.SH BUGS
Probably should have an option to not require
old-a.out-file if the core came from a 407 file.
SHAR_EOF
fi # end of overwriting check
#	End of shell archive
exit 0




   |
   |   _   _   _  |
   |  / ) / ) / ) --
 __) (_/ (_/   /  (__

Tel: +3473 75154
Adr: nixdorf computer bv
	 mijlweg 9
	 4124 pj vianen
	 the netherlands
uucp:  ....mcvax!unido!nixpbe!helberg.via

daveb@geaclib.UUCP (David Collier-Brown) (01/28/89)

> In article <404@cwjcc.CWRU.Edu> ferencz@cwsys3.cwru.Edu (Don Ferencz) writes:
>> I have been looking (high and low) for a UNIX SysV.3 version of
>> undump (or an equivalent a.out <-> core combining program).
 
From article <1527@sauron.Columbia.NCR.COM>, by wescott@sauron.Columbia.NCR.COM (Mike Wescott):
> As one poster suggested, it may not be that much of an advantage for TeX.

  And in general, you want a facility to store configuration
information in an easy-to-access manner. Dump/undump and unexec are
two well-known ways of doing this, but there are others.  One I like
is to write a (necessarily non-portable) binary file from the
contents of a struct, having created it with a #ifdef'd version of
the standard source.
  This doesn't really work for TeX, though, as you'd have to write a
**remarkably** large chunk of core (;-)).
  Or, if you don't like non-portable tricks, have the configuration
compiled to a user-specific library and dynamically links it by name
(this assumes that you have dynamic linking, of course).

--adev
-- 
 David Collier-Brown.  | yunexus!lethe!dave
 Interleaf Canada Inc. |
 1550 Enterprise Rd.   | He's so smart he's dumb.
 Mississauga, Ontario  |       --Joyce C-B