/* ********************************************************************** *\
 *         Copyright IBM Corporation 1988,1991 - All Rights Reserved      *
 *        For full copyright information see:'andrew/config/COPYRITE'     *
\* ********************************************************************** */

/*
	$Disclaimer: 
 * Permission to use, copy, modify, and distribute this software and its 
 * documentation for any purpose is hereby granted without fee, 
 * provided that the above copyright notice appear in all copies and that 
 * both that copyright notice, this permission notice, and the following 
 * disclaimer appear in supporting documentation, and that the names of 
 * IBM, Carnegie Mellon University, and other copyright holders, not be 
 * used in advertising or publicity pertaining to distribution of the software 
 * without specific, written prior permission.
 * 
 * IBM, CARNEGIE MELLON UNIVERSITY, AND THE OTHER COPYRIGHT HOLDERS 
 * DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING 
 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.  IN NO EVENT 
 * SHALL IBM, CARNEGIE MELLON UNIVERSITY, OR ANY OTHER COPYRIGHT HOLDER 
 * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY 
 * DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, 
 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS 
 * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 
 * OF THIS SOFTWARE.
 *  $
*/

#ifndef NORCSID
#define NORCSID
static char rcsid[]="$Header: /afs/cs.cmu.edu/project/atk-dist/auis-6.3/overhead/class/machdep/dec_mach/RCS/doload.c,v 1.4 1992/12/15 20:58:42 rr2b R6tape $";
#endif

/* 
 *	doload.c - dynamic loader for class system
 *
 *	Author:  Zalman Stern July 1989
 *      (Rewritten to handle non-G0 code by Zalman Stern May 1991.)
 */

#include <stdio.h>
#include <a.out.h>
#include <setjmp.h>
#include <mips/cachectl.h>
#include <doload.h>

#include <andrewos.h> /* sys/types.h */

char *malloc();
char *realloc();
long lseek();

/* External variables defined during the final link process which provide a
 * pool of gp addressable space.
 */
extern char *mips_GlobalArea; 	/* The pool */
extern int mips_GlobalSize;	/* Amount of space left in the pool */

int doload_trace = 0;		/* nonzero if debugging */

extern struct globaltab {
    long entrypoint;	/* entry point value */
    char *entryname;	/* symbolic name */
} globals[];

extern long globalcount;

#include "../common/safe.h"

static char *read_section(e, offset, buffer, length)
    struct doload_environment *e;
    long offset;
    char *buffer;
    long length;
{

    if (buffer == NULL) {
        buffer = (char *) safe_malloc(e, length);
    }
    safe_lseek(e, offset, L_SET);
    safe_read(e, buffer, length);
    return buffer;
}

/* initialize state */
static void doload_setup(e, inFD, mode)
    struct doload_environment *e;
    int inFD;
    doload_mode mode;
{
    e->mode = mode;
    e->fd = inFD;
    e->problems = 0;
    e->sections = NULL;
    e->text = NULL;
    e->gp_pool = NULL;
    bzero(e->relocSection, sizeof(e->relocSection));
    e->symtab = NULL;
    e->stringtab = NULL;
    e->common_space = NULL;
    e->common_size = 0;
    return;
}

/* tear down environment */

static void doload_cleanup_success(e)
    struct doload_environment *e;
{
    struct doload_section *section;

    ENUMERATE_SECTIONS(section)
        safe_free((char *)section->rtab);
    safe_free((char *)e->sections);
    safe_free((char *)e->symtab);
    safe_free(e->stringtab);
}

static void doload_cleanup(e)
    struct doload_environment *e;
{
    if (e->problems > 0) {
	e->problems = 0;
	doload_punt(e, "Errors while processing");
    }

    doload_cleanup_success(e);

    safe_free((char *)e->text);
/* Return space to global area pool */
    if (e->gp_pool != NULL) {
        mips_GlobalSize += mips_GlobalArea - e->gp_pool;
        mips_GlobalArea = e->gp_pool;
    }
}

static struct globaltab *find_global(name)
    char *name;
{
    register struct globaltab *thisGlobal;

    for (thisGlobal = globals;
         thisGlobal < globals + globalcount &&
          strcmp(thisGlobal->entryname, name) != 0;
         thisGlobal++ )
        ;
    if (thisGlobal < globals + globalcount)
        return thisGlobal;
    else
        return NULL;
}

