#import "gdb.h"
#import <fcntl.h>
#import "loadSymbols.h"
#import "objfiles.h"
#import <sys/stat.h>
#import "TellDisplayer.h"

typedef struct _ManagerChain {
  SegmentManager *manager;
  CORE_ADDR jmpTableStart;
  CORE_ADDR jmpTableEnd;
  CORE_ADDR dyldTableStart;
  CORE_ADDR dyldTableEnd;
  struct _ManagerChain *next;
} ManagerChain;

static ManagerChain *execManagers = NULL;
SegmentManager *execManager = NULL;
extern struct target_ops exec_ops;

extern void
addLoadedObjectModule (struct objfile *obj);
extern struct objfile * symfile_add ();

int
isShlibJump(address)
     CORE_ADDR address;
{
  ManagerChain *mChain;
  for (mChain = execManagers; mChain; mChain = mChain->next) {
    if ((mChain->jmpTableStart <= address) && 
	(address < mChain->jmpTableEnd))
      return YES;
  }
  return NO;
}

int
isDylibJump(address)
     CORE_ADDR address;
{
  ManagerChain *mChain;
  for (mChain = execManagers; mChain; mChain = mChain->next) {
    if ((mChain->dyldTableStart <= address) && 
	(address < mChain->dyldTableEnd))
      return YES;
  }
  return NO;
}

static void initExecManager(mChain)
     ManagerChain *mChain;
{
  const struct section *firstSection;
  SegmentManager       *manager = mChain->manager;
  const struct section *sec;

  /* first see if it is a static shared library */
  if ([manager isShlib] && (firstSection = [manager firstSection])) {
    mChain->jmpTableStart = firstSection->addr;
    mChain->jmpTableEnd = jmpTableEnd(((void *)[manager getMachHeader])
				      + firstSection->offset,
				      firstSection);
  } else {		/* no, not a static shlib */
    mChain->jmpTableStart = 0xFFFFFFFF;
    mChain->jmpTableEnd   = 0x0;

    /* now see if it is a dynamic shared library */
    if (!(sec = [manager getSeg: "__TEXT" sect: "__picsymbol_stub"]))
      sec =     [manager getSeg: "__TEXT" sect: "__symbol_stub"];
    if (sec) {	/* found (pic)symbol_stub */
      mChain->dyldTableStart = sec->addr + [manager getSlide];
      mChain->dyldTableEnd   = sec->size + mChain->dyldTableStart;
    } else {	/* no, not a dynamic shlib */
      mChain->dyldTableStart = 0xFFFFFFFF;
      mChain->dyldTableEnd   = 0x0;
    }
  }
  readObjC(manager);
}

void addExecManager(manager)
     SegmentManager *manager;
{
  ManagerChain *mChain;

  if (execManagers) {
    for (mChain = execManagers; mChain->next; mChain = mChain->next);
    mChain->next = malloc(sizeof(*mChain));
    mChain = mChain->next;
  } else {
    execManagers = mChain = malloc(sizeof(*mChain));
    execManager = manager;
  }
  mChain->manager = manager;
  mChain->next = NULL;
  initExecManager(mChain);
}

void invalidateExecManager(manager)
     SegmentManager *manager;
{
  removeObjC(manager);
  clear_value_history ();
  clear_displays ();
  clear_internalvars ();
  set_default_breakpoint (0, 0, 0, 0);
  current_source_symtab = 0;
  innermost_block = 0;
}

void removeExecManager(manager)
     SegmentManager *manager;
{
  invalidateExecManager(manager);
  if (execManagers) {
    ManagerChain *mChain;
    for (mChain = execManagers;
	 mChain && (mChain->next->manager != manager);
	 mChain = mChain->next);
    if (mChain) {
      ManagerChain *oldChain = mChain->next;
      mChain->next = oldChain->next;
      free(oldChain);
    }
  }
}

