/* Copyright (C) 2016 DJ Delorie, see COPYING.DJ for details */
/* Copyright (C) 2015 DJ Delorie, see COPYING.DJ for details */
/* Copyright (C) 2013 DJ Delorie, see COPYING.DJ for details */
/* Copyright (C) 2012 DJ Delorie, see COPYING.DJ for details */
/* Copyright (C) 1999 DJ Delorie, see COPYING.DJ for details */
/* Copyright (C) 1996 DJ Delorie, see COPYING.DJ for details */
/* Copyright (C) 1995 DJ Delorie, see COPYING.DJ for details */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include <pc.h>

#include "sysdep.h"
#include "bfd.h"
#include "bfdver.h"
#include "getopt.h"
#include "libiberty.h"
#include "demangle.h"

#define SC(r,c) (*(char *)(sc + (r) * screen_cols + (c)))
#define SW(r,c) (*(sc + (r) * screen_cols + (c)))

#undef basename

static asymbol **syms;		/* Symbol table.  */
static asymbol **sorted_syms;	/* Sorted symbol table.  */
static long symcount;
static long sorted_symcount;

/* Dummy functions, for now.  */

static void
bfd_fatal(const char *msg)
{
  fprintf(stderr, "BFD fatal: %s\n", msg);
  exit(1);
}

static void
bfd_nonfatal(const char *msg)
{
  fprintf(stderr, "BFD warning: %s\n", msg);
}

static void
list_matching_formats(char **formats)
{
}

static void
set_default_bfd_target(void)
{
  bfd_set_default_target("coff-go32-exe");
}
/* Read in the symbol table.  */

static void
slurp_symtab(bfd *bfdp)
{
  long storage;

  if ((bfd_get_file_flags(bfdp) & HAS_SYMS) == 0)
    return;

  storage = bfd_get_symtab_upper_bound(bfdp);
  if (storage < 0)
    bfd_fatal(bfd_get_filename(bfdp));

  syms = (asymbol **)xmalloc(storage);

  symcount = bfd_canonicalize_symtab(bfdp, syms);
  if (symcount < 0)
    bfd_fatal(bfd_get_filename(bfdp));
}

/* Filter out (in place) symbols that are useless for disassembly.
   COUNT is the number of elements in SYMBOLS.
   Return the number of useful symbols. */

static long
remove_useless_symbols(asymbol **symbols, long count)
{
  register asymbol **in_ptr = symbols, **out_ptr = symbols;

  while (--count >= 0)
  {
    asymbol *sym = *in_ptr++;

    if (sym->name == NULL || sym->name[0] == '\0')
      continue;
    /* The funky symbols .bb, .eb, .ef, .bf get in the way.  */
    if (sym->name[0] == '.'
        && (sym->name[1] == 'b' || sym->name[1] == 'e')
        && (sym->name[2] == 'b' || sym->name[2] == 'f')
        && sym->name[3] == '\0')
      continue;
    if (strncmp(sym->name, ".debug_", 7) == 0)
      continue;
    if (sym->flags & (BSF_DEBUGGING))
      continue;
    if (bfd_is_und_section(sym->section)
        || bfd_is_com_section(sym->section))
      continue;

    *out_ptr++ = sym;
  }
  return out_ptr - symbols;
}

/* Sort symbols into value order.  */

