/* caudit.c
   Utilities and common routines for compiler audit suite.
   Copyright (C) 1991 by James R. Larus (larus@cs.wisc.edu).
   All rights reserved. 
   Modified by: Satish Chandra */
/* $Header: /var/home/larus/CAudit/RCS/caudit.c,v 1.5 1991/12/11 22:49:22 larus Exp larus $ */


#include <sys/file.h>
#include <stdio.h>
#include <a.out.h>
#include <ldfcn.h>
#include <varargs.h>
#include <strings.h>
#include <stdlib.h>

#include "caudit.h"

/* Local variables: */

static LDFILE *ldptr = NULL;	/* Open descriptor for a.out file */

static FILHDR f_hdr;		/* ECOFF file components */

static AOUTHDR ao_hdr;

static SCNHDR *s_hdr;

static int text_size;		/* Size of text segment */

static int data_size;

/* Imported functions: */

int ldclose (LDFILE *);
char *ldgetname (LDFILE *, SYMR *);
int ldgetpd (LDFILE *, int, PDR *);
int ldnshread (LDFILE *, char *, SCNHDR *);
int ldnsseek (LDFILE *, char *);
int ldtbread (LDFILE *, long, SYMR *);
int ldtbread (LDFILE *, long, SYMR *);


/* Local functions: */

static int bit_on (REG_SET);
static long get_bytes ();
static char *get_symname (ADDR);
static void print_header (ADDR, long, INST);


#define MAX_BACK_SEARCH 20



/* Issue an error message and then quit. */

/*VARARGS*/
void
fatal_error (va_alist)
va_dcl
{
  va_list args;
  char *fmt;

  va_start (args);
  fmt = va_arg (args, char *);
  vfprintf (stderr, fmt, args);
  va_end (args);
  exit (1);
  /*NOTREACHED*/
}


/* Issue an error message and continue. */

/*VARARGS*/
void
warn (va_alist)
va_dcl
{
  va_list args;
  char *fmt;

  va_start (args);
  fmt = va_arg (args, char *);
  vfprintf (stderr, fmt, args);
  va_end (args);
}


char *
xmalloc (int size)
{
  char *x;

  x = (char *) malloc ((size_t) size);
  if (x == 0)
    fatal_error ("Out of memory\n");
  return (x);
}


/* List operations: */

list
cons (int head, list tail)
{
  list x = (list) xmalloc (sizeof (list_cell));

  CAR (x) = head;
  CDR (x) = tail;
  return (x);
}



/* Code to manipulate MIPS a.out files. */

void
read_aout (char *file_name)
{
  int i;
  PDR ppd;

  ldptr = ldopen (file_name, NULL);
  if (ldptr == NULL)
    fatal_error ("Can't open %s\n", file_name);

  /* Read the filehdr, aouthdr, and scnhdr structures */
  if (FREADM (&f_hdr, FILHSZ, 1, ldptr) != 1)
    fatal_error ("Can't read file header in %s in READ_AOUT\n", file_name);
  if (f_hdr.f_magic != MIPSELMAGIC)
    fatal_error ("Bad magic number 0x%x in %s\n", f_hdr.f_magic, file_name);

  if (FREADM (&ao_hdr, AOUTHSZ, 1, ldptr) != 1)
    fatal_error ("Can't read aout header in %s in READ_AOUT\n", file_name);

  s_hdr = (SCNHDR *) xmalloc (SCNHSZ * f_hdr.f_nscns);
  if (FREADM (s_hdr, SCNHSZ, f_hdr.f_nscns, ldptr) != f_hdr.f_nscns)
    fatal_error ("Can't read section header in %s in READ_AOUT\n", file_name);

  if (FSEEK (ldptr, N_TXTOFF (f_hdr, ao_hdr), L_SET) == -1)
    fatal_error ("Can't seek text segment in %s in READ_AOUT\n", file_name);

  text_start = ao_hdr.text_start;
  text_size = ao_hdr.tsize;
  text_end = text_start + text_size;
  text_seg = (INST *) xmalloc (text_size);
  if (FREADM (text_seg, text_size, 1, ldptr) != 1)
    fatal_error ("Can't read text segment in %s in READ_AOUT\n", file_name);
 
  if (FSEEK (ldptr, N_TXTOFF(f_hdr, ao_hdr) + text_size , L_SET) == -1)
    fatal_error ("Can't seek data segment in %s in READ_AOUT\n", file_name);

  data_start = ao_hdr.data_start;
  data_size = ao_hdr.dsize;
  data_end = data_start + data_size;
  data_seg = (DATA *) xmalloc (data_size);
  if (FREADM (data_seg, data_size, 1, ldptr) != 1)
    fatal_error ("Can't read data segment in %s in READ_AOUT\n", file_name);


  num_procs = SYMHEADER (ldptr).ipdMax;
  proc_tbl = (proc_info *) xmalloc (num_procs * sizeof (proc_info));
  for (i = 0; i < num_procs - 1; i++)
    if (ldgetpd (ldptr, i, &ppd))
      {
	SYMR sym;

	if (ldtbread (ldptr, ppd.isym, &sym))
	  {
	    proc_tbl[i].index = i;
	    proc_tbl[i].addr = ppd.adr;
	    proc_tbl[i].name = string_copy (ldgetname (ldptr, &sym));
	  }
      }
}