#define ROUNDUP4(value)  (((value) + 3) & (~0x3))
#define ROUNDUP8(value)  (((value) + 7) & (~0x7))

/* Find the amount of space needed for unallocated common symbols.
 * Doesn't included common symbols which have to be allocated in the global
 * area.
 */
static unsigned long common_space(e)
    struct doload_environment *e;
{
    pEXTR sp;
    pEXTR sbound;
    unsigned long common_total = 0;

    for (sp = e->symtab; sp < e->symtab + e->symheader.iextMax; sp++)
        if (find_global(e->stringtab + sp->asym.iss) == NULL && sp->asym.sc
	    == scCommon && sp->asym.value > 0)
            common_total += ROUNDUP8(sp->asym.value);
    return common_total;
}

static char *common_alloc(e, size)
struct doload_environment *e;
unsigned long size;
{
    char *ret_val;

    size = ROUNDUP8(size);

    if (size > e->common_size)
	doload_punt(e, "Internal error: out of common space.");

    ret_val = e->common_space;
    e->common_size -= size;
    e->common_space += size;

    return ret_val;
}

static char *gp_alloc(e, size)
struct doload_environment *e;
unsigned long size;
{
    char *ret_val;

    if (size > mips_GlobalSize)
	doload_punt(e, "Insufficient global area space to load module.");

    /* This is recorded so that space can be free'd in case of error. */
    if (e->gp_pool == NULL)
	e->gp_pool = mips_GlobalArea;

    /* Force 8 byte alignment if size is greater than 4. Yes, its a hack. */
    if (size > 4)
	ret_val = (char *) ROUNDUP4((unsigned long) mips_GlobalArea);
    else
	ret_val = (char *) ROUNDUP8((unsigned long) mips_GlobalArea);

    mips_GlobalSize -= (ret_val - mips_GlobalArea) + size;
    mips_GlobalArea = ret_val + size;

    return ret_val;
}

/* Used to specify different loading types for each section. */
#define FILL_IGNORE -1
#define FILL_READ 0
#define FILL_ZERO 1
#define FILL_MAX 1

#define WHERE_NORMAL 0
#define WHERE_GLOBAL 1
#define WHERE_MAX 1

#define _TEXT ".text"
#define	_DATA	".data"
#define _INIT ".init"
#define _FINI ".fini"
#define _LIB ".lib"
#define _RDATA ".rdata"
#define _SBSS ".sbss"
#define _LIT8 ".lit8"
#define _LIT4 ".lit4"
#define _SDATA ".sdata"
#define S_NRELOC_OVFL 0x20000000

#define	SECT_TEXT	1
#define	SECT_RDATA	2
#define	SECT_DATA	3
#define	SECT_SDATA	4
#define	SECT_SBSS	5
#define	SECT_BSS	    6
#define	SECT_LIT8	8
#define	SECT_LIT4	9

struct doload_section_description {
    char *name;	/* Character string naming section. */
    int fill;	/* How to get contents of section. */
    int where;	/* Where to allocate memory for section. */
    int relSecNum;	/* Section number constant in relocation record. */
} sectionDescs[] = {
    _TEXT,	FILL_READ, WHERE_NORMAL, SECT_TEXT,
    _RDATA,	FILL_READ, WHERE_NORMAL, SECT_RDATA,
    _DATA,	FILL_READ, WHERE_NORMAL, SECT_DATA,
    _SDATA,	FILL_READ, WHERE_GLOBAL, SECT_SDATA,
    _SBSS,	FILL_ZERO, WHERE_GLOBAL, SECT_SBSS,
    _LIT4,	FILL_READ, WHERE_GLOBAL, SECT_LIT4,
    _LIT8,	FILL_READ, WHERE_GLOBAL, SECT_LIT8,
    _BSS,	FILL_ZERO, WHERE_NORMAL, SECT_BSS
};

struct reloc {
    long	    vaddr;	
    unsigned	symndx:24;
    unsigned	unused:3;
    unsigned	type:4;
    unsigned	ext:1;
};


/* read module into memory */

