/* Copyright (C) 2021-2025 Free Software Foundation, Inc.
   Contributed by Oracle.

   This file is part of GNU Binutils.

   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 3, 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, 51 Franklin Street - Fifth Floor, Boston,
   MA 02110-1301, USA.  */

#include "config.h"
#include <unistd.h>
#include <fcntl.h>

#include "util.h"
#include "DbeApplication.h"
#include "DbeSession.h"
#include "Function.h"
#include "LoadObject.h"
#include "Module.h"
#include "DbeView.h"
#include "Print.h"
#include "DbeFile.h"
#include "Command.h"

class er_src : public DbeApplication
{
public:
  er_src (int argc, char *argv[]);
  void start (int argc, char *argv[]);

private:

  // override methods in base class
  void usage ();
  int check_args (int argc, char *argv[]);
  void run_args (int argc, char *argv[]);

  enum Obj_Types
  {
    OT_EXE_ELF = 0, OT_JAVA_CLASS, OT_JAR_FILE, OT_UNKNOWN
  };

  void open (char *exe);
  void dump_annotated (char *name, char* sel, char *src, DbeView *dbev,
		       bool is_dis, bool first);
  void checkJavaClass (char *exe);
  void print_header (bool first, const char* text);
  void proc_cmd (CmdType cmd_type, bool first, char *arg1,
		 const char *arg2, const char *arg3 = NULL);
  FILE *set_outfile (char *cmd, FILE *&set_file);

  bool is_java_class ()     { return obj_type == OT_JAVA_CLASS; }

  int dbevindex;
  DbeView *dbev;
  LoadObject *lo;
  Obj_Types obj_type;
  const char *out_fname;
  FILE *out_file;
  bool isDisasm;
  bool isFuncs;
  bool isDFuncs;
  bool isSrc;
  bool v_opt;
  int multiple;
  char *str_compcom;
};

static int
real_main (int argc, char *argv[])
{
  er_src *src = new er_src (argc, argv);
  src->start (argc, argv);
  delete src;
  return 0;
}

int
main (int argc, char *argv[])
{
  xmalloc_set_program_name (argv[0]);
  return catch_out_of_memory (real_main, argc, argv);
}

er_src::er_src (int argc, char *argv[])
: DbeApplication (argc, argv)
{
  obj_type = OT_UNKNOWN;
  out_fname = "<stdout>";
  out_file = stdout;
  isDisasm = false;
  isFuncs = false;
  isDFuncs = false;
  isSrc = false;
  v_opt = false;
  multiple = 0;
  lo = NULL;
}

static int
FuncNameCmp (const void *a, const void *b)
{
  Function *item1 = *((Function **) a);
  Function *item2 = *((Function **) b);
  return strcmp (item1->get_mangled_name (), item2->get_mangled_name ());
}

static int
FuncAddrCmp (const void *a, const void *b)
{
  Function *item1 = *((Function **) a);
  Function *item2 = *((Function **) b);
  return (item1->img_offset == item2->img_offset) ?
	  FuncNameCmp (a, b) : item1->img_offset > item2->img_offset ? 1 : -1;
}

void
er_src::usage ()
{

/*
  Ruud - Isolate this line because it has an argument.  Otherwise it would be at the
  end of a long usage list.
*/
  printf ( GTXT (
    "Usage: gprofng display src [OPTION(S)] TARGET-OBJECT\n"));

  printf ( GTXT (
    "\n"
    "Display the source code listing, or source code interleaved with disassembly code,\n"
    "as extracted from the target object (an executable, shared object, object file, or\n"
    "a Java .class file).\n"
    "\n"
    "Options:\n"
    "\n"
    " --version           print the version number and exit.\n"
    " --help              print usage information and exit.\n"
    " --verbose {on|off}  enable (on) or disable (off) verbose mode; the default is \"off\".\n"
    "\n"
    " -func                   list all the functions from the given object.\n"
    "\n"
    " -source item tag    show the source code for item; the tag is used to\n"
    "                     differentiate in case of multiple occurences with\n"
    "                     the same name; the combination of \"all -1\" selects\n"
    "                     all the functions in the object; the default is\n"
    "                     \"-source all -1\".\n"
    "\n"
    " -disasm item tag        show the source code, interleaved with the disassembled\n"
    "                         instructions; the same definitions for item and tag apply.\n"
    "\n"
    " -outfile <filename>     write results to file <filename>; a dash (-) writes to\n"
    "                         stdout; this is also the default; note that this only\n"
    "                         affects options included to the right of this option.\n"
    "\n"
   "Documentation:\n"
    "\n"
    "A getting started guide for gprofng is maintained as a Texinfo manual. If the info and\n"
    "gprofng programs are properly installed at your site, the command \"info gprofng\"\n"
    "should give you access to this document.\n"
    "\n"
    "See also:\n"
    "\n"
    "gprofng(1), gprofng-archive(1), gprofng-collect-app(1), "
    "gprofng-display-html(1), gprofng-display-text(1)\n"));
/*
  printf (GTXT ("Usage: %s [OPTION] a.out/.so/.o/.class\n\n"), whoami);
  printf (GTXT ("    -func                     List all the functions from the given object\n"
		"    -source, -src item tag    Show the annotated source for the listed item\n"
		"    -disasm item tag          Include the disassembly in the listing\n"
		"    -V                        Print the current release version of er_src\n"
		"    -cc, -scc, -dcc com_spec  Define the compiler commentary classes to show\n"
		"    -outfile filename         Open filename for output\n"));
*/
  exit (0);
}

