/*
 *  smoke16/src/nm.c - list symbols from object files.
 *  $Revision: 1.12 $
 *  Copyright 1999-2002, Benjamin Sittler
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include <ctype.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include <stdlib.h>
#include <smoke16/types.h>
#include <smoke16/libObj.h>
#include "getopt.h" /* argument parser */

#if defined(__GNUC__) && (__GNUC__ > 1)
#define unused __attribute__((unused))
#else
#define unused
#endif

static char unused RCSID[] = "$Id: nm.c,v 1.12 2002/07/28 23:26:33 bsittler Exp $";

/* display a Sym in nm format */
void nmSym(Sym *sym,
	   int demangle) {
  if (! (sym -> type & SMOKE16_N_STAB)) {
    char t = '?';
    char *string = sym -> string;
    
    switch (sym -> type & SMOKE16_N_TYPE) {
    case SMOKE16_N_UNDF:
      t = 'u';
      break;
    case SMOKE16_N_ABS:
      t = 'a';
      break;
    case SMOKE16_N_TEXT:
      t = 't';
      break;
    case SMOKE16_N_DATA:
      t = 'd';
      break;
    case SMOKE16_N_BSS:
      t = 'b';
      break;
    case SMOKE16__N_COMM:
      t = 'c';
      break;
    }
    if (sym -> type & SMOKE16_N_EXT)
      t = toupper(t);
    if (sym -> value ||
	((sym -> type & SMOKE16_N_TYPE) != SMOKE16_N_UNDF))
      printf("%4.4" FMT_X16, sym -> value);
    else
      printf("    ");
    if (demangle && string && (*string == '_'))
      string ++;
    printf(" %c %s\n",
	   t,
	   string ? string : "");
  } else {
    printf("%4.4" FMT_X16 " - ",
	   sym -> value);
    if (sym -> string)
      printf("\"%s\",", sym -> string);
    printf("%d,%d,%u\n",
	   (int)sym -> other,
	   (int)sym -> desc,
	   (unsigned)(sym -> type & SMOKE16_N_STAB));
  }
}

/* display a file in nm format */
int nm(char *filename,
       int multiple,
       int print_file_name,
       int demangle,
       int print_armap,
       int print_mtime,
       int (*criterion)(Sym *)) {
  Obj *object = 0;
  FILE *infile;
  int ok = 1;
  
  if (filename &&
      (filename[0] == '-') &&
      (filename[1] == '\0')) {
    infile = stdin;
    filename = "stdin";
  } else
    infile = fopen(filename, "rb");
  if (infile) {
    struct SymSearch *search;
    struct Sym *sym;
    
    if (! (object = readObj(filename, infile))) {
      return 1;
    }
    if (multiple && ! print_file_name)
      printf("\n%s:\n", object -> name);
    if (object -> magic == SMOKE16_LMAGIC) {
      struct Member *member;
      
      if (print_armap) {
	printf("\nArchive index:\n");
	if (! (search = newSymSearch(object -> tab, anySym))) {
	  freeObj(object);
	  ok = 0;
	} else {
	  while ((sym = nextSym(search))) {
	    char *string = sym -> string;
	    u16 index;
	    
	    member = object -> first;
	    for (index = 0; index < sym -> value; index ++)
	      if (member)
		member = member -> next;
	    if (! member) {
	      fprintf(stderr,
		      "%s: archive member containing `%s' is missing\n",
		      filename, string);
	      ok = 0;
	      continue;
	    }
	    if (demangle && string && (*string == '_'))
	      string ++;
	    printf("%s in %s\n", string, member -> name);
	  }
	  freeSymSearch(search);
	}
      }
      for (member = object -> first; member; member = member -> next) {
	Obj *mobj;
	time_t t;
	
	if (! print_file_name) {
          if (multiple)
	    printf("\n%s:%s:\n", object -> name, member -> name);
	  else
	    printf("\n%s:\n", member -> name);
	}
	t = (time_t) member -> mtime;
	if (print_file_name)
	  printf("%s:%s:", object -> name, member -> name);
        if (print_mtime)
            printf("---- ! %s", ctime(&t));
	if (! (mobj = extractObj(object, member))) {
	  ok = 0;
	  continue;
	}
	if (! (search = newSymSearch(mobj -> tab, criterion))) {
	  freeObj(mobj);
	  ok = 0;
	  continue;
	}
	while ((sym = nextSym(search))) {
	  if (print_file_name)
	    printf("%s:%s:", object -> name, mobj -> name);
	  nmSym(sym,
		demangle);
	}
	freeSymSearch(search);
	freeObj(mobj);
      }
    } else {
      if (! (search = newSymSearch(object -> tab, criterion))) {
	freeObj(object);
	return 1;
      }
      while ((sym = nextSym(search))) {
	if (print_file_name)
	  printf("%s:", object -> name);
	nmSym(sym,
	      demangle);
      }
      freeSymSearch(search);
    }
    freeObj(object);
  } else {
    perror(filename);
    ok = 0;
  }
  if (infile && (infile != stdin))
    fclose(infile);
  return ! ok;
}