void execFileAddShlibs(manager)
     SegmentManager *manager;
{
  struct mach_header *header = [manager getMachHeader];
  int nCmds;
  struct load_command *loadCmd;
  char *name, *originalName;
  SegmentManager *shlibManager;
  if ([manager isExecutable]) {
    for (nCmds = header->ncmds, loadCmd = (struct load_command *)(header + 1);
	 nCmds; nCmds--, ((void *)loadCmd) += loadCmd->cmdsize) {
      if (loadCmd->cmd == LC_LOADFVMLIB) {
	name = (char *) getShlibName((struct fvmlib_command *)loadCmd,
				     &originalName);
	if (originalName)
	  printf("Aliasing %s to %s...\n", originalName, name);
	shlibManager = [SegmentManager newShlib: name cpuType: cpuType()];
	if (shlibManager)
	  addExecManager(shlibManager);
	else
	  error("Could not open `%s' as a shlib.", name);
      }
    }
  }
}

SegmentManager *execFileAdd(name, desc)
     char *name;
     int desc;
{
  SegmentManager *manager = [SegmentManager newExecutable: name
					       skipShlibs: YES
						 withDesc: desc
						  cpuType: cpuType()];
  ManagerChain *mChain;

  if (!manager)
    error("Could not open `%s' as an executable file.", name);
  addExecManager(manager);    
  execFileAddShlibs(manager);
  validate_files ();
  return manager;
}

/*  Process the first arg in ARGS as the new exec file.

    Note that we have to explicitly ignore additional args, since we can
    be called from file_command(), which also calls symbol_file_command()
    which can take multiple args. */

void
exec_file_command(args, from_tty)
     char *args;
     int from_tty;
{
  char **argv;
  char *filename;

  target_preopen (from_tty);

  /* Remove any previous exec file.  */
  unpush_target (&exec_ops);
  /* Now open and digest the file the user requested, if any.  */

  if (args) {
    char *scratch_pathname;
    int scratch_chan;

    /* Scan through the args and pick up the first non option arg
       as the filename. */

    if ((argv = buildargv(args)) == NULL) {
      nomem(0);
    }
    make_cleanup(freeargv, (char *)argv);

    for (; (*argv != NULL) && (**argv == '-'); argv++) {;}
    if (*argv == NULL) {
      error ("no exec file name was specified");
    }

    filename = tilde_expand(*argv);
    make_cleanup(free, filename);

    scratch_chan = openp(getenv("PATH"), 1, filename, O_RDONLY, 0,
			 &scratch_pathname);
    if (scratch_chan < 0)
      perror_with_name(filename);

    execFileAdd(scratch_pathname, scratch_chan);

    push_target (&exec_ops);
    tell_displayer_state_changed(DBG_STATE_INFERIOR_LOADED);
  }
  else if (from_tty)
    printf ("No exec file now.\n");
}

static void
exec_close(quitting)
{
  if (execManager)
    execManager = [execManager free];
}

static int
exec_xfer_memory (memaddr, myaddr, len, write, target)
     CORE_ADDR memaddr;
     char *myaddr;
     int len, write;
     struct target_ops *target;
{
  ManagerChain *mChain;
  int bytesRead = 0;

  if (write)
    error("Can't write to executable files.");
  for (mChain = execManagers; mChain && !bytesRead; mChain = mChain->next) {
    bytesRead = [mChain->manager getDataAt: (void *)memaddr
	       for: len into: myaddr];
  }
  return bytesRead;
}

static void
NeXT_add_file(args, from_tty)
     char *args;
     int from_tty;
{
  char *name;
  CORE_ADDR text_addr = 0;

  dont_repeat ();

  if (args == 0)
    error ("add-symbol-file takes a file name and an optional address");

  args = tilde_expand (args);
  make_cleanup (free, args);

  for(; *args == ' '; args++ );
  name = args;

  for(; *args && *args != ' ' ; args++ );

  if (*args) {
    LoadInfo loadInfo;
    char *theNames[] = {name, NULL};

    *args++ = '\0';
    loadInfo.headerAddr
      = (struct mach_header *)parse_and_eval_address(args);
    loadSymbolsForInfo(&loadInfo, theNames);
  } else {
    SegmentManager *manager = execFileAdd(name, -1);
    symfile_add(manager, 0, 0, 0, 0, 0);
  }
  tell_displayer_state_changed(DBG_STATE_INFERIOR_LOADED);
}