void
er_src::start (int argc, char *argv[])
{
  dbevindex = dbeSession->createView (0, -1);
  dbev = dbeSession->getView (dbevindex);

  // get options
  check_args (argc, argv);
  run_args (argc, argv);
  if (out_file != stdout)
    fclose (out_file);
}

FILE *
er_src::set_outfile (char *cmd, FILE *&set_file)
{
  FILE *new_file;
  if (!strcasecmp (cmd, "-"))
    {
      new_file = stdout;
      out_fname = "<stdout>";
    }
  else
    {
      char *cmdpath;
      char *fname = strstr (cmd, "~/");
      // Handle ~ in file names
      char *home = getenv ("HOME");
      if (fname != NULL && home != NULL)
	cmdpath = dbe_sprintf ("%s/%s", home, fname + 2);
      else if ((fname = strstr (cmd, "~")) != NULL && home != NULL)
	cmdpath = dbe_sprintf ("/home/%s", fname + 1);
      else
	cmdpath = xstrdup (cmd);
      new_file = fopen (cmdpath, "w");
      if (new_file == NULL)
	{
	  fprintf (stderr, GTXT ("Unable to open file: %s"), cmdpath);
	  free (cmdpath);
	  return NULL;
	}
      out_fname = cmdpath;
    }
  if (set_file && (set_file != stdout))
    fclose (set_file);

  set_file = new_file;
  return set_file;
}

void
er_src::proc_cmd (CmdType cmd_type, bool first, char *arg1,
		  const char *arg2, const char *arg3)
{
  Cmd_status status;
  Module *module;
  Function *fitem;
  int mindex, findex;
  switch (cmd_type)
    {
    case SOURCE:
      dbev->set_view_mode (VMODE_USER);
      print_anno_file (arg1, arg2, arg3, false,
		       stdout, stdin, out_file, dbev, false);
      break;
    case DISASM:
      dbev->set_view_mode (VMODE_MACHINE);
      print_header (first, GTXT ("Annotated disassembly\n"));
      print_anno_file (arg1, arg2, arg3, true,
		       stdout, stdin, out_file, dbev, false);
      break;
    case OUTFILE:
      if (arg1)
	set_outfile (arg1, out_file);
      break;
    case FUNCS:
      print_header (false, GTXT ("Function list\n"));
      fprintf (out_file, GTXT ("Functions sorted in lexicographic order\n"));
      fprintf (out_file, GTXT ("\nLoad Object: %s\n\n"), lo->get_name ());
      if (lo->wsize == W32)
	fprintf (out_file, GTXT ("    Address     Size        Name\n\n"));
      else
	fprintf (out_file, GTXT ("    Address                     Size        Name\n\n"));

      Vec_loop (Module*, lo->seg_modules, mindex, module)
      {
	module->functions->sort (FuncNameCmp);
	const char *fmt = (lo->wsize == W32) ?
		GTXT ("  0x%08llx  %8lld      %s\n") :
		GTXT ("  0x%016llx  %16lld      %s\n");
	Vec_loop (Function*, module->functions, findex, fitem)
	{
	  fprintf (out_file, fmt,
		   (ull_t) fitem->img_offset,
		   (ull_t) fitem->size,
		   fitem->get_name ());
	}
      }
      break;
    case DUMPFUNC:
      lo->functions->sort (FuncAddrCmp);
      print_header (first, GTXT ("Dump functions\n"));
      lo->dump_functions (out_file);
      first = false;
      break;
    case SCOMPCOM:
      status = dbev->proc_compcom (arg1, true, false);
      if (status != CMD_OK)
	fprintf (stderr, GTXT ("Error: %s"), Command::get_err_string (status));
      break;
    case DCOMPCOM:
      status = dbev->proc_compcom (arg1, false, false);
      if (status != CMD_OK)
	fprintf (stderr, GTXT ("Error: %s"), Command::get_err_string (status));
      break;
    case COMPCOM:
      status = dbev->proc_compcom (arg1, true, false);
      if (status != CMD_OK)
	fprintf (stderr, GTXT ("Error: %s: %s"), Command::get_err_string (status), arg1);
      status = dbev->proc_compcom (arg1, false, false);
      if (status != CMD_OK)
	fprintf (stderr, GTXT ("Error: %s: %s"), Command::get_err_string (status), arg1);
      break;
    case HELP:
      usage ();
      break;
    case VERSION_cmd:
      if (out_file != stdout)
	Application::print_version_info ();
      break;
    default:
      fprintf (stderr, GTXT ("Invalid option"));
      break;
    }
}