static int
compare_symbols(const PTR ap, const PTR bp)
{
  const asymbol *a = *(const asymbol **)ap;
  const asymbol *b = *(const asymbol **)bp;
  const char *an, *bn;
  size_t anl, bnl;
  bool af, bf;
  flagword aflags, bflags;

  if (bfd_asymbol_value(a) > bfd_asymbol_value(b))
    return 1;
  else if (bfd_asymbol_value(a) < bfd_asymbol_value(b))
    return -1;

  if (a->section > b->section)
    return 1;
  else if (a->section < b->section)
    return -1;

  an = bfd_asymbol_name(a);
  bn = bfd_asymbol_name(b);
  anl = strlen(an);
  bnl = strlen(bn);

  /* The symbols gnu_compiled and gcc2_compiled convey no real
     information, so put them after other symbols with the same value.  */

  af = (strstr (an, "gnu_compiled") != NULL
        || strstr (an, "gcc2_compiled") != NULL);
  bf = (strstr (bn, "gnu_compiled") != NULL
        || strstr (bn, "gcc2_compiled") != NULL);

  if (af && ! bf)
    return 1;
  if (! af && bf)
    return -1;

  /* We use a heuristic for the file name, to try to sort it after
     more useful symbols.  It may not work on non Unix systems, but it
     doesn't really matter; the only difference is precisely which
     symbol names get printed.  */

#define file_symbol(s, sn, snl)          \
  (((s)->flags & BSF_FILE) != 0          \
   || ((sn)[(snl) - 2] == '.'            \
       && ((sn)[(snl) - 1] == 'o'        \
           || (sn)[(snl) - 1] == 'a')))

  af = file_symbol(a, an, anl);
  bf = file_symbol(b, bn, bnl);

  if (af && ! bf)
    return 1;
  if (! af && bf)
    return -1;

  /* Try to sort global symbols before local symbols before function
     symbols before debugging symbols.  */

  aflags = a->flags;
  bflags = b->flags;

  if ((aflags & BSF_DEBUGGING) != (bflags & BSF_DEBUGGING))
  {
    if ((aflags & BSF_DEBUGGING) != 0)
      return 1;
    else
      return -1;
  }
  if ((aflags & BSF_FUNCTION) != (bflags & BSF_FUNCTION))
  {
    if ((aflags & BSF_FUNCTION) != 0)
      return -1;
    else
     return 1;
  }
  if ((aflags & BSF_LOCAL) != (bflags & BSF_LOCAL))
  {
    if ((aflags & BSF_LOCAL) != 0)
      return 1;
    else
     return -1;
  }
  if ((aflags & BSF_GLOBAL) != (bflags & BSF_GLOBAL))
  {
    if ((aflags & BSF_GLOBAL) != 0)
      return -1;
    else
      return 1;
  }

  /* Symbols that start with '.' might be section names, so sort them
     after symbols that don't start with '.'.  */
  if (an[0] == '.' && bn[0] != '.')
    return 1;
  if (an[0] != '.' && bn[0] == '.')
    return -1;

  /* Finally, if we can't distinguish them in any other way, try to
     get consistent results by sorting the symbols by name.  */
  return strcmp(an, bn);
}

static bfd *
init_bfd_syms_or_die(const char *prog, const char *tgt)
{
  char **matching;
  bfd  *bfdp;

  bfdp = bfd_openr(prog, tgt);
  if (bfdp == NULL)
    bfd_fatal(prog);

  if (bfd_check_format(bfdp, bfd_archive))
    fprintf (stderr, "%s: can not get addresses from archive\n", prog);

  if (!bfd_check_format_matches(bfdp, bfd_object, &matching))
  {
    bfd_nonfatal(bfd_get_filename(bfdp));
    if (bfd_get_error() == bfd_error_file_ambiguously_recognized)
    {
      list_matching_formats(matching);
      free(matching);
    }
    exit(1);
  }

  slurp_symtab(bfdp);

  /* We make a copy of syms to sort.  We don't want to sort syms
     because that will screw up the relocs.  */
  sorted_syms = (asymbol **) xmalloc(symcount * sizeof (asymbol *));
  memcpy(sorted_syms, syms, symcount * sizeof (asymbol *));

  sorted_symcount = remove_useless_symbols(sorted_syms, symcount);

  /* Sort the symbols into section and symbol order */
  qsort(sorted_syms, sorted_symcount, sizeof (asymbol *), compare_symbols);

  return bfdp;
}

/* Locate a symbol given a bfd, a section, and a VMA.  */

