/*****************************************************************************
 * DUMPAOUT - Dump data about a GNU a.out-format object module.
 *
 *          This is a simple utility without a lot of bells and whistles.
 *          Throw a bogus or broken a.out file at it and it'll probably
 *          crash.  I wrote it mainly to help me determine that I had the
 *          HSC JAS assembler properly emitting regulation a.out modules.
 *
 *          GEMDOS file I/O and memory allocation is used, not because I
 *          have anything against runtime libraries so much as that it
 *          provides independence from size_t/int/long portability issues.
 *
 *          This file is self-contained; there are no other source modules
 *          or header files other than standard compiler headers.  It was
 *          written for HSC, and doesn't contain prototypes.
 *
 *          Don't let the appearance of the term 'GNU' fool you:  this is
 *          public domain software, free of any encumbrances whatsoever.
 *          There are no facist restrictions on copying or using this code
 *          in any way you see fit.
 *
 * 06/16/93 v1.0 (Ian Lepore)
 *          Created.
 *****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <osbind.h>

#ifndef TRUE
  #define TRUE  1
  #define FALSE 0
#endif

#ifdef __BORLANDC__     /* %@#!^@%#$@# Borland compiler won't define    */
  #define __STDC__      /* __STDC__ unless you ask for Pure-ANSI mode.  */
#endif                  /* (And nothing works right in its Pure-ANSI.)  */

/*----------------------------------------------------------------------------
 *  structures and constants used with a.out modules.
 *    cobbled together from the manpage for a.out on a Sun system,
 *    mainly by deleting massive amounts of unneeded (by me) stuff.
 *--------------------------------------------------------------------------*/

typedef struct {                /* a.out module file header... */
  unsigned long     a_info;     /* typical magic value identifies file */
  unsigned long     a_text;     /* length of text, in bytes */
  unsigned long     a_data;     /* length of data, in bytes */
  unsigned long     a_bss;      /* length of uninitialized data area in bytes */
  unsigned long     a_syms;     /* length of symbol table data in bytes */
  unsigned long     a_entry;    /* start address (unused in object modules) */
  unsigned long     a_trsize;   /* length of relocation info for text, in bytes */
  unsigned long     a_drsize;   /* length of relocation info for data, in bytes */
} EXEC;

#define A_OUT_MAGIC 0407        /* value of exec.a_info for object module */

typedef struct {                /* symbol for a.out file... */
  long              n_strx;
  unsigned char     n_type;
  char              n_other;
  short             n_desc;
  unsigned long     n_value;
} NLIST;

#define N_UNDF      0           /* symbol n_type flag values... */
#define N_EXT       1
#define N_ABS       2
#define N_TEXT      4
#define N_DATA      6
#define N_BSS       8
#define N_FN       15
#define N_COMM     18

typedef struct {                /* reloc directive for a.out file... */
  unsigned long     r_address;
  unsigned long     r_symboldat;
} RINFO;

#define RINFO_PCREL 0x80        /* values ORed into low byte of */
#define RINFO_LONG  0x40        /* rinfo.r_symboldat */
#define RINFO_WORD  0x20
#define RINFO_EXT   0x10

/*----------------------------------------------------------------------------
 * internal data.
 *--------------------------------------------------------------------------*/

static EXEC  thehdr;
static short inhandle;
static char  *fname;
static char  *strtab;
static NLIST *symtab;

static char  *text;
static char  *data;

static long  nsyms;
static long  ntrelocs;
static long  ndrelocs;

static long  toffset;
static long  doffset;
static long  treloffset;
static long  dreloffset;
static long  symoffset;
static long  stroffset;

/*----------------------------------------------------------------------------
 * code.
 *--------------------------------------------------------------------------*/

#ifdef __STDC__
  void fatal(char *,...);
  void gfree(void *);
  void *emalloc(long);
  void read_error(char *);
  void load_show_header(void);
  void load_text(void);
  void load_data(void);
  void load_symbol_data(void);
  void show_a_symbol(NLIST *);
  void load_show_symbol_table(void);
  void show_a_reloc(RINFO *,char *);
  void load_show_reloc(char *,char *,long,long);
  void load_show_file(char *);
  int main(int,char **);
#endif

/*----------------------------------------------------------------------------
 * service routines.
 *--------------------------------------------------------------------------*/

#ifdef __STDC__
  static void fatal(char *fmt, ...)
#else
  static void fatal(fmt)
    char *fmt;