static void
NeXT_add_module(args, from_tty)
     char *args;
     int from_tty;
{
  CORE_ADDR header_addr = 0, vm_slide = 0;
  SegmentManager *manager = 0;
  char *next_arg;
  struct mach_header* header;

  dont_repeat ();

  if (args == 0)
    {
      error ("add-module takes an address and an optional vmslide");
      return;
    }

  args = tilde_expand (args);
  make_cleanup (free, args);

  while (*args == ' ')
    args++;
  for (next_arg = args; *next_arg && *next_arg != ' '; next_arg++)
    ;
  if (*next_arg == ' ')
    *next_arg++ = 0;
  else
    *next_arg = 0;
  header_addr = (CORE_ADDR) parse_and_eval_address (args);

  header = WARP ((struct mach_header*)header_addr);
  if (header == 0)
    {
      error ("address %p not a valid address", header_addr);
      return;
    }
  if (header->magic != MH_MAGIC && header->magic != MH_CIGAM)
    {
      error ("address %p not a valid header", header_addr);
      return;
    }

  if (*next_arg)
    vm_slide = (CORE_ADDR) parse_and_eval_address (next_arg);
  else
    {
      if (header->filetype == MH_DYLIB || header->filetype == MH_DYLINKER)
	vm_slide = header_addr;
      else 
	vm_slide = 0;
    }

  if (from_tty)
    {
      switch (header->filetype)
	{
	case MH_DYLIB: 
	  printf ("Dynamic Shared Library"); break;
	case MH_DYLINKER: 
	  printf ("Dynamic Linkeditor");
	  break;
	case MH_FVMLIB:
	  printf ("Fixed Virtual Memory Shared Library"); 
	  break;
	case MH_PRELOAD: 
	  printf ("Preloaded Executable"); 
	  break;
	case MH_EXECUTE:
	  printf ("Executable");
	  break;
	default:
	  printf ("Object Module");
	}
      printf (" at 0x%x offset 0x%x\n", header_addr, vm_slide);
    }

  if (header->filetype == MH_DYLINKER)
    {
      struct stat buf;
      static vm_offset_t pos = 0;
      
      if (pos == 0)
	{
	  int fd, i;
	  struct load_command *cmd;
	  char *name = "/usr/lib/dyld";
	  
	  for (cmd = (struct load_command *)(header + 1), i = 0;
	       i < header->ncmds; ((void *)cmd) += cmd->cmdsize, i++) {
	    if (cmd->cmd == LC_ID_DYLINKER) {
	      struct dylinker_command* dylCmd = (struct dylinker_command*) cmd;
	      name = (char*) cmd + 
		((struct dylinker_command*) cmd)->name.offset;
	      break;
	    }
	  }

	  fd = open ("/usr/lib/dyld", O_RDONLY);
	  fstat (fd, &buf);
	  map_fd (fd, 0, &pos, 1, buf.st_size);
	}
      manager = [SegmentManager newHeader: (struct mach_header*)pos
				withSlide: vm_slide];
      addExecManager(manager);
      addLoadedObjectModule (symfile_add(manager, 0, vm_slide, 0, 0, 0));
    }
  else
    {
      header 
	= [regionManager copyLoadedImage:(struct mach_header*) header_addr 
	 withSlide:vm_slide];
      manager = [SegmentManager newHeader:header withSlide:0];
      addExecManager(manager);
      addLoadedObjectModule (symfile_add (manager, 0, 0, 0, 0, 0));
    }
}


/* Return the name of the executable file as a string.
   ERR nonzero means get error if there is none specified;
   otherwise return 0 in that case.  */

char *
get_exec_file(err)
{
  if (execManager)
    return [execManager executableName];
  else if (err)
    error("No executable file specified.\n"
	  "Use the \"file\" or \"exec-file\" command.");
  else
    return NULL;
}

void
file_command (arg, from_tty)
     char *arg;
     int from_tty;
{
  /* FIXME, if we lose on reading the symbol file, we should revert
     the exec file, but that's rough.  */
  exec_file_command (arg, from_tty);
  symbol_file_command (arg, from_tty);
}