#define SUMMARY \
   "Summary: %s [--help] [--version] [options] object-file...\n"

#define USAGE "\
Usage: %s [options] [object-file...]\n\
Options:\n\
-a or\n\
--debug-syms            include debugging symbols\n\
-g or\n\
--extern-only           display only external symbols\n\
-h or\n\
--help                  show this message and exit\n\
-o or -A or\n\
--print-file-name       precede each symbol with its filename\n\
-s or\n\
--print-armap           list archive index table\n\
-C or\n\
--demangle              remove leading underscores from symbol names\n\
--no-demangle           disable demangling (default)\n\
-M or\n\
--print-mtime           print archive member modification times\n\
-V or\n\
--version               print nm version and exit\n\
"

#define VERSION \
   "SMOKE-16 nm $Revision: 1.12 $\n" \
   SMOKE16_WARRANTY

int main(int argc, char *argv[])
{
  int errors = 0;
  int i, option_index;
  int print_file_name = 0;
  int demangle = 0;
  int print_armap = 0;
  int print_mtime = 0;
  int (*criterion)(Sym *) = nondebugSym;
  static struct option long_options[] = {
    {"no-demangle", 0, 0, 0},
    {"debug-syms", 0, 0, 'a'},
    {"extern-only", 0, 0, 'g'},
    {"help", 0, 0, 'h'},
    {"print-file-name", 0, 0, 'o'},
    {"print-armap", 0, 0, 's'},
    {"print-mtime", 0, 0, 'M'},
    {"demangle", 0, 0, 'C'},
    {"version", 0, 0, 'V'},
    {0, 0, 0, 0}
  };

  while((i = getopt_long(argc, argv, "aghosACMV", long_options, &option_index))
	!= -1)
    switch(i) {
    case 0:
      switch (option_index) {
      case 0:
	demangle = 0;
	break;
      default:
	fprintf(stderr, SUMMARY, argv[0]);
	exit(2);
      }
      break;
    case 'a':
      criterion = anySym;
      break;
    case 'g':
      criterion = externorundfSym;
      break;
    case 'h':
      printf(USAGE, argv[0]);
      exit(0);
      break;
    case 'o':
    case 'A':
      print_file_name = 1;
      break;
    case 's':
      print_armap = 1;
      break;
    case 'C':
      demangle = 1;
      break;
    case 'M':
      print_mtime = 1;
      break;
    case 'V':
      printf(VERSION, SMOKE16_TOOLVERSION);
      exit(0);
      break;
    default:
      fprintf(stderr, SUMMARY, argv[0]);
      exit(2);
    }
  i = optind;
  if (i < argc)
    do
      errors = nm(argv[i],
		  argc - optind > 1,
		  print_file_name,
		  demangle,
		  print_armap,
                  print_mtime,
		  criterion);
    while(++i < argc);
  else
    errors = nm("a.out",
		0,
		print_file_name,
		demangle,
		print_armap,
                print_mtime,
		criterion);
  return errors;
}