void
er_src::run_args (int argc, char *argv[])
{
  CmdType cmd_type;
  int arg_count, cparam;
  char *arg;
  char *arg1;
  const char *arg2;
  bool first = true;
  bool space;
  Module *module;
  int mindex;

  for (int i = 1; i < argc; i++)
    {
      if (*argv[i] != '-')
	{
	  if (!multiple)
	    { // er_src -V exe
	      space = false;
	      dbev->set_view_mode (VMODE_USER);
	      print_header (first, GTXT ("Annotated source\n"));
	      Vec_loop (Module*, lo->seg_modules, mindex, module)
	      {
		if ((module->flags & MOD_FLAG_UNKNOWN) != 0 ||
		    module->lang_code == Sp_lang_unknown)
		  continue;
		if (space)
		  fprintf (out_file, "\n");
		print_anno_file (module->file_name, "1", NULL, false,
				 stdout, stdin, out_file, dbev, false);
		space = true;
	      }
	    }
	  break;
	}
      if (strncmp (argv[i], NTXT ("--whoami="), 9) == 0)
	{
	  whoami = argv[i] + 9;
	  continue;
	}
      switch (cmd_type = Command::get_command (argv[i] + 1, arg_count, cparam))
	{
	case SOURCE:
	case DISASM:
	  {
	    i += arg_count;
	    multiple++;
	    if (i >= argc || argv[i] == NULL ||
		(*(argv[i]) == '-' && atoi (argv[i]) != -1) || i + 1 == argc)
	      {
		i--;
		arg = argv[i];
		arg2 = "1";
	      }
	    else
	      {
		arg = argv[i - 1];
		if (*(argv[i]) == '-' && atoi (argv[i]) == -1 &&
		    streq (arg, NTXT ("all")))
		  {
		    space = false;
		    if (cmd_type == SOURCE)
		      print_header (first, GTXT ("Annotated source\n"));
		    else
		      print_header (first, GTXT ("Annotated disassembly\n"));
		    Vec_loop (Module*, lo->seg_modules, mindex, module)
		    {
		      if ((module->flags & MOD_FLAG_UNKNOWN) != 0 ||
			  module->lang_code == Sp_lang_unknown)
			continue;
		      if (space)
			fprintf (out_file, "\n");
		      proc_cmd (cmd_type, first, module->file_name, "1");
		      space = true;
		    }
		    first = false;
		    break;
		  }
		arg2 = argv[i];
	      }
	    char *fcontext = NULL;
	    arg1 = parse_fname (arg, &fcontext);
	    if (arg1 == NULL)
	      {
		fprintf (stderr, GTXT ("Error: Invalid function/file setting: %s\n"), arg1);
		free (fcontext);
		break;
	      }
	    proc_cmd (cmd_type, first, arg1, arg2, fcontext);
	    free (arg1);
	    free (fcontext);
	    first = false;
	    break;
	  }
	case OUTFILE:
	case FUNCS:
	case DUMPFUNC:
	case COMPCOM:
	case SCOMPCOM:
	case DCOMPCOM:
	case VERSION_cmd:
	case HELP:
	  proc_cmd (cmd_type, first, (arg_count > 0) ? argv[i + 1] : NULL,
		    (arg_count > 1) ? argv[i + 2] : NULL);
	  i += arg_count;
	  break;
	default:
	  if (streq (argv[i] + 1, NTXT ("all")) || streq (argv[i] + 1, NTXT ("dall")))
	    {
	      first = false;
	      multiple++;
	      if (streq (argv[i] + 1, NTXT ("all")))
		proc_cmd (FUNCS, first, NULL, NULL);
	      else
		proc_cmd (DUMPFUNC, first, NULL, NULL);
	      space = false;
	      print_header (first, GTXT ("Annotated source\n"));
	      Vec_loop (Module*, lo->seg_modules, mindex, module)
	      {
		if ((module->flags & MOD_FLAG_UNKNOWN) != 0 ||
		    module->lang_code == Sp_lang_unknown)
		  continue;
		if (space)
		  fprintf (out_file, "\n");
		proc_cmd (SOURCE, first, module->file_name, "1");
		space = true;
	      }
	      print_header (first, GTXT ("Annotated disassembly\n"));
	      Vec_loop (Module*, lo->seg_modules, mindex, module)
	      {
		if ((module->flags & MOD_FLAG_UNKNOWN) != 0 ||
		    module->lang_code == Sp_lang_unknown)
		  continue;
		if (space)
		  fprintf (out_file, "\n");
		proc_cmd (DISASM, first, module->file_name, "1");
		space = true;
	      }
	    }
	  else
	    {
	      proc_cmd (cmd_type, first, (arg_count > 0) ? argv[i + 1] : NULL,
			(arg_count > 1) ? argv[i + 2] : NULL);
	      i += arg_count;
	      break;
	    }
	}
    }
}

