/* GccFindHit V1.2, 30/12/95
*
*  The same than FindHit by Michael Sinz, but for GCC users
*  Usage: FindHit <executable file> <list of offsets in hexadecimal>
*  The file should have been linked with the '-g' flag passed to gcc
*  to turn on debugging information (the 'stabs')
*
*  FindHit outputs the line numbers matching with the offsets given by
*  Enforcer (or whatever). Currently, there is no need
*  to provide the hunk number because it should always be zero.
*
*  Copyright (C) 1995 Daniel Verite -- daniel@brainstorm.cnam.fr
*  This program is distributed under the General GNU Public License version 2
*  See the file COPYING for information about the GPL
*
*  v1.2, 30/12/95
*
*    Daniel Verite, daniel@brainstorm.cnam.fr:
*  o added handling of HUNK_NAME
*  o fixed a small glitch in the strings seeks
*
*  v1.1, 28/09/95
*
*    Hans Verkuil, hans@wyst.hobby.nl:
*  o GccFindHit no longer reads the strings into memory, but seek()s them
*    instead, saving lots of memory.
*  o added version string.
*
*  v1.0, 22/05/95
*
*    First release
*/

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#include "defs.h"

extern int errno;

int ExeFile,NbOffs;
unsigned long *SearchOffs;

static const char version_id[] = "\000$VER: GccFindHit 1.2 (30.12.95)";

int Read4 (long *buf)
{
  if (read (ExeFile,buf,4)==4) {
    *buf = GETLONG(*buf);
    return 1;
  }
  else
    return 0;
}

struct Header *ReadHeader ()
{
  long nb,size;
  struct Header *h;
  int i;

  /* skip hunk names */
  while (Read4 (&size) && size)
    lseek (ExeFile, size<<2, SEEK_CUR);

  /* reads the number of hunks */
  if (!Read4(&nb))
    return NULL;
  h = (struct Header*)malloc(sizeof(struct Header)+(nb-1)*sizeof(long));
  if (!h)
    return NULL;
  h->nb_hunks = nb;
  if (Read4 (&h->first) && Read4 (&h->last)) {
    for (i=0; i<nb; i++)
      if (!Read4 (&h->sizes[i])) {
	free (h);
	return NULL;
      }
  }
  return h;
} /* ReadHeader() */

int long_cmp (long *e1,long *e2)
{
  return (*e1)<(*e2);
}

void SkipRelocation ()
{
  unsigned long no; /* number of offsets */
  long h;  /* hunk number */
  while (Read4 (&no) && no && Read4 (&h))
    lseek (ExeFile, no<<2, SEEK_CUR);
}

/* this function hasn't been tested; AFAIK, ld won't output short relocs,
   so it's useless for now */
void SkipShortRel ()
{
  unsigned long no;
  short h;
  while (Read4 (&no) && no && read (ExeFile, &h, sizeof h))
    lseek (ExeFile, no<<1, SEEK_CUR);
}

/* can be slow if I/O buffering doesn't do some read-ahead */
void SkipSymbols ()
{
  long nl; /* name length in long words */
  while (Read4 (&nl) && nl) {
    /* skips the name + the value */
    lseek (ExeFile, (nl+1)<<2, SEEK_CUR);
  }
}

/* skip hunks such as HUNK_NAME that have their size in the first long */
void SkipHunk ()
{
  unsigned long size;
  if (Read4 (&size))
    lseek (ExeFile, size<<2, SEEK_CUR);
}

char *get_string(long offset)
{
  static char buf[256];

  lseek(ExeFile, offset, SEEK_SET);
  read(ExeFile, buf, 255);
  buf[255] = 0;
  return buf;
}

void GetLines (long symsz, BSD_SYM *syms, long string_offset)
{
  long nbsyms = symsz / sizeof(BSD_SYM);
  BSD_SYM *sym = syms;
  unsigned char prev_type;
  long srcname = 0, prev_src = 0;
  unsigned short prev_line = 0;
  unsigned long offs , prev_offs = -1UL;
  int i;

  while (nbsyms--) {
    switch (sym->type) {
    case N_SO:
    case N_SOL:
      srcname = GETLONG (sym->strx);
      break;
    case N_SLINE:
      offs = OFFSET_N_SLINE (GETLONG (sym->value));
      for (i = 0; i < NbOffs; i++) {
	if (SearchOffs[i] >= prev_offs && SearchOffs[i] < offs) {
	  printf ("%s: line %hd, offset 0x%lx\n",
                  get_string(prev_src + string_offset), prev_line,
		  prev_offs);
	}
      }
      prev_offs = offs;
      prev_line = GETWORD (sym->desc);
      prev_src = srcname;
      break;
    }
    prev_type = sym->type;
    sym++;
  }
  /* the last SLINE is a special case */
  for (i = 0; i < NbOffs; i++) {
    if (SearchOffs[i] == prev_offs) {
      printf ("%s: line %hd, offset 0x%lx\n",
              get_string(prev_src + string_offset), prev_line,
	      prev_offs);
    }
  }
}

