/* ********************************************************************** *\
 *         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/aix_rt/RCS/doload.c,v 1.7 1992/12/15 20:58:42 rr2b R6tape $";
#endif

/* 
	doload.c - dynamic loader for class system

	Author:  John H Howard - April 4, 1987
 */

#include <stdio.h>
#include <a.out.h>
#include <setjmp.h>
#include <doload.h>

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

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

int doload_trace=0;		/* nonzero if debugging */
char doload_extension[] = ".do";

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

/* initialize state */

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->text = NULL;
    e->rtab = NULL;
    e->symtab = NULL;
    e->stringtab = NULL;
    e->newsym = NULL;
    e->newsymcount = 0;
    return;
}

/* tear down environment */

void doload_cleanup(e)
struct doload_environment *e;
{
    if (e->problems > 0) {
	e->problems = 0;
	doload_punt(e, "Errors while processing");
    }
    safe_free((char *)e->rtab);
    safe_free((char *)e->symtab);
    safe_free(e->stringtab);
    safe_free((char *)e->newsym);
    return ;
}

/* read module into memory */

doload_read(e)
struct doload_environment *e;
{
    long stringlen;	/* length of string table */

    /* read header */

    /* 
     * This code is very system dependent.  The headers
     * that the AIX ld command generates vary between 
     * 0x28 and 0x38 depending on whether the values for
     * the ending fields of the header contain values
     * other than the defaults that can be obtained from
     * the preceeding values.
     * 
     * When the header is read, if it is shorter than
     * 0x38 the last few fields in the record will be 
     * filled in and the header length forced to 0x38
     * so all software that uses the header can assume
     * the whole header is available.
     */

    safe_read(e, (char *)&(e->header.a_magic[0]), (long) 0x20);	/* read in the shortest form */
    if (e->header.a_hdrlen != (unsigned) 0x20) {		/* full length header? */
	safe_read(e, (char *)&(e->header.a_trsize), (unsigned long) e->header.a_hdrlen - (unsigned long) 0x20);    /* get the rest */
    }
    switch (e->header.a_hdrlen)	{   /* fall through to initialize whole thing */
	case 0x20:  e->header.a_trsize = (long) 0;
	case 0x24:  e->header.a_drsize = (long) 0;
	case 0x28:  e->header.a_tbase = (long) 0;
	case 0x2c:  e->header.a_dbase = e->header.a_text;
	case 0x30:  e->header.a_lnums = (long) 0;
	case 0x34:  e->header.a_toffs = (long) 0x38;
    }

    /*
     * This is the logical place to do this but macros
     * such as A_TEXTPOS use the value of a_hdrlen for 
     * the given file so we have to leave a_hdrlen in place
     * for now.  This is done at the end of the routine.
     */
/*    e->header.a_hdrlen = 0x38;	*/    /* force the length to full sized header */


    /* if desired, print a debugging message */

    if (e->mode == List)
	printf( "\nHEADER\n  magic= %x\n  text = %x\n  data = %x\n\
  bss  = %x\n  syms = %x\n  entry= %x\n  trsize=%x\n  drsize=%x\n",
		e->header.a_magic, e->header.a_text, e->header.a_data,
		e->header.a_bss, e->header.a_syms, e->header.a_entry,
		e->header.a_trsize, e->header.a_drsize);
    if (N_BADMAG(e->header))
	doload_punt(e, "file not in loader format");

    /* read text plus data */
    e->text = safe_malloc( e,
		 (long)(e->header.a_text + e->header.a_data + e->header.a_bss));
    e->data = e->text + e->header.a_text;
    safe_lseek(e, (long)N_TXTOFF(e->header), 0);
    safe_read(e, e->text, (long)(e->header.a_text + e->header.a_data));
    bzero(e->data + e->header.a_data, e->header.a_bss);

    /* read relocation information */

    if ( !A_HASRELS( e->header ) ) /* Else Don't Bother */
	doload_punt(e, "object hasn't any relocation info !!");
    else if (e->header.a_trsize + e->header.a_drsize > 0) {
	long rsize;		/* size of relocation info */

	rsize = e->header.a_trsize + e->header.a_drsize;
	e->rtab = (struct relocation_info *)safe_malloc(e, rsize);
	safe_lseek(e, A_TRELPOS(e->header), 0);
	safe_read(e, (char *)e->rtab, e->header.a_trsize );
	safe_lseek(e, A_DRELPOS(e->header), 0);
	safe_read(e, (char *)e->rtab + e->header.a_trsize,
		  e->header.a_drsize );
    }