int
er_src::check_args (int argc, char *argv[])
{
  CmdType cmd_type = UNKNOWN_CMD;
  int arg_count, cparam;
  int i;
  char *exe;
  bool first = true;
  if (argc == 1)
    usage ();

  // If any comments from the .rc files, log them to stderr
  Emsg * rcmsg = fetch_comments ();
  while (rcmsg != NULL)
    {
      fprintf (stderr, "%s: %s\n", prog_name, rcmsg->get_msg ());
      rcmsg = rcmsg->next;
    }

  // Parsing the command line
  opterr = 0;
  exe = NULL;
  for (i = 1; i < argc; i++)
    {
      if (*argv[i] != '-')
	{
	  exe = argv[i];
	  if (i == 1)
	    { // er_src exe ?
	      if (!exe)
		usage ();
	      if (argc == 3) // er_src exe file
		usage ();
	    }
	  else if (v_opt && !multiple && !exe && !str_compcom) // just er_src -V
	      exit (0);
	  if (argc < i + 1 || argc > i + 3)
	    usage ();
	  i++;
	  if (argc > i)
	    usage ();
	  open (exe);
	  return i;
	}
      switch (cmd_type = Command::get_command (argv[i] + 1, arg_count, cparam))
	{
	case WHOAMI:
	  whoami = argv[i] + 1 + cparam;
	  break;
	case HELP:
	  i += arg_count;
	  multiple++;
	  usage ();
	  break;
	case VERSION_cmd:
	  if (first)
	    {
// Ruud
	      Application::print_version_info ();
/*
	      printf ("GNU %s version %s\n", get_basename (prog_name), VERSION);
*/
	      v_opt = true;
	      first = false;
	    }
	  break;
	case SOURCE:
	case DISASM:
	  i += arg_count;
	  multiple++;
	  isDisasm = true;
	  if (i >= argc || argv[i] == NULL ||
	      (*(argv[i]) == '-' && atoi (argv[i]) != -1) || (i + 1 == argc))
	    i--;
	  break;
	case DUMPFUNC:
	  i += arg_count;
	  multiple++;
	  break;
	case FUNCS:
	  i += arg_count;
	  multiple++;
	  break;
	case OUTFILE:
	case COMPCOM:
	case SCOMPCOM:
	case DCOMPCOM:
	  i += arg_count;
	  break;
	default:
	  if (!(streq (argv[i] + 1, NTXT ("all")) ||
		streq (argv[i] + 1, NTXT ("dall"))))
	    {
	      fprintf (stderr, "Error: invalid option: `%s'\n", argv[i]);
	      exit (1);
	    }
	}
    }
  if (!exe && !(argc == 2 && cmd_type == VERSION_cmd))
    usage ();
  return i;
}

void
er_src::checkJavaClass (char* exe)
{
  unsigned char cf_buf[4];
  unsigned int magic_number;
  int fd = ::open (exe, O_RDONLY | O_LARGEFILE);
  if (fd == -1)
    return;
  if (sizeof (cf_buf) == read_from_file (fd, cf_buf, sizeof (cf_buf)))
    {
      magic_number = cf_buf[0] << 24;
      magic_number |= cf_buf[1] << 16;
      magic_number |= cf_buf[2] << 8;
      magic_number |= cf_buf[3];
      if (magic_number == 0xcafebabe)
	obj_type = OT_JAVA_CLASS;
    }
  close (fd);
}