void HunkDebug (void)
{
  long hunksz, symsz, strsz;
  struct bsd_header hdr;
  long pos, init_pos = lseek (ExeFile, 0, SEEK_CUR);
  char *syms;

  if (init_pos < 0)
    return;
  if (Read4(&hunksz) && read (ExeFile, &hdr, sizeof(hdr)) == sizeof(hdr)) {
    if (GETLONG(hdr.magic)==ZMAGIC) {
      /* seems to be gcc-compiled */
      strsz = GETLONG (hdr.strsz);
      symsz = GETLONG (hdr.symsz);
      if (strsz + symsz != 0) {
	syms = (char*)malloc (symsz);
	if (syms) {
	  if (read (ExeFile, syms, symsz) == symsz) {
	    pos = lseek(ExeFile, strsz, SEEK_CUR);
            if (pos > 0)
	      GetLines (symsz, (BSD_SYM*)syms, pos - strsz);
	  }
	  free (syms);
	}
      }
    }
  }
  /* go to the end of the hunk whatever happened before */
  lseek (ExeFile, init_pos+((hunksz+1)<<2), SEEK_SET);
}

void DoHunks (struct Header *h)
{
  long hnum,size,nsec=0;
  while (Read4 (&hnum)) {
    switch (hnum) {
    case HUNK_NAME:
      SkipHunk ();
      break;
    case HUNK_CODE:
    case HUNK_DATA:
      if (Read4 (&size)) {
	nsec++;
	lseek (ExeFile, (size&0x3fffffff)<<2, SEEK_CUR);
      }
      break;
    case HUNK_BSS:
      nsec++;
      Read4 (&size);
    case HUNK_END:
    case HUNK_BREAK:
      break;
    case HUNK_RELOC32:
    case HUNK_RELOC16:
    case HUNK_RELOC8:
    case HUNK_DRELOC32:
    case HUNK_DRELOC16:
    case HUNK_DRELOC8:
      SkipRelocation();
      break;
    case HUNK_RELOC32SHORT:
      SkipShortRel ();
      break;
    case HUNK_SYMBOL:
      SkipSymbols();
      break;
    case HUNK_DEBUG: /* here we are... */
      HunkDebug ();
      break;
    default:
      fprintf (stderr, "Unexpected hunk 0x%lx\n", hnum);
      return;
    }
  }
} /* DoHunks() */

void Out(int code)
{
  if (ExeFile>0) close (ExeFile);
  if (SearchOffs) free (SearchOffs);
  exit (code);
}

int main(int argc,char **argv)
{
  long HunkNum;
  struct Header *header=NULL;
  int i;

  if (argc<3) {
    fprintf (stderr,"Usage: %s <file> <hex offsets>\n",argv[0]);
    Out (1);
  }
  ExeFile = open (argv[1], O_RDONLY);
  if (ExeFile<0) {
    fprintf (stderr,"can't open %s:%s\n", argv[1], strerror (errno));
    Out (1);
  }
  NbOffs = argc-2;
  SearchOffs = (long*)malloc (sizeof (long)*NbOffs);
  if (!SearchOffs) {
    fprintf (stderr,"No memory\n");
    Out (1);
  }
  for (i=0; i<NbOffs; i++) {
    if (sscanf (argv[i+2],"%lx",&SearchOffs[i])!=1) {
      fprintf (stderr, "Operand %s is not an hex offset\n", argv[i+2]);
      Out (1);
    }
  }
  if (!Read4(&HunkNum) || HunkNum!=HUNK_HEADER || !(header=ReadHeader())) {
    fprintf (stderr, "%s is not an amigados executable\n", argv[1]);
    Out (1);
  }
  DoHunks (header);
  free (header);
  Out (0);
  return 0; /* another brick in the -Wall */
} /* main() */