void reread_exec()
{
  struct objfile *obj, *foundObj = NULL;
  struct stat buf;
  int res;
  ManagerChain *mChain;
  if (execManagers) {
    for (mChain = execManagers; mChain; mChain = mChain->next) {
      res = stat([mChain->manager executableName], &buf);
      if (res == 0) {
	if (buf.st_mtime != [mChain->manager mtime]) {
	  printf("'%s' has changed, rereading symbols.\n", 
		 [mChain->manager executableName]);
	  invalidateExecManager(mChain->manager);
	  [mChain->manager invalidate];
	  [mChain->manager getImages];
	  [mChain->manager readInAllRelocs];		
	  initExecManager(mChain);
	  for (obj = object_files; obj && !foundObj; obj = obj->next) {
	    if (obj->manager == mChain->manager)
	      foundObj = obj;
	  }
	  if (foundObj)
	    free_objfile(foundObj);
	  symfile_add(mChain->manager, 0, 0, 0, 0, 0);
	}
      }
    }
  }
}

void check_executables(args, from_tty)
     char *args;
     int from_tty;
{
  reread_exec();
}

void no_file_command(args, from_tty)
     char *args;
     int from_tty;
{ /* watch out -- ANSI string merging */
  printf("Commands symbol-file, exec-file, and file are no longer supported.\n"
	 "Use the update-files command to force rereading of any files "
	 "which have changed.\n");
}

struct target_ops exec_ops = {
    "exec",			/* to_shortname */
    "Local exec file",		/* to_longname */
    "Use an executable file as a target.\n"
    "Specify the filename of the executable file.",	/* to_doc */
    exec_file_command,		/* to_open */
    exec_close,			/* to_close */
    find_default_attach,	/* to_attach */
    0, 				/* to_detach */
    0,				/* to_resume */
    0,				/* to_wait */
    0,				/* to_fetch_registers */
    0,				/* to_store_registers */
    0,				/* to_prepare_to_store */
    exec_xfer_memory,		/* to_xfer_memory */
    0,				/* to_files_info */
    0,				/* to_insert_breakpoint */
    0,				/* to_remove_breakpoint */
    0,				/* to_terminal_init */
    0, 				/* to_terminal_inferior */
    0,				/* to_terminal_ours_for_output */
    0,				/* to_terminal_ours */
    0,				/* to_terminal_info */
    0,				/* to_kill */
    0,				/* to_load */
    0,				/* to_lookup_symbol */
    find_default_create_inferior,	/* to_create_inferior */
    0,				/* to_mourn_inferior */
    0,				/* to_can_run */
    0, 				/* to_notice_signals */
    0,  			/* to_stop */
    file_stratum,		/* to_stratum */
    0,				/* DONT_USE (formerly to_next) */
    0,				/* to_has_all_memory */
    1,				/* to_has_memory */
    0,				/* to_has_stack */
    0,				/* to_has_registers */
    0,				/* to_has_execution */
    0,				/* sections */
    0,				/* sections_end */
    OPS_MAGIC			/* to_magic */
};

void
_initialize_nextexec ()
{
  add_com ("add-file", class_files, NeXT_add_file,
   "Load the symbols from FILE, assuming FILE has been dynamically loaded.\n"
   "If a second argument is provided it indicates the starting address of\n"
   "the file's text.\n"
   "Otherwise the file is assumed to be the debug output file as produced by\n"
   "rld_load or objc_loadModules.\n");
  add_com ("add-module", class_files, NeXT_add_module,
	   "Register the module with mach-o header ADDR, and optional\n"
	   "vmslide SLIDE as a module in the current program.  This will\n"
	   "enable debugging of that particular module.");
  add_com ("update-files", class_files, check_executables,
  	   "Reread any symbol files which have changed.");
  add_com ("file", class_files, no_file_command,
           "No longer supported.  Use update-files command to force rereading of any changed symbol files.");
  add_com ("exec-file", class_files, no_file_command,
           "No longer supported.  Use update-files command to force rereading of any changed symbol files.");
}