char *
string_copy (char *s)
{
  return (strcpy (xmalloc (strlen (s) + 1), s));
}


proc_info *
addr_to_proc_info (ADDR addr)
{
  int low = 0, hi = num_procs - 1;

  while (low <= hi)
    {
      int mid = (low + hi) / 2;

      if (proc_tbl[mid].addr == addr)
	return (&proc_tbl[mid]);
      else if (proc_tbl[mid].addr < addr)
	low = mid + 1;
      else
	hi = mid - 1;
    }

  return (&proc_tbl[hi]);
}



/* Operations on instructions: */

/* Given an instruction, categorize it (by INST_TYPE). */

INST_TYPE
inst_category (INST inst)
{
  switch ((inst & OP_MASK) >> OP_SHIFT)
    {
    case sCOP1:
    case sCOP2:
    case sCOP3:
      if ((inst & BCz_MASK) == BCF || (inst & BCz_MASK) == BCT)
	return (COND_REL);
      else
	return (OTHER);

    case sBCOND:
      return (COND_REL);

    case sBEQ:			/* beq r0 r0 == b */
      return ((inst & (RS_MASK | RT_MASK)) == 0 ? UN_COND_REL : COND_REL);

    case sBNE:
    case sBLEZ:
    case sBGTZ:
      return (COND_REL);

    case sLB:
    case sLH:
    case sLWL:
    case sLW:
    case sLBU:
    case sLHU:
    case sLWR:
    case sLWC0:
    case sLWC1:
    case sLWC2:
    case sLWC3:
      return (MEM_LOAD);

    case sSB:
    case sSH:
    case sSWL:
    case sSW:
    case sSWR:
    case sSWC0:
    case sSWC1:
    case sSWC2:
    case sSWC3:
      return (MEM_STORE);

    case sJ:
      return (UN_COND_ABS);

    case sJAL:
      return (SUBR_ABS);

    case sSPECIAL:
      switch (inst & SPEC_MASK)
	{
	case JR:
	  /* Not very conservative: */
	  if ((inst >> RS_SHIFT) == 31)
	    return (SUBR_RET);
	  else
	    return (UN_COND_IND);

	case JALR:
	  return (SUBR_IND);

	case SYSCALL:
	  return (SYS_CALL);

	default:
	  return (OTHER);
	}

    default:
      return (OTHER);
    }
}


/* Given a branch instruction, return the address of its target. If
the instruction does not branch, signal an error. */

ADDR
target_address (ADDR pc, INST inst)
{
  short offset;

  switch ((inst & OP_MASK) >> OP_SHIFT)
    {
    case sCOP1:
    case sCOP2:
    case sCOP3:
      if (!((inst & BCz_MASK) == BCF || (inst & BCz_MASK) == BCT))
	break;
      /* else fall through */

    case sBCOND:
    case sBEQ:
    case sBNE:
    case sBLEZ:
    case sBGTZ:
      offset = inst & IMM_MASK;
      return (pc + 4 + (offset << 2));

    case sJ:
    case sJAL:
      return ((pc & ~J_MASK) | ((inst & J_MASK) << J_SHIFT));

    default:
      break;
    }
  fatal_error ("Non branch instruction in TARGET_ADDRESS\n");
  /*NOTREACHED*/
}


/* Assume a switch statement if a jump indirect through a regsiter
  other than r31.  This test should be fast, but not necessarily
  accurate. */

int
could_begin_switch (INST inst)
{
  return ((inst & RS_MASK) != RS_MASK);
}