static asymbol *
find_symbol_for_address(bfd *abfd, asection *sec, bfd_vma vma)
{
  /* @@ Would it speed things up to cache the last two symbols returned,
     and maybe their address ranges?  For many processors, only one memory
     operand can be present at a time, so the 2-entry cache wouldn't be
     constantly churned by code doing heavy memory accesses.  */

  /* Indices in `sorted_syms'.  */
  long min = 0;
  long max = sorted_symcount;
  long this_place;

  if (sorted_symcount < 1)
    return NULL;

  /* Perform a binary search looking for the closest symbol to the
     required value.  We are searching the range (min, max].  */
  while (min + 1 < max)
  {
    asymbol *sym;

    this_place = (max + min) / 2;
    sym = sorted_syms[this_place];

    if (bfd_asymbol_value(sym) > vma)
      max = this_place;
    else if (bfd_asymbol_value(sym) < vma)
      min = this_place;
    else
    {
      min = this_place;
      break;
    }
  }

  /* The symbol we want is now in min, the low end of the range we
     were searching.  If there are several symbols with the same
     value, we want the first one.  */
  this_place = min;
  while (this_place > 0
         && (bfd_asymbol_value(sorted_syms[this_place])
             == bfd_asymbol_value(sorted_syms[this_place - 1])))
    --this_place;

  /* If the file is relocateable, and the symbol could be from this
     section, prefer a symbol from this section over symbols from
     others, even if the other symbol's value might be closer.

     Note that this may be wrong for some symbol references if the
     sections have overlapping memory ranges, but in that case there's
     no way to tell what's desired without looking at the relocation
     table.  */

  if (sorted_syms[this_place]->section != sec
      && ((abfd->flags & HAS_RELOC) != 0
          && vma >= bfd_get_section_vma(abfd, sec)
          && vma < (bfd_get_section_vma(abfd, sec)
                    + bfd_section_size(abfd, sec))))
  {
    long i;

    for (i = this_place + 1; i < sorted_symcount; i++)
    {
      if (bfd_asymbol_value(sorted_syms[i])
          != bfd_asymbol_value(sorted_syms[this_place]))
        break;
    }
    for (--i; i >= 0; i--)
    {
      if (sorted_syms[i]->section == sec
          && (i == 0
              || sorted_syms[i - 1]->section != sec
              || (bfd_asymbol_value(sorted_syms[i])
                  != bfd_asymbol_value(sorted_syms[i - 1]))))
      {
        this_place = i;
        break;
      }
    }

    if (sorted_syms[this_place]->section != sec)
    {
      /* We didn't find a good symbol with a smaller value.
         Look for one with a larger value.  */
      for (i = this_place + 1; i < sorted_symcount; i++)
      {
        if (sorted_syms[i]->section == sec)
        {
          this_place = i;
          break;
        }
      }
    }

    if (sorted_syms[this_place]->section != sec
        && ((abfd->flags & HAS_RELOC) != 0
             && vma >= bfd_get_section_vma(abfd, sec)
             && vma < (bfd_get_section_vma(abfd, sec)
                       + bfd_section_size(abfd, sec))))
    {
      /* There is no suitable symbol.  */
      return NULL;
    }
  }

  return sorted_syms[this_place];
}

/* These global variables are used to pass information between
   translate_address and find_address_in_section.  */

static bfd_vma pc;
static const char *filename;
static const char *functionname;
static unsigned int linenum;
static unsigned long offset;
static int found;

/* Look for an address in a section.  This is called via
   bfd_map_over_sections.  */

static void
find_address_in_section(bfd *bfdp, asection *section, void *data)
{
  bfd_vma vma;
  bfd_size_type size;

  if (found)
    return;

  if ((bfd_get_section_flags(bfdp, section) & SEC_ALLOC) == 0)
    return;

  vma = bfd_get_section_vma(bfdp, section);
  if (pc < vma)
    return;

  size = bfd_get_section_size(section);
  if (pc >= vma + size)
    return;

  found = bfd_find_nearest_line(bfdp, section, syms, pc - vma,
                                &filename, &functionname, &linenum);
}