/* This routine reads all of the interesting sections of a object module into memory so they can be worked on by other parts of the code. Some of these sections are permanent while others are only sued during the loading phase and are freed afterwards. (List specific sections used by this code.) */
static doload_read(e)
    struct doload_environment *e;
{
    long stringlen;	/* length of string table */
    long sectionSizes[WHERE_MAX + 1];
    long totalRelocs = 0;
    long globalNeeded;
    struct doload_section *section;
    char *thisStart[WHERE_MAX + 1];

    /* read header */
    safe_read(e, (char *)&(e->filehdr), (long)sizeof e->filehdr);
    safe_read(e, (char *)&(e->aouthdr), (long)sizeof e->aouthdr);

    e->sections = (struct doload_section *) safe_malloc(e, e->filehdr.f_nscns *
                                                sizeof(struct doload_section));
    bzero(e->sections, e->filehdr.f_nscns * sizeof(struct doload_section));
    bzero(sectionSizes, sizeof(sectionSizes));

/* Sum sizes for sections so we can allocate space to laod them. */
    ENUMERATE_SECTIONS(section) {
        struct scnhdr *tempSection = &section->header;
        struct doload_section_description *thisDesc;

        safe_read(e, (char *)tempSection, (long)sizeof(*tempSection));
        for (thisDesc = sectionDescs; thisDesc < sectionDescs +
             (sizeof(sectionDescs) / sizeof(sectionDescs[0]))
             && strcmp(tempSection->s_name, thisDesc->name) != 0; thisDesc++)
            ;

        if (thisDesc < sectionDescs +
             (sizeof(sectionDescs) / sizeof(sectionDescs[0]))) {
            sectionSizes[thisDesc->where] += tempSection->s_size;
            totalRelocs += tempSection->s_nreloc;
            section->fill = thisDesc->fill;
            section->where = thisDesc->where;
	    e->relocSection[thisDesc->relSecNum] = section;
        }
        else if (tempSection->s_size != 0) {

            char buffer[200];

            sprintf(buffer, "doload: Unexpected non-zero section named %.100s encountered.", tempSection->s_name);
            doload_punt(e, buffer);
        }
        else
            section->fill = FILL_IGNORE;
    }

    if (strcmp(e->sections[0].header.s_name, _TEXT) != 0 ||
        e->sections[0].header.s_size == 0)
        doload_punt(e, "Missing or empty text section!");


    read_section(e, e->filehdr.f_symptr, &e->symheader, sizeof(e->symheader));
    e->symtab = (pEXTR) read_section(e, e->symheader.cbExtOffset, NULL,
                                     e->symheader.iextMax * sizeof(EXTR));
    e->stringtab = (char *) read_section(e, e->symheader.cbSsExtOffset, NULL,
                                         e->symheader.issExtMax);

    e->common_size = common_space(e);
    e->total_size = ROUNDUP8(sectionSizes[WHERE_NORMAL]) + e->common_size;
    e->text = safe_malloc(e, ROUNDUP8(sectionSizes[WHERE_NORMAL]) +
                             e->common_size);
    e->common_space = e->text + ROUNDUP8(sectionSizes[WHERE_NORMAL]);
    thisStart[WHERE_NORMAL] = e->text;
    thisStart[WHERE_GLOBAL] = gp_alloc(e, sectionSizes[WHERE_GLOBAL]);

/* Load per-section data. */
    ENUMERATE_SECTIONS(section) {
        long size;
        int where;
        unsigned long file_offset = section->header.s_relptr;

        if (section->fill == FILL_IGNORE)        
            continue;

        size = section->header.s_size;
        where = section->where;
        if (section->fill == FILL_READ)
            read_section(e, section->header.s_scnptr,
                         thisStart[where],
                         size);
        else
            bzero(thisStart[where], size);
        section->contents = thisStart[where];
        thisStart[where] += size; 

/* Get relocation info for each section.
 * Must handle case where number of relocations wouldn't fit in the short some
 * bozo defined for COFF to hold the number of relocations in...
 */
        if (section->header.s_flags & S_NRELOC_OVFL) {
            struct reloc tempReloc;

            read_section(e, file_offset, &tempReloc, sizeof(tempReloc));
            file_offset += sizeof(tempReloc);
            section->num_relocs = tempReloc.vaddr;
        }
        else
            section->num_relocs = section->header.s_nreloc;

        if (section->num_relocs != 0)
            section->rtab =
                (struct reloc *) read_section(e, file_offset, NULL, 
                                              section->num_relocs *
                                              sizeof(struct reloc));
    }
}