/* Find if the PC points to a switch statement.  If so, return the
   address of the jump table in memory.  If not, return 0.  Set (by-ref)
   SIZE to the number of words in the jump table (0 if no table).

   PC points to a JR instruction with register jrreg (!= 31).
   We assume that this is a switch statement compiled by cc.
   The code we search backwards for is of the form:

   SLTIU  ...,size              size = number of entries in jump table
   ...
   LUI lwbase, baseoff
   ...
   LW lwrreg, lwoff (lwbase)
   nop
   JR lwrreg
*/


ADDR
find_switch_tbl (ADDR pc, INST jr_inst, int *size, int *stride, ADDR *pi_base)
{
  INST inst;
  int lw_reg, lw_base, lw_offset, base_offset;
  int jr_reg = (jr_inst & RS_MASK) >> RS_SHIFT;
  ADDR limit = pc - MAX_BACK_SEARCH * sizeof (INST);

  *size = 0;
  *stride = 1;
  *pi_base = 0;
  pc -= 2 * sizeof (INST);
  if ((GET_INST (pc) & OP_MASK) != LW)
    return (0);

  inst = GET_INST (pc);
  lw_reg = (inst & RT_MASK) >> RT_SHIFT;
  lw_base = (inst & BASE_MASK) >> BASE_SHIFT;
  if (lw_reg != jr_reg)
    warn ("LW reg mismatch at 0x%x in FIND_SWITCH_TBL\n", pc);

  /* compute offset and sign extend it */
  lw_offset = inst & IMM_MASK;
  if (lw_offset & 0x00008000) lw_offset |= 0xffff0000;

  /* Search backwards for LUI */
  while (pc >= limit)
    {
      pc -= sizeof (INST);
      inst = GET_INST (pc);
      if ((inst & OP_MASK) == LUI)
	{
	  if (((inst & RT_MASK) >> RT_SHIFT) != lw_base)
	    warn ("LUI reg mismatch at 0x%x in FIND_SWITCH_TBL\n", pc);
	  base_offset = (inst & IMM_MASK) << 16;
	  break;
	}
    }

  /* Search backwards to find first SLTIU */
  while (pc >= limit)
    {
      INST inst2;

      inst = GET_INST (pc);
      inst2 = GET_INST (pc - INST_SIZE (inst));
      if ((inst & OP_MASK) == SLTIU)
	{
	  *size = inst & IMM_MASK;
	  if (*size & 0x00008000) *size |= 0xffff0000;
	  break;
	}
      else
	if ((inst & OP_MASK) == SPECIAL
	    && (inst & SPEC_MASK) == SLTU
	    && (inst2 & OP_MASK) == ADDIU
	    && (inst2 & RS_MASK) == 0
	    && ((inst2 & RT_MASK) >> RT_SHIFT == (inst & RS_MASK) >> RS_SHIFT))
	{
	  *size = inst2 & IMM_MASK;
	  if (*size & 0x00008000) *size |= 0xffff0000;
	  *size += 1;
	  break;
	}
      pc -= sizeof (INST);
    }

  return ((ADDR) base_offset + lw_offset);
}


/* Return the indirect address from the jump-indirect table entry at
   ENTRY_ADDR for the switch table jump at PC.  If PI is non-zero, this
   is a position-indepent switch table. */

ADDR
switch_tbl_entry (ADDR entry_addr, ADDR pi_base)
{
  return ((ADDR) (pi_base + GET_DATA (entry_addr)));
}


/* Return non-zero if the instruction is a branch with a delay slot of
   one type or another.  That means that the instruction following is
   sometimes executed.  Consider subroutine calls to be branches if
   INCLUDE_CALLS flag is non-zero. */

int
is_delayed (INST inst, int include_calls)
{
  switch ((inst & OP_MASK) >> OP_SHIFT)
    {
    case sCOP1:
    case sCOP2:
    case sCOP3:
      if ((inst & BCz_MASK) == BCF || (inst & BCz_MASK) == BCT)
	return (1);
      else
	return (0);

    case sBCOND:
      return (1);

    case sBEQ:
    case sBNE:
    case sBLEZ:
    case sBGTZ:
      return (1);

    case sJ:
      return (1);

    case sJAL:
      return (include_calls);

    case sSPECIAL:
      switch (inst & SPEC_MASK)
	{
	case JR:
	  return (1);

	case JALR:
	  return (include_calls);

	case SYSCALL:
	  return (0);

	default:
	  return (0);
	}
    default:
      return (0);
    }
}