    /* read symbol table */
    /* Hope symbol table comes Right after data relocations !! */
    e->symtab = (struct nlist *)safe_malloc(e, (long)e->header.a_syms);
    safe_read(e, (char *)e->symtab, (long)e->header.a_syms);

    /* read string table */
    /* COFF String table runs from end of linenum entries to end of file */
    {
    struct stat sbuf ;
    if ( fstat( e->fd, &sbuf ) < 0 )
	doload_punt(e, "fstat of object file desciptor failed");
    stringlen = sbuf.st_size - A_NAMEPOS( e->header ) ;
    e->stringtab = safe_malloc(e, stringlen);
    safe_lseek(e, A_NAMEPOS(e->header), 0);
    safe_read(e, (char *)e->stringtab, stringlen ) ;
    }


    /* had to defer this to the very end */
    e->header.a_hdrlen = 0x38;	    /* force the length to full sized header */

}

/* read and relocate module */
char *doload(inFD, name, bp, lenP, path) /* return pointer to entry point, */
				/* or NULL if error */
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;
    unsigned long n;	/* number of relocation items */
    struct relocation_info *rp;

    /* set up environment */

    doload_setup(e = &E, inFD, Load);
    if (setjmp(e->errorJump)) {
	doload_cleanup(e);
	return NULL;
    }

    /* read module into memory */

    doload_read(e);

    /* do relocation */

    if (e->header.a_syms)
	doload_preset(e);
    rp = e->rtab;
    for (n = (e->header.a_trsize)/(sizeof *rp); n > 0; n--, rp++) {
	doload_relocate(e, e->text + rp->r_address, rp);
    }
    for (n = (e->header.a_drsize)/(sizeof *rp); n > 0; n--, rp++) {
	doload_relocate(e, e->data + rp->r_address, rp);
    }

    /* all done */

    if (doload_trace)
	printf( " %s: text = 0x%.8x  data = 0x%.8x  entry = 0x%.8x\n",
		name, e->text, e->data, e->text + e->header.a_entry);

    if(bp!=NULL) *bp = e->text;
    if(lenP!=NULL) *lenP = e->header.a_text;

    doload_cleanup(e);

    return e->text + e->header.a_entry;
}

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

/* preset global symbols */

static char *symtypename[] = {"UNDF", "ABS ", "TEXT", "DATA", "BSS ", "????" };


char *RelocType(i)
int i;
{
    i &= N_SECT ;
    return symtypename[i <= 4 ? i : 5];
}

doload_preset(e)
register struct doload_environment *e;
{
    register struct nlist *sp;
    register struct nlist *sbound;

    sp = e->symtab;
    sbound = (struct nlist *)((char *)sp + e->header.a_syms);

    for (; sp < sbound; sp++) {
	char *np = ( sp->_n._n_n._n_zeroes
	 	? sp->_n._n_name
	 	: (e->stringtab + sp->_n._n_n._n_offset ) ) ;

	if (e->mode == List) {
	    printf( " %.2x %.2x %.4x %.8x  %s %s %s\n",
		    sp->n_sclass, sp->n_numaux, sp->n_type, sp->n_value,
		    RelocType(sp->n_sclass),
		    ( IS_EXTERN_SYM( sp ) ? "EXT " : "    "),
		    np );
	}
	else if ( SYM_TYPE(sp) == N_UNDF) {
	    register int i;

	    for (i = globalcount;
		 --i >= 0 && strcmp(globals[i].entryname, np) != 0; ) ;
	    if (i >= 0)
		sp->n_value = globals[i].entrypoint;
	    else if (sp->n_value > 0) {

                unsigned long length = sp->n_value;

		sp->n_value = (unsigned long)safe_malloc(e, length);
                bzero(sp->n_value, length);
	    }
	    else {
		fprintf(stderr, "doload:  Undefined symbol: %s\n", np);
		e->problems++;
	    }
	    sp->n_sclass = N_ABS + N_EXT;
	} /* endif N_UNDF */
	/* Discard SymbolTable Auxiliary Entries */
	{
	register int auxcnt ;
	if ( auxcnt = sp->n_numaux ) {
	    sp->n_numaux = 0 ; /* Set Number to Zero Entries */
	    sp += auxcnt ; /* Skip Auxiliary Symbol Entries */
	}
	}
    }
}