#endif
/*****************************************************************************
 * whine and die.
 ****************************************************************************/
{
    va_list args;

    fputs("dumpaout (fatal): ", stderr);
    va_start(args, fmt);
    vfprintf(stderr, fmt, args);
    va_end(args);
    fputc('\n', stderr);
    exit(1);
}

static void gfree(block)
    void *block;
/*****************************************************************************
 * gentle-free; free the block if the pointer is non-NULL.
 ****************************************************************************/
{
    if (block) {
        Mfree(block);
    }
}

static void *emalloc(size)
    long size;
/*****************************************************************************
 * allocate a block; whine and die if it fails.
 ****************************************************************************/
{
    void *block;

    if (NULL == (block = (void*)Malloc(size))) {
        fatal("out of memory processing file %s, asked for %ld", fname, size);
    }
    return block;
}

static void read_error(type)
    char *type;
/*****************************************************************************
 *
 ****************************************************************************/
{
    fatal("Error reading %s on file %s\n", type, fname);
}

static void load_show_header()
/*****************************************************************************
 *
 ****************************************************************************/
{
    register EXEC *h = &thehdr;

    if (sizeof(EXEC) != Fread(inhandle, (long)sizeof(EXEC), h)) {
        read_error("header");
    }

    nsyms    = h->a_syms   / sizeof(NLIST);
    ntrelocs = h->a_trsize / sizeof(RINFO);
    ndrelocs = h->a_drsize / sizeof(RINFO);

    toffset    = sizeof(EXEC);
    doffset    = toffset    + h->a_text;
    treloffset = doffset    + h->a_data;
    dreloffset = treloffset + h->a_trsize;
    symoffset  = dreloffset + h->a_drsize;
    stroffset  = symoffset  + h->a_syms;

    printf("Header for file %s...\n"
           "  a_info                  = %08lX\n"
           "  Text size               = %08lX\n"
           "  Data size               = %08lX\n"
           "  BSS size                = %08lX\n"
           "  Sym size, count         = %08lX, %5ld\n"
           "  Text reloc size, count  = %08lX, %5ld\n"
           "  Data reloc size, count  = %08lX, %5ld\n"
           "\n",
           fname,
           h->a_info,
           h->a_text,
           h->a_data,
           h->a_bss,
           h->a_syms,   nsyms,
           h->a_trsize, ntrelocs,
           h->a_drsize, ndrelocs
          );

    if (A_OUT_MAGIC != (h->a_info & 0x0000FFFF)) {
        fatal("Bad magic in %s file header", fname);
    }

}

static void load_text()
/*****************************************************************************
 *
 ****************************************************************************/
{
    if (thehdr.a_text == 0) {
        return;
    }
    text = emalloc(thehdr.a_text);
    Fseek(toffset, inhandle, SEEK_SET);
    if (thehdr.a_text != Fread(inhandle, thehdr.a_text, text)) {
        read_error("text segment");
    }
}

static void load_data()
/*****************************************************************************
 *
 ****************************************************************************/
{
    if (thehdr.a_data == 0) {
        return;
    }
    data = emalloc(thehdr.a_data);
    Fseek(doffset, inhandle, SEEK_SET);
    if (thehdr.a_data != Fread(inhandle, thehdr.a_data, data)) {
        read_error("data segment");
    }
}

static void load_symbol_data()
/*****************************************************************************
 *
 ****************************************************************************/
{
    long    strtabsize;

    if (thehdr.a_syms == 0) {
        return;
    }

    Fseek(stroffset, inhandle, SEEK_SET);
    if (sizeof(strtabsize) != Fread(inhandle, (long)sizeof(strtabsize), &strtabsize)) {
        read_error("string table size");
    }

    symtab = emalloc(thehdr.a_syms);
    strtab = emalloc(strtabsize);

    Fseek(symoffset, inhandle, SEEK_SET);
    if (thehdr.a_syms != Fread(inhandle, thehdr.a_syms, symtab)) {
        read_error("symbol table");
    }
    if (strtabsize != Fread(inhandle, strtabsize, strtab)) {
        read_error("string table");
    }
}

static void show_a_symbol(sym)
    register NLIST *sym;