static int
bit_on (REG_SET bits)
{
  int i;

  for (i = 0; i < sizeof (REG_SET) * 8; i ++)
    if (bits & (1 << i))
      return (i);
  return (0);
}


/* Return the set of registers defined by the instructions from PC
   (inclusive) to TO_PC (exclusive). */

REG_SET
inst_defines (ADDR pc, ADDR to_pc)
{
  REG_SET defs = 0;

  for ( ; pc < to_pc; pc += INST_SIZE (GET_INST (pc)))
    {
      INST inst = GET_INST (pc);

      switch ((inst & OP_MASK) >> OP_SHIFT)
	{
	case sJAL:
	  /* For tracing, a call defines the values in the result regs */
	  defs |= (0x1 << R_V0) | (0x1 << R_V1);
	  break;

	case sBCOND:
	  switch ((inst & BCOND_MASK) >> BCOND_SHIFT)
	    {
	    case sBLTZ:
	    case sBGEZ:
	      break;

	    case sBLTZAL:
	    case sBGEZAL:
	      /* For tracing, a call defines the values in the result regs */
	      defs |= (0x1 << R_V0) | (0x1 << R_V1);
	      break;

	    default:
	      fatal_error ("Unknown inst 0x%x at 0x%x in INST_DEFINES\n",
			   inst, pc);
	    }
	  break;

	case sJ:
	case sBEQ:
	case sBNE:
	case sBLEZ:
	case sBGTZ:
	  break;

	case sADDI:
	case sADDIU:
	case sSLTI:
	case sSLTIU:
	case sANDI:
	case sORI:
	case sXORI:
	case sLUI:
	case sLB:
	case sLH:
	case sLWL:
	case sLW:
	case sLBU:
	case sLHU:
	case sLWR:
	  defs |= (0x1 << ((inst & RT_MASK) >> RT_SHIFT));
	  break;

	case sCOP0:
	case sCOP1:
	case sCOP2:
	case sCOP3:
	  switch (inst & Cz_MASK)
	    {
	    case MFC:
	    case CFC:
	      defs |= (0x1 << ((inst & RT_MASK) >> RT_SHIFT));
	      break;

	    default:
	      break;
	    }
	  break;

	case sSB:
	case sSH:
	case sSWL:
	case sSW:
	case sSWR:
	case sLWC0:
	case sSWC0:
	case sLWC1:
	case sSWC1:
	case sLWC2:
	case sSWC2:
	case sLWC3:
	case sSWC3:
	  break;

	case sSPECIAL:
	  switch (inst & SPEC_MASK)
	    {
	    case SLL:
	    case SRL:
	    case SRA:
	    case SLLV:
	    case SRLV:
	    case SRAV:
	    case MFHI:
	    case MFLO:
	    case ADD:
	    case ADDU:
	    case SUB:
	    case SUBU:
	    case AND:
	    case OR:
	    case XOR:
	    case NOR:
	    case SLT:
	    case SLTU:
	      defs |= (0x1 << ((inst & RD_MASK) >> RD_SHIFT));
	      break;

	    case JR:
	    case SYSCALL:
	    case BREAK:
	      break;

	    case MTHI:
	      defs |= (0x1 << R_HI); /* $k1 never appears in user code */
	      break;

	    case MTLO:
	      defs |= (0x1 << R_LO); /* $k0 never appears in user code */
	      break;

	    case MULT:
	    case MULTU:
	    case DIV:
	    case DIVU:
	      defs |= (0x1 << R_LO) | (0x1 << R_HI);
	      break;

	    case JALR:
	      /* For tracing, a call defines the values in the result regs */
	      defs |= (0x1 << R_V0) | (0x1 << R_V1);
	      break;

	    default:
	      fatal_error ("Unknown inst 0x%x at 0x%x in INST_DEFINES\n",
			   inst, pc);
	    }
	  break;

	default:
	  fatal_error ("Unknown inst 0x%x at 0x%x in INST_DEFINES\n",
		       inst, pc);
	}
    }
  return (defs);
}


/* Return the set of registers used by INST in a way that is interesting for
   computing address slices. */