/* compute relocation adjustment */

long adjust(e, tw, rp, format)
register struct doload_environment *e;
register long tw;
register struct relocation_info *rp;
char *format;
{
    if (e->mode == List)
	printf("  %s", format);
    if (IS_RP_EXTERN( rp )) {
	register struct nlist *sp = e->symtab + rp->r_symbolnum;
	char *np = sp->_n._n_n._n_zeroes
		? sp->_n._n_name
		: ( e->stringtab + sp->_n._n_n._n_offset ) ;
	if (e->mode == List) {
	    if (tw)
		(void) printf("%x+", tw);
	}
	if ( SYM_TYPE(sp) == N_UNDF && e->mode == Load)
	    doload_punt(e,
		"Internal botch - should have resolved in doload_preset");
	if (e->mode == List)
	    (void) printf( "%s=%x<%s>", np, sp->n_value,
		    RelocType(sp->n_sclass));
	else {
	    tw += sp->n_value;
	    switch ( SYM_TYPE(sp) ) {
	    case N_DATA:
	    case N_BSS:
		/* Relocate the data segment to right after the text */
		tw -= e->header.a_dbase - e->header.a_text ;
	    case N_TEXT:
		tw += (long) e->text;
	    case N_ABS:
		break;
	    case N_UNDF:
		if (IS_EXTERN_SYM( sp ))
		    break;
	    default:
		fprintf(stderr, "doload:  Unknown relocation in symbol.\n");
		fprintf( stderr, "  reltab:  %.8x %.6x %.2x\n",
			 rp->r_address, rp->r_symndx, *((char *)rp + 7));
		fprintf( stderr,
			 "  symtab[%.6x]: %.8x %.2x %.2x %.4x %.8x %s\n",
			 rp->r_symndx, sp->_n._n_n._n_offset,
			 sp->n_type & 0xFF, sp->n_type >> 8,
			 sp->n_sclass, sp->n_value, np);
		e->problems++;
	    }
	}
    } /* endif IS_RP_EXTERN( rp ) */
    else {
	if (e->mode == List)
	    printf( "%x<%s>", tw,
		    RelocType( - rp->r_symndx ));
	switch ( rp->r_symndx )
		{

	    /* AIX's "ld" puts DATA & BSS symbols
		at offset 0x10000000  beyond the start of TEXT.
		We will load DATA & BSS right after TEXT,
		all in the same Segment ( i.e. the BSS segment ) */
	    case S_DATA:
	    case S_BSS:
		tw += (long) e->data - e->header.a_dbase ;
		break;
	    case S_TEXT:
		tw += (long) e->text ;
	    case S_ABS:
		break;
	    default:
		doload_punt(e, "unknown symbol type");
	} /* end switch */
    } /* end else */
    return tw;
}

/* relocate one item */