void
er_src::print_header (bool first, const char* text)
{
  if (!first)
    fprintf (out_file, "\n");
  if (multiple > 1)
    {
      fprintf (out_file, NTXT ("%s"), text);
      fprintf (out_file, "---------------------------------------\n");
    }
}

void
er_src::dump_annotated (char *name, char *sel, char *src, DbeView *dbevr,
			bool is_dis, bool first)
{
  Module *module;
  bool space;
  int mindex;
  print_header (first, (is_dis) ? ((is_java_class ()) ?
				   GTXT ("Annotated bytecode\n") :
				   GTXT ("Annotated disassembly\n")) :
		GTXT ("Annotated source\n"));
  if (!name)
    {
      space = false;
      Vec_loop (Module*, lo->seg_modules, mindex, module)
      {
	if ((module->flags & MOD_FLAG_UNKNOWN) != 0 ||
	    (!is_dis && module->lang_code == Sp_lang_unknown))
	  continue;
	if (space)
	  fprintf (out_file, "\n");
	print_anno_file (module->file_name, sel, src, is_dis,
			 stdout, stdin, out_file, dbevr, false);
	space = true;
      }
    }
  else
    print_anno_file (name, sel, src, is_dis, stdout, stdin, out_file, dbevr, false);
}

static bool
isFatal (bool isDisasm, LoadObject::Arch_status status)
{
  if (isDisasm)
    {
      switch (status)
	{
	  // non-fatal errors for disassembly
	case LoadObject::ARCHIVE_BAD_STABS:
	case LoadObject::ARCHIVE_NO_STABS:
	  return false;
	default:
	  return true;
	}
    }
  return true;
}

void
er_src::open (char *exe)
{
  LoadObject::Arch_status status;
  char *errstr;
  Module *module;
  Vector<Histable*> *module_lst;

  // Construct the Segment structure
  char *path = xstrdup (exe);
  lo = dbeSession->createLoadObject (path);
  if (NULL == lo->dbeFile->find_file (lo->dbeFile->get_name ()))
    {
      fprintf (stderr, GTXT ("%s: Error: unable to open file %s\n"), prog_name, lo->dbeFile->get_name ());
      exit (1);
    }
  checkJavaClass (exe);

  if (is_java_class ())
    {
      lo->type = LoadObject::SEG_TEXT;
      lo->set_platform (Java, Wnone);
      lo->id = (uint64_t) - 1; // see AnalyzerSession::ask_which for details
      module = dbeSession->createClassFile (dbe_strdup (exe));
      module->loadobject = lo;
      lo->seg_modules->append (module);
      module->dbeFile->set_location (exe);
      if (module->readFile () != module->AE_OK)
	{
	  Emsg *emsg = module->get_error ();
	  if (emsg)
	    {
	      fprintf (stderr, GTXT ("%s: Error: %s\n"), prog_name, emsg->get_msg ());
	      return;
	    }
	  fprintf (stderr, GTXT ("%s: Error: Could not read class file `%s'\n"), prog_name, exe);
	  return;
	}
      status = lo->sync_read_stabs ();
      if (status != LoadObject::ARCHIVE_SUCCESS)
	{
	  if (status == LoadObject::ARCHIVE_ERR_OPEN)
	    {
	      fprintf (stderr, GTXT ("%s: Error: Could not read class file `%s'\n"), prog_name, exe);
	      return;
	    }
	  else
	    {
	      if (isDisasm)
		if (status == LoadObject::ARCHIVE_NO_STABS)
		  {
		    fprintf (stderr, GTXT ("%s: Error: `%s' is interface; disassembly annotation not available\n"), prog_name, exe);
		    return;
		  }
	    }
	}
    }
  else
    {
      status = lo->sync_read_stabs ();
      if (status != LoadObject::ARCHIVE_SUCCESS)
	{
	  errstr = lo->status_str (status);
	  if (errstr)
	    {
	      fprintf (stderr, "%s: %s\n", prog_name, errstr);
	      free (errstr);
	    }
	  if (isFatal (isDisasm, status))
	    return;
	}
      obj_type = OT_EXE_ELF;

      // if .o file, then set file as the exe name
      if (lo->is_relocatable ())
	{
	  // find the module, if we can
	  module_lst = new Vector<Histable*>;
	  module = dbeSession->map_NametoModule (path, module_lst, 0);
	  if (module == NULL)
	    // Create a module with the right name
	    module = dbeSession->createModule (lo, path);
	}
    }
}