REG_SET
uses_registers (ADDR pc)
{
  REG_SET uses = 0;
  INST inst = GET_INST (pc);

  switch ((inst & OP_MASK) >> OP_SHIFT)
    {
    case sJAL:
      break;

    case sBCOND:
      switch ((inst & BCOND_MASK) >> BCOND_SHIFT)
	{
	case sBLTZ:
	case sBGEZ:
	case sBLTZAL:
	case sBGEZAL:
	  uses = (0x1 << ((inst & RS_MASK) >> RS_SHIFT));
	  break;

	default:
	  fatal_error ("Unknown inst 0x%x at 0x%x in USES_REGISTERS\n",
		       inst, pc);
	}
      break;

    case sJ:
      break;

    case sBEQ:
    case sBNE:
      uses = (0x1 << ((inst & RS_MASK) >> RS_SHIFT))
	| (0x1 << ((inst & RT_MASK) >> RT_SHIFT));
      break;

    case sBLEZ:
    case sBGTZ:
      uses = (0x1 << ((inst & RS_MASK) >> RS_SHIFT));
      break;

    case sADDI:
    case sADDIU:
    case sSLTI:
    case sSLTIU:
    case sANDI:
    case sORI:
    case sXORI:
      uses = (0x1 << ((inst & RS_MASK) >> RS_SHIFT));
      break;

    case sLUI:
      break;

    case sLB:
    case sLH:
    case sLWL:
    case sLW:
    case sLBU:
    case sLHU:
    case sLWR:
    case sLWC0:
    case sLWC1:
    case sLWC2:
    case sLWC3:
      break;

    case sCOP0:
    case sCOP1:
    case sCOP2:
    case sCOP3:
      switch (inst & Cz_MASK)
	{
	case MTC:
	case CTC:
	  uses = (0x1 << ((inst & RT_MASK) >> RT_SHIFT));
	  break;

	default:
	  break;
	}
      break;

    case sSB:
    case sSH:
    case sSWL:
    case sSW:
    case sSWR:
    case sSWC0:
    case sSWC1:
    case sSWC2:
    case sSWC3:
      /* Don't care about the value in RT! */
      break;

    case sSPECIAL:
      switch (inst & SPEC_MASK)
	{
	case SLL:
	case SRL:
	case SRA:
	  uses = (0x1 << ((inst & RT_MASK) >> RT_SHIFT));
	  break;

	case SLLV:
	case SRLV:
	case SRAV:
	  uses = (0x1 << ((inst & RS_MASK) >> RS_SHIFT))
	    | (0x1 << ((inst & RT_MASK) >> RT_SHIFT));
	  break;

	case MFHI:
	  uses = (0x1 << R_HI);
	  break;

	case MFLO:
	  uses = (0x1 << R_LO);
	  break;

	case ADD:
	case ADDU:
	case SUB:
	case SUBU:
	case AND:
	case OR:
	case XOR:
	case NOR:
	case SLT:
	case SLTU:
	  uses = (0x1 << ((inst & RS_MASK) >> RS_SHIFT))
	    | (0x1 << ((inst & RT_MASK) >> RT_SHIFT));
	  break;

	case JR:
	  uses = (0x1 << ((inst & RT_MASK) >> RT_SHIFT));
	  break;

	case SYSCALL:
	case BREAK:
	  break;

	case MTHI:
	case MTLO:
	  uses = (0x1 << ((inst & RS_MASK) >> RS_SHIFT));
	  break;

	case MULT:
	case MULTU:
	case DIV:
	case DIVU:
	  uses = (0x1 << ((inst & RS_MASK) >> RS_SHIFT))
	    | (0x1 << ((inst & RT_MASK) >> RT_SHIFT));

	case JALR:
	  uses = (0x1 << ((inst & RS_MASK) >> RS_SHIFT));
	  break;
	}
      break;

    default:
      fatal_error ("Unknown inst 0x%x at 0x%x in USES_REGISTERS\n",
		   inst, pc);
    }

  /* Don't notices uses of R0 */
  return (uses & ~0x1);
}



static ADDR pc, pc1, pc2;

static char *
get_symname (ADDR addr)
{
  proc_info *pi = addr_to_proc_info (addr);
  return (pi->name);
}


static long
get_bytes ()
{
  long inst = (long) GET_INST (pc);

  pc += 4;
  return (inst);
}


static void
print_header (ADDR addr, long iadr, INST inst)
{
  if (addr == pc1 || addr == pc2)
    printf ("* 0x%08x: ", addr);
  else
    printf ("  0x%08x: ", addr);
}