static void
find_symbol_in_section(bfd *bfdp, asection *section, void *data)
{
  asymbol *sym0, *sym;
  bfd_vma vma;

  if (found)
    return;

  vma = bfd_get_section_vma(bfdp, section);
  if (pc < vma)
    return;

  sym = sym0 = find_symbol_for_address(bfdp, section, pc);
  if (sym)
  {
    const char *f = bfd_asymbol_name(sym);
    int i = 0;

    if (!functionname || *functionname == '\0')
      functionname = f;

    /* find_symbol_for_address has a nasty habit of finding local
       labels and other irrelevant symbols that are closer to PC
       than the enclosing function.  This loop searches backwards
       until it finds a symbol with the same name as FUNCTIONNAME.  */
    while (i < 0x1000 && strcmp(f, functionname) != 0)
    {
      sym = find_symbol_for_address(bfdp, section,
                                      bfd_asymbol_value(sym) - 1);
      if (!sym)
        break;
      f = bfd_asymbol_name(sym);
      i++;		/* don't look too far... */
    }
    if (i >= 0x1000)	/* not found--fall back on the original guess */
      sym = sym0;
  }
  if (sym)
  {
    found = 1;
    functionname = bfd_asymbol_name(sym);
    offset = pc - bfd_asymbol_value(sym);
  }
}

static void
translate_address(unsigned v, bfd *bfdp,
                  char **func, int *lineno, char **file, unsigned long *offs)
{
  char *demangled_name;

  pc = v;
  found = 0;
  bfd_map_over_sections(bfdp, find_address_in_section, (void *)NULL);
  if (!found)
  {
    *func = NULL;
    *file = NULL;
    *lineno = -1;
    *offs = 0;
    functionname = NULL;
  }
  else
    *file = (char *)filename;
  if (!functionname || *functionname == '\0')
  {
    *lineno = -1;
    *func = NULL;
  }
  else
    *func = (char *)functionname;
  found = 0;
  bfd_map_over_sections(bfdp, find_symbol_in_section, (void *)NULL);
  if (found)
  {
    /* The convoluted comparison with `main' is so that we filter
       out false attributions of EIPs in library functions to the
       last line of the main function.  */
    if (*func
         && (strcmp (*func, "main") == 0
             || strcmp (*func, "_main") == 0)
         && strcmp (functionname, "main") != 0
         && strcmp (functionname, "_main") != 0)
    {
      *file = NULL;
      *lineno = -1;
    }
    *func = (char *)functionname;
  }
  if (found && *func && **func != '\0')
  {
    if (**func == bfd_get_symbol_leading_char(bfdp))
      ++*func;
    demangled_name = cplus_demangle(*func, DMGL_ANSI | DMGL_PARAMS);
    if (demangled_name)
      *func = demangled_name;
    *lineno = linenum;
    *offs = offset;
  }
}