doload_relocate(e, cp, rp)
register struct doload_environment *e;
register char *cp;
register struct relocation_info *rp;
{
    register long tw;

    switch (RP_LENGTH( rp )) {
    case 0:	/* 1 byte */
	tw = *cp;
  	if (IS_RP_PC_REL( rp )) {
  	    tw += rp->r_address;
  	    tw = adjust(e, tw, rp, "(pcrel)");
  	    tw -= (long)cp;
  	}
  	else
	    tw = adjust(e, tw, rp, "(char)");
	if (e->mode == Load) {
	    if (tw > 255)
		doload_punt(e, "byte displacement overflow");
	    *cp = tw;
	}
	break;
    case 1:	/* 2 bytes */
	tw = *(short *)cp;
	if (IS_RP_PC_REL( rp ))
	    doload_punt(e, "pc relative short relocation");
	tw = adjust(e, tw, rp, "(short)");
	if (e->mode == Load) {
	    if (tw < -32768 || tw > 32767)
		doload_punt(e, "short displacement overflow");
	    *(short *)cp = tw;
	}
	break;
    case 2:	/* 4 bytes */
	tw = ((*(short *)cp)<<16) + (*(unsigned short *)(cp+2));
	if (IS_RP_PC_REL( rp )) {

	/* the following kludge is taken from 4.2A's ld.c */

#define BI_LO_OP	0x88
#define BI_HI_OP	0x8f
#define BALA_OP		0x8a
#define BALI_OP		0x8c
#define CAU_OP		0xd8
#define JI_HI_OP	0x0f
#define MAX_POS_20BITS	524287
#define MAX_NEG_20BITS	-524288

	    int opcode = (tw>>24) & 0xff;
	    if (opcode >= BI_LO_OP && opcode <= BI_HI_OP) {
		if (opcode>>1 != BALA_OP>>1) { /* if not bala or balax */
		    int reg = (tw>>20) & 0x0f;
		    /* pick up a signed 20 bit value and multiply by two */	
		    tw = ((tw & 0xfffff) << 12) >> 11;
		    if (!IS_RP_EXTERN( rp ))
			tw += rp->r_address;	/* make it absolute */
		    tw = adjust(e, tw, rp, "(20bit)");
		    if (e->mode == Load) {
			tw -= (long)cp;		/* make it pc-relative */
			if (tw>>1 > MAX_POS_20BITS || tw>>1 < MAX_NEG_20BITS) {
			    tw += (long)cp;	/* make it absolute */
			    if ( opcode >> 1 == BALI_OP >> 1
			     && reg == 15 && ( tw & 0xff000000 ) == 0 ) {
				opcode = (opcode&1) | BALA_OP;
				tw = opcode<<24 | (tw&0xffffff);
			    } /* if bali 15 */
			    else {
				/* Fake long jump into the kernel */
				if ( rp->r_type == R_KCALL ) {
				    if ( opcode == BALI_OP )
					tw = 0x8a000c00 ; /* MAGIC */
				    else if ( opcode == BALI_OP + 1 )
					tw = 0x8b000c00 ; /* MAGIC */
				    else
					doload_punt(e, "KCALL 20-bit overflow");
				}
				else
				    doload_punt(e, "20-bit overflow");
			    } /* if !bali 15 */
			} /* if longer than 20 bits */
			else
			    tw = opcode<<24 | reg<<20 | tw>>1 & 0xfffff;
		    } /* if loading */
		} /* if not bala or balax */
		else { /* must be bala or balax */
		    tw = tw & 0xffffff;
		    tw = adjust(e, tw, rp, "(24bit)");
		    if (e->mode == Load) {
			if ( tw & 0xff000000 )
			    doload_punt(e, "24-bit overflow");
			tw = opcode<<24 | tw & 0xffffff;
		    }
		}
	    } /* if BI_LO_OP .. BI_HI_OP */
	    else  /* as or ld botch ??? */
		doload_punt(e,"Op-code invalid for 20/24 bit data. as/ld botch?");
	} /* if IS_RP_PC_REL( rp ) */
	else
	{
	    tw = adjust(e, tw, rp, "(word)");
	}
	if (e->mode == Load) {
	    *(short *)cp = tw >> 16;
	    *(short *)(cp + 2)= tw;
	}
	break;
    case 3:	/* split address */
	if (IS_RP_PC_REL( rp ))
	    doload_punt(e, "pc relative split relocation");
	if ( *(cp-2) == CAU_OP ) { /* low value is signed */
	    short low_half;
	    tw = (( *(short *)cp )<<16) + *(short *)(cp+4);
	    tw = adjust(e, tw, rp, "(splitcau)");
	    if (e->mode == Load) {
		low_half = tw & 0xffff;
		*(short *)cp = ((tw - low_half)>>16) & 0xffff;
		*(short *)(cp+4) = low_half;
	    }
	} else {   
	    tw = ( *(short *)(cp+4) )<<16 | *(unsigned short *)cp;
	    tw = adjust(e, tw, rp, "(split)");
	    if (e->mode == Load) {
		*(short *)cp = tw;	 /* low order two */
		*(short *)(cp+4) = tw>>16;
	    }
	}
	break;
    default:
	if ( rp->r_type != R_ABS )
	    doload_punt(e, "unknown relocation length");
    }
    return ;
}