/*****************************************************************************
 *
 ****************************************************************************/
{
    char *symname;
    char *symscope;
    char *symtype;

    if (sym->n_strx == 0) {
        symname = "<No Name>";
    } else {
        symname = &strtab[sym->n_strx];
    }

    symscope = (sym->n_type & N_EXT) ? "EXT " : "    ";

    switch (sym->n_type & 0x00FE) {
      case N_ABS:   symtype = "ABS  "; break;
      case N_TEXT:  symtype = "TEXT "; break;
      case N_DATA:  symtype = "DATA "; break;
      case N_BSS:   symtype = "BSS  "; break;
      case N_FN:    symtype = "FNAM "; break;
      default:      symtype = "UNKN "; break;
    }

    printf("  %-24s %s%s %08lX\n", symname, symscope, symtype, sym->n_value);
}

static void load_show_symbol_table()
/*****************************************************************************
 *
 ****************************************************************************/
{
    long i;

    load_symbol_data();

    printf("Symbol table for file %s\n", fname);

    if (thehdr.a_syms == 0) {
        printf("<no symbols in file>\n");
        return;
    }

    for (i = 0; i < nsyms; ++i) {
        show_a_symbol(&symtab[i]);
    }

    printf("\n");
}

static void show_a_reloc(rinfo, segaddr)
    register RINFO *rinfo;
    char           *segaddr;
/*****************************************************************************
 *
 ****************************************************************************/
{
    unsigned char flags;
    unsigned long symnum;
    unsigned long segvalue;
    char          *type;
    char          *size;

    flags  = rinfo->r_symboldat & 0x000000FF;
    symnum = rinfo->r_symboldat >> 8;

    switch (flags & (RINFO_LONG|RINFO_WORD)) {
      case RINFO_LONG:
        size = "LONG";
        segvalue = *(unsigned long *)(segaddr+rinfo->r_address);
        break;
      case RINFO_WORD:
        size = "WORD";
        segvalue = *(unsigned short *)(segaddr+rinfo->r_address);
        break;
      default:
        size = "BYTE";
        segvalue = *(unsigned char *)(segaddr+rinfo->r_address);
        break;
    }

    if (flags & RINFO_PCREL) {
        type = "PC";
    } else {
        type = "  ";
    }

    printf("  %08lX %s %s =%08lX ", rinfo->r_address, size, type, segvalue);

    if (flags & RINFO_EXT) {
        if (symnum > nsyms) {
            printf("?? reference to symbol # %ld of %ld syms in file ??\n",
                    symnum, nsyms);
        } else {
            printf("SYM =");
            show_a_symbol(&symtab[symnum]);
        }
    } else {
        switch (symnum & 0x000000FE) {
          case N_TEXT: type = "TEXT"; break;
          case N_DATA: type = "DATA"; break;
          case N_BSS:  type = "BSS "; break;
          default:     type = "UNKN"; break;
        }
        printf("SEG = %s\n", type, symnum);
    }
}

static void load_show_reloc(segname, segaddr, offset, count)
    char *segname;
    char *segaddr;
    long offset;
    long count;
/*****************************************************************************
 *
 ****************************************************************************/
{
    long  i;
    RINFO rinfo;

    printf("%s segment relocation info for file %s\n", segname, fname);
    Fseek(offset, inhandle, SEEK_SET);
    for (i = 0; i < count; ++i) {
        if (sizeof(rinfo) != Fread(inhandle, (long)sizeof(rinfo), &rinfo)) {
            read_error("relocation data");
        }
        show_a_reloc(&rinfo, segaddr);
    }
    printf("\n");
}

static void load_show_file(name)
    char *name;
/*****************************************************************************
 *
 ****************************************************************************/
{
    fname = name;

    if (0 > (inhandle = Fopen(name, 0))) {
        fatal("Can't open file %s\n", name);
    }

    load_show_header();
    load_text();
    load_data();
    load_show_symbol_table();
    load_show_reloc("TEXT", text, treloffset, ntrelocs);
    load_show_reloc("DATA", data, dreloffset, ndrelocs);
    printf("\n");

    gfree(symtab);
    gfree(strtab);
    gfree(data);
    gfree(text);
    Fclose(inhandle);
}

int main(argc, argv)
    int  argc;
    char **argv;
/*****************************************************************************
 *
 ****************************************************************************/
{
    if (argc < 2) {
        fputs("usage: DUMPAOUT file [file...]\n"
              "\n"
              "Displays header, symbol, and relocation info from\n"
              "one or more GNU a.out object modules.\n"
              ,stderr);
        exit(1);
    }

    while (--argc) {
        load_show_file(*++argv);
    }

    return 0;
}