/* read and relocate module
 * Returns modules entrypoint or NULL if error.
 */
void *(* doload(inFD, name, bp, lenP, path) )()
    int inFD;			/* open fd for package file */
    char *name;			/* name of package being loaded */
    char **bp;			/* base address of package */
    long *lenP;			/* size of text segment */
    char *path;			/* Pathname of package being loaded */
				/* Path is used by the MACH loader, not this one */
{
    struct doload_environment E;
    register struct doload_environment *e = &E;
    unsigned long n;	/* number of relocation items */
    struct reloc *rp;
    struct doload_section *section;

    /* set up environment */
    doload_setup(e, inFD, Load);
    if (setjmp(e->errorJump)) {
	doload_cleanup(e);
	return NULL;
    }

    /* read module into memory */
    doload_read(e);

    /* Get addresses for known global symbols. Errors for unresolved */
    /* externals show up here. */
    if (e->symheader.iextMax != 0)
        doload_finish_resolution(e);

    /* Relocate each section in turn. Sum section sizes as side effect. */
    ENUMERATE_SECTIONS(section)
        relocate_section(e, section);

    /* all done */
    if (doload_trace)
	printf( " %s: text = 0x%.8x  data = 0x%.8x  entry = 0x%.8x\n",
		name, e->text, e->sections[1].contents, e->text +
               e->aouthdr.entry - e->aouthdr.text_start);

    if (bp != NULL)
        *bp = e->text;
    if (lenP != NULL)
        *lenP = e->total_size;

#if 0
    if (cacheflush(e->text, e->total_size, BCACHE) != 0) {
#else
/* Ultrix 3.0 does not have cacheflush in libc.a, however, the kernel on my
 * machine implements it and it is needed to make doindex work...
 */
#include <sys/syscall.h>
#define MIPS_CACHEFLUSH 0x104

    if (syscall(SYS_sysmips, MIPS_CACHEFLUSH, e->text, e->total_size,
                BCACHE, 0) == -1) {
#endif
        perror("doload - cacheflush");
        doload_punt(e, "Could not flush caches in doload.");
    }

    doload_cleanup_success(e);

    return (void *(*)()) (e->text + e->aouthdr.entry - e->aouthdr.text_start);
}

/* Lookup table used to give useful information in error messages. This
 * table comes from /usr/include/symconst.h.
 */
static char *doload_symbolClasses[] = {
    "scNil",
    "scText",		/* text symbol */
    "scData",		/* initialized data symbol */
    "scBss",		/* un-initialized data symbol */
    "scRegister",	/* value of symbol is register number */
    "scAbs",		/* value of symbol is absolute */
    "scUndefined",	/* who knows? */
    "scCdbLocal",	/* variable's value is IN se->va.?? */
    "scBits",		/* this is a bit field */
    "scCdbSystem or scDbx",    /* variable's value is IN CDB's address space */
                               /* overlap dbx internal use */
    "scRegImage",	/* register value saved on stack */
    "scInfo",		/* symbol contains debugger information */
    "scUserStruct",	/* address in struct user for current process */
    "scSData",		/* load time only small data */
    "scSBss",		/* load time only small common */
    "scRData",		/* load time only read only data */
    "scVar",		/* Var parameter (fortran,pascal) */
    "scCommon",		/* common variable */
    "scSCommon",	/* small common */
    "scVarRegister",	/* Var parameter in a register */
    "scVariant",	/* Variant record */
    "scSUndefined",	/* small undefined(external) data */
    "scInit",		/* .init section symbol */
    "scBasedVar",	/* Fortran or PL/1 ptr based var */ 
    "scMax",
};

static char *symbol_class_name(class_number)
    int class_number;
{
    if (class_number < (sizeof(doload_symbolClasses) / sizeof(doload_symbolClasses[0])))
        return doload_symbolClasses[class_number];
    else
	return "<NOT IN TABLE>";
}

/* This routine rolls down the symbol table and finds those symbols which
 * also appear in the globals array. When it finds one, it fills in the
 * value with that from globals. It is an error to have undefined symbols
 * after this routine executes. doload_finish_resolution also allocates space
 * for common symbols.
 */
static doload_finish_resolution(e)
    struct doload_environment *e;
{
    register pEXTR sp;
    register pEXTR sbound;

    for (sp = e->symtab; sp < e->symtab + e->symheader.iextMax; sp++) {
 	if (sp->asym.sc == scUndefined || sp->asym.sc == scSUndefined ||
            sp->asym.sc == scCommon || sp->asym.sc == scSCommon) {
            char *name = e->stringtab + sp->asym.iss;
            struct globaltab *thisGlobal;

	    if ((thisGlobal = find_global(name)) != NULL)
		sp->asym.value = thisGlobal->entrypoint;
	    else if ((sp->asym.sc == scCommon ||
                      sp->asym.sc == scSCommon) &&
                     sp->asym.value > 0) {
                unsigned long length = sp->asym.value;

		if (sp->asym.sc == scSCommon)
		    sp->asym.value = (unsigned long) gp_alloc(e, length);
		else
		    sp->asym.value = (unsigned long) common_alloc(e, length);
                bzero(sp->asym.value, length);
	    }
	    else {
		fprintf(stderr, "doload:  Undefined symbol: %s, symbol class %s.\n", name, symbol_class_name(sp->asym.sc));
		e->problems++;
	    }
            sp->asym.sc = scAbs;
        }
    }
}

/* compute relocation adjustment */
static long adjust(e, tw, rp)
    struct doload_environment *e;
    long tw;
    struct reloc *rp;
{
    if (rp->ext) {
        pEXTR sp = e->symtab + rp->symndx;
        char *np = e->stringtab + sp->asym.iss;
        struct doload_section *section;

/* Maybe do this with a map array instead. */
#define SECTION_ADJ(sec_num) \
        section = e->relocSection[sec_num]; \
        if (section == NULL) \
            doload_punt(e, "External relocation for non-exisitient section.");\
        else \
            tw += sp->asym.value + (long) section->contents - \
                section->header.s_vaddr; \
        break;

	switch (sp->asym.sc) {
          case scAbs:
            tw += sp->asym.value;
            break;
          case scText:
            SECTION_ADJ(SECT_TEXT);
          case scData:
            SECTION_ADJ(SECT_DATA);
          case scBss:
            SECTION_ADJ(SECT_BSS);
          case scRData:
            SECTION_ADJ(SECT_RDATA);
          case scSData:
            SECTION_ADJ(SECT_SDATA);
          case scSBss:
            SECTION_ADJ(SECT_SBSS);
          case scUndefined:
          case scCommon:
            doload_punt(e, "Internal botch - common and undefined symbols should have been resolved in doload_preset"); 
            break;
          default:
            {
                char message[120];

                sprintf(message,
                    "Unexpected class (%.20s) in relocation for symbol %.50s .",
                        symbol_class_name(sp->asym.sc), np);
                doload_punt(e, message);
            }
            break;
        }
    } /* endif IS_RP_EXTERN( rp ) */
    else {
        struct doload_section *section;

	if (rp->symndx > 0 && rp->symndx < NUM_RELOC_SECTS &&
	    (section = e->relocSection[rp->symndx]) != NULL)
            tw += (long) section->contents - section->header.s_vaddr;
        else
            doload_punt(e, "Bad section number in non-external relocation entry");
    } /* end else */

    return tw;
}

/* relocate one item */

#define HI16MASK    0xffff0000
#define LOW16MASK   0x0000ffff
#define LOW26MASK       0x03ffffff
#define HI6MASK     0xfc000000

#ifdef MIPSEB /* BIG ENDIAN */
#define GETLOWHALF(addr) (*(((short *)(addr)) + 1))
#define SETLOWHALF(addr, value) ((*(((short *)(addr)) + 1)) = (value))
#else /* LITTLE ENDIAN */
#define GETLOWHALF(addr) (*(((short *)(addr))))
#define SETLOWHALF(addr, value) ((*(((short *)(addr)))) = (value))
#endif /* Byte order selection */

#define	REFHALF	1
#define	REFWORD	2
#define	JMPADDR	3
#define	REFHI	4
#define	REFLO	5
#define	GPREL	6
#define	LITERAL	7

static void relocate_section(e, section)
    register struct doload_environment *e;
    struct doload_section *section;
{
    struct reloc *rp;
    struct reloc *sectionRelocBound = section->rtab + section->num_relocs;
    long offset = (long) section->contents - section->header.s_vaddr;

    for (rp = section->rtab; rp < sectionRelocBound; rp++) {
        long relocAddr = rp->vaddr + offset;
        unsigned long tw;
    
        switch (rp->type) {
            case REFHALF:
                tw = *(short *)relocAddr;
                tw = adjust(e, tw, rp);
                if (tw > 32767 || tw < -32768)
                    doload_punt(e, "REFHALF reloaction out of range.");
                *(short *)relocAddr = tw;
                break;
            case REFWORD:
                tw = *(long *)relocAddr;
                tw = adjust(e, tw, rp);
                *(long *)relocAddr = tw;
                break;
            case JMPADDR:
                tw = (*(long *)relocAddr & LOW26MASK) << 2;
/* Sign extend if external, otherwise make relative to relocation address. */
                if (rp->ext) {
                    if (tw & 0x08000000)
/* I don't know why this is 0xfc instead of 0xf0 but thats what the MIPS ld
 * code does...
 */
                        tw |= 0xfc000000;
                }
                else
                    tw |= (rp->vaddr + 4) & 0xf0000000;
                tw = adjust(e, tw, rp);
                if ((tw & 0xf0000000) != (((long) relocAddr + 4) & 0xf0000000))
                    doload_punt(e, "JUMPADDR relocation overflow.");
                *(long *)relocAddr = (*(long *)relocAddr & HI6MASK) +
                                     ((tw >> 2) & LOW26MASK);
                break;
            case REFHI:
                if (rp < sectionRelocBound) {
                    struct reloc *nextRp = rp + 1;

                    if (nextRp->type == REFLO) {
                        short highHalf; /* High 16 bits of final value. */
                        short lowHalf; /* Low 16 bits of final value. */
                        long nextRelocAddr = nextRp->vaddr + offset;
                    
/* I assume that the low half of this will be appropriately sign extended. */
                        tw = (GETLOWHALF(relocAddr) << 16) +
                             GETLOWHALF(nextRelocAddr);
                        tw = adjust(e, tw, rp);
                        lowHalf = tw & LOW16MASK;
                        highHalf = tw >> 16;
                        if (lowHalf < 0)
                            ++highHalf;
                        SETLOWHALF(relocAddr, highHalf);
/* Do second relocation since we have the value. Advance loop counter so
 * it doesn't get done again.
 */
                        SETLOWHALF(nextRelocAddr, lowHalf);
                        rp = nextRp;
                    }
                    else
                        doload_punt(e, "REFHI relocation record not followed by REFLO.");
                }
                else
                    doload_punt(e, "REFHI relocation record at end of relocations.");
                break;
            case REFLO: {
                short lowHalf;
                
                tw = GETLOWHALF(relocAddr);
                tw = adjust(e, tw, rp);
                lowHalf = tw & LOW16MASK;
                SETLOWHALF(relocAddr, lowHalf);
                break;
            }
            case LITERAL:
            case GPREL: {
                extern int _gp; /* MIPS' ld defines this label at the address in the gp register */
                long gpOffset;
                short lowHalf;
                
                tw = GETLOWHALF(relocAddr);
                if (!rp->ext)
                    tw += e->aouthdr.gp_value;
                tw = adjust(e, tw, rp);
                gpOffset = tw - (long)&_gp;
                if (gpOffset > 32767 || gpOffset < -32768)
                    doload_punt(e, "External GPREL/LITERAL relocation does not result in a gp addresable value.");
                lowHalf = (short)gpOffset;
                SETLOWHALF(relocAddr, lowHalf);
                break;
            }
#ifdef OLD
            case LITERAL:
/* If this ever happens, I think the code for GPREL will work for
 * literals too. Just put this case label under the one above for GPRREL
 * and try it...
 */
                doload_punt(e, "LITERAL relocation in doload_relocate.");
                break;
#endif /* OLD */
        }
    }
}