int main(int argc, char **argv)
{
  int r, c;
  short *sc;
  char *buf = NULL;
  size_t bufsize = 0;
  int i, lineno;
  unsigned v;
  unsigned long d;
  char *func, *file;
  FILE *ofile=0;
  FILE *ifile=0;
  char *progname = NULL;
  char *bfdsymify;
  bfd  *abfd = NULL;
  char *target = NULL;
  int   screen_rows, screen_cols, cursor_row, cursor_col;

  bfdsymify = argv[0];

  if (argc < 2)
  {
    fprintf(stderr,
	    "BFDSymify Version 1.2, 2002-02-24 (BFD version %s)\n",
            BFD_VERSION_STRING);
    fprintf(stderr,
	    "Add source file information to a DJGPP program's call-frame traceback\n\n");
    fprintf(stderr, "Usage: bfdsymify [-o <outfile>] [-i <corefile>] <program>\n");
    return 1;
  }
  while (argv[1][0] == '-')
  {
    if ((strcmp(argv[1], "-o") == 0) && (argc > 3))
    {
      ofile = fopen(argv[2], "w");
      if (ofile == 0)
        fprintf(stderr, "Error: unable to open file %s\n", argv[2]);
      argc -= 2;
      argv += 2;
    }
    else if ((strcmp(argv[1], "-i") == 0) && (argc > 3))
    {
      ifile = fopen(argv[2], "r");
      if (ifile == 0)
        fprintf(stderr, "Error: unable to open file %s\n", argv[2]);
      argc -= 2;
      argv += 2;
    }
    else
    {
      fprintf(stderr, "Invalid option %s - type `%s' for help\n",
	      argv[1], bfdsymify);
      exit(1);
    }
  }

  progname = argv[1];
  bfd_init();
  set_default_bfd_target();

  if (ifile)
  {
    char line[1000];
    if (ofile == 0)
      ofile = stdout;
    while (fgets(line, 1000, ifile))
    {
      if (strncmp(line + 25, " program=", 9) == 0)
      {
	fputs(line, ofile);
	if (progname)
	{
	  char *arg_base = basename(progname), *prog_base = basename(line + 9);
	  if (strcasecmp(prog_base, arg_base)
	      && strncasecmp(prog_base, arg_base, strlen(prog_base) - 4))
	    fprintf(stderr,
		    "!!! Program name in corefile doesn't match `%s'\n",
		    progname);
	}
	else
	  progname = strdup(line + 9);
      }
      else if (strncmp(line, "  0x", 4) == 0)
      {
	if (progname == NULL)
	{
	  progname = "a.exe";
	  if (access(progname, R_OK))
	  {
	    progname = "a.out";
	    if (access(progname, R_OK))
	    {
	      fprintf(stderr, "Error: no usable program name given.\n");
	      exit (1);
	    }
	  }
	}
	if (abfd == NULL)
	  abfd = init_bfd_syms_or_die(progname, target);
        sscanf(line+4, "%x", &v);

	translate_address(v, abfd, &func, &lineno, &file, &d);
        fprintf(ofile, "  0x%08x", v);
        if (func)
        {
          fprintf(ofile, " %s", func);
          if (d)
            fprintf(ofile, "%+ld", d);
        }
        if (file)
        {
          if (func)
            fprintf(ofile, ", ");
	  fprintf(ofile, "file %s", file);
	  if (lineno > 0)
	    fprintf (ofile, ", line %d", lineno);
        }
        fputc('\n', ofile);
      }
      else
        fputs(line, ofile);
    }

    if (syms != NULL)
    {
      free(syms);
      syms = NULL;
      free(sorted_syms);
    }

    if (abfd)
      bfd_close(abfd);
    return 0;
  }

  screen_rows = ScreenRows();
  screen_cols = ScreenCols();
  sc = (short *)malloc(screen_rows * screen_cols * 2);

  ScreenGetCursor(&cursor_row, &cursor_col);

  ScreenRetrieve(sc);	/* FIXME: handle graphics modes! */

  bufsize = screen_cols + 10;
  buf = xmalloc (bufsize);

  for (r = 0; r < screen_rows; r++)
  {
    if (SC(r,25) == ' '
	&& SC(r,26) == 'p' && SC(r,27) == 'r' && SC(r,28) == 'o'
	&& SC(r,29) == 'g' && SC(r,30) == 'r' && SC(r,31) == 'a'
	&& SC(r,32) == 'm' && SC(r,33) == '=')
    {
      char prog[FILENAME_MAX];

      /* The test for blank relies on the fact that DJGPP programs
	 always get short 8+3 aliases as their DOS argv[0], and those
	 cannot have embedded blanks.  */
      for (i = 26; SC(r,i) != ' ' && SC(r,i) != '\0'; i++)
         prog[i - 26] = SC(r, i);
      prog[i - 26] = 0;
      /* If the name is extremely long, it may overflow into the next
	 screen line, and we err by one row at this point.  (We cannot
	 possibly be more than one row off, since short path names are
	 never longer than 80 characters.)  */
      if (i >= screen_cols)
      {
	r += 1;
	i -= screen_cols;
      }
      if (progname)
      {
	char *arg_base = basename(progname), *prog_base = basename(prog);

	if (strcasecmp(prog_base, arg_base)
	    && (strncasecmp(prog_base, arg_base, strlen(prog_base) - 4)
		|| arg_base[strlen(prog_base) - 4])) /* FIXME: LFN and 8+3 */
	  {
	    sprintf(buf,
		    "!!! Program name on screen doesn't match `%s'",
		    progname);

	    for (i = 0; buf[i]; i++)
	      SW(cursor_row, i) = 0x0700 + buf[i];
	  }
      }
      else
	progname = strdup(prog);
    }
    else if (SC(r, 0) == ' ' && SC(r, 1) == ' '
	     && SC(r, 2) == '0' && SC(r, 3) == 'x')
    {
      int l_left = bufsize - 1, l_func = 0, l_off = 0, l_file = 0, l_line = 0;
      if (progname == NULL)
      {
	progname = "a.exe";
	if (access(progname, R_OK))
	{
	  progname = "a.out";
	  if (access(progname, R_OK))
	  {
	    ScreenSetCursor(cursor_row, cursor_col);
	    fprintf(stderr, "Error: no usable program name given.\n");
	    exit (1);
	  }
	}
      }
      if (abfd == NULL)
	abfd = init_bfd_syms_or_die(progname, target);
      buf[8] = 0;
      for (i = 0; i < 8; i++)
        buf[i] = SC(r, i + 4);
      sscanf(buf, "%x", &v);
      translate_address(v, abfd, &func, &lineno, &file, &d);
      if (ofile)
	fprintf (ofile, "  0x%08x", v);
      buf[0] = 0;
      if (func)
      {
	l_func = strlen (func);
	if (l_func > l_left - 10)
	{
	  bufsize += l_func + 10;
	  l_left += l_func + 10;
	  buf = xrealloc (buf, bufsize);
	}
	strcat(buf, func);
	l_left -= l_func;
	if (d)
	{
	  l_left -= sprintf(buf + l_func, "%+ld", d);
	  l_off = strlen(buf);
	}
	else
	  l_off = l_func;
      }
      if (file)
      {
	l_file = strlen(file);
	if (l_left < l_file + 25)
	{
	  bufsize += l_file + 25;
	  l_left += l_file + 25;
	  buf = xrealloc (buf, bufsize);
	}
        if (buf[0])
	{
          strcat(buf, ", ");
	  l_left -= 2;
	}
        sprintf(buf + strlen(buf), "file %s", file);
	l_file = strlen(buf);
	if (lineno > 0)
	  sprintf(buf + strlen(buf), ", line %d", lineno);
      }
      else
	l_file = l_off;
      l_line = strlen(buf);
      if (buf[0])
      {
	int j;
	int max_func =
	  l_line > screen_cols - 13 && l_off > screen_cols / 2 - 6
	  ? screen_cols / 2 - 8 - (l_off - l_func)
	  : l_off;
	int in_func = 1, in_file = 0;

	/* 0xdeadbeef _FooBar_Func..+666, file FooBarF..e.Ext, line 1234  */
	/*            ^             ^   ^                    ^          ^ */
	/*            0        l_func l_off             l_file     l_line */
	for (i = 0, j = 0; buf[j]; i++, j++)
	{
	  if (13 + i >= screen_cols)
	    break;
	  else if (in_func && i >= max_func && j < l_off)
	  {
	    SW(r, 13 + i) = 0x0700 + '.'; i++;
	    SW(r, 13 + i) = 0x0700 + '.'; i++;
	    j = l_func;		/* truncate function name, jump to offset */
	    in_func = 0;
	    in_file = 1;
	  }
	  else if (in_file
		   && 13 + i >= screen_cols - 8 - (l_line - l_file)
		   && j < l_file - 6)
	  {
	    SW(r, 13 + i) = 0x0700 + '.'; i++;
	    SW(r, 13 + i) = 0x0700 + '.'; i++;
	    j = l_file - 6;	/* jump to last portion of file name */
	    in_file = 0;
	  }
	  SW(r, 13 + i) = 0x0f00 + buf[j];
	}
	if (ofile)
	  fprintf (ofile, " %s\n", buf);
      }
    }
  }

  if (ofile)
  {
    for (r = 0; r < screen_rows; r++)
    {
      c = 0;
      for (i = 0; i < screen_cols; i++)
        if (SC(r, i) != ' ')
          c = i;
      for (i = 0; i <= c; i++)
        fputc(SC(r, i), ofile);
      fputc('\n', ofile);
    }
    fclose(ofile);
  }
  else
    ScreenUpdate(sc);	/* FIXME: graphics modes */

  if (syms != NULL)
  {
    free(syms);
    syms = NULL;
    free(sorted_syms);
  }

  if (abfd)
    bfd_close(abfd);
  return 0;
}
