#import "gdb.h"
#import <mach-o/rld_state.h>

#import <stdlib.h>

#import <streams/streams.h>

#import "loadSymbols.h"
#import "objfiles.h"
#import "SegmentManagerThreads.h"

static int *nLoadedAddress;
static struct rld_loaded_state **loadedAddress;
CORE_ADDR changedAddress;

static LoadInfo *loaded = NULL;
static int nLoaded = 0, nAlloced = 0;
static int loadedInited = 0;
static LoadInfo *staticLoaded;
static int doLoadSymbols = 1;
static int doLoadBreakpoints = 1;
int baseFileLoaded = 0;

extern RegionManager *regionManager;

#define INVALID_ADDRESS ((CORE_ADDR)-1)


static void
removeBreakpoints(CORE_ADDR start, CORE_ADDR end)
{
    invalidate_breakpoints_between(start, end);
}

static void *
addressOfSymbol(char *name)
{
    struct minimal_symbol *minSym = lookup_minimal_symbol(name, NULL, NULL);
    if (!minSym)
	error("Couldn't find symbol for %s.", name);
    return (void *) SYMBOL_VALUE_ADDRESS(minSym);
}

const static char breakpoint_insn[] = BREAKPOINT;
static char oldBreakpointData[sizeof(breakpoint_insn) / sizeof(*breakpoint_insn)];

void
insertLoadBreakpoint()
{
    if (loadedInited) {
	[regionManager getDataAt: (void *)changedAddress
			     for: sizeof(breakpoint_insn)
			    into: (void *)oldBreakpointData];
	[regionManager putDataAt: (void *)changedAddress
			     for: sizeof(breakpoint_insn)
			    from: (void *)breakpoint_insn];
    }
}

void
removeLoadBreakpoint()
{
    if (loadedInited) {
	[regionManager putDataAt: (void *)changedAddress
			     for: sizeof(breakpoint_insn)
			    from: (void *)oldBreakpointData];
    }
}
				

static char entryBreakpointData[sizeof breakpoint_insn];
CORE_ADDR entryPointAddress = INVALID_ADDRESS;

static enum {
  STATE_INIT,
  STATE_INSERTED,
  STATE_REMOVED,
  STATE_DONE
} entryBreakpointState = STATE_INIT;

static CORE_ADDR findEntryPoint()
{
    extern struct objfile *object_files;
    struct objfile *objfile;
    SegmentManager *manager;
    ThreadInfo *threads;
    char *IReallyDontCare;
    int numthreads;
    
    for (objfile = object_files; objfile; objfile = objfile->next) {
      manager = objfile->manager;
      numthreads = [manager numThreads];
      if (numthreads) {
          if (numthreads > 1)  /* Should never happen, in theory */
            printf("Executable file %s contains more than 1 LC_UNIXTHREAD/LC_THREAD command.", objfile->name );
          threads = [manager threadInfos];
          return getThreadPCAndNameFromState(THREADINFO_PC_STATE(*threads), &IReallyDontCare);
          }
      }
    return INVALID_ADDRESS;
}


insertEntryPointBreakPoint ()
{
  if (entryBreakpointState == STATE_DONE)
    return;

  if (entryPointAddress == INVALID_ADDRESS)
    entryPointAddress = findEntryPoint();

  if (entryPointAddress == INVALID_ADDRESS)
    entryPointAddress = (CORE_ADDR)addressOfSymbol("start");

if (entryBreakpointState == STATE_INIT)
    {
      [regionManager getDataAt: (void *)entryPointAddress
		     for: sizeof(breakpoint_insn)
		     into: (void *)entryBreakpointData];
    }


  if (entryBreakpointState == STATE_INIT
      || entryBreakpointState == STATE_REMOVED)
    {
      [regionManager putDataAt: (void *)entryPointAddress
		     for: sizeof(breakpoint_insn)
		     from: (void *)breakpoint_insn];
    }

  if (entryBreakpointState == STATE_INIT)
    maybeAddDyld ();

  entryBreakpointState = STATE_INSERTED;
}

removeEntryPointBreakPoint (int done)
{
  if (entryBreakpointState == STATE_INSERTED)
    {
      [regionManager putDataAt:(void*)entryPointAddress
		     for: sizeof (breakpoint_insn)
		     from:(void*) entryBreakpointData];
      if (done)
	{
	  entryBreakpointState = STATE_DONE;
	}
      else
	entryBreakpointState = STATE_REMOVED;
    }
}

extern void restore_inferior_status_after_error (struct inferior_status*);

subscribeDyldEvents ()
{
  extern port_t gdb_dyld_port;
  extern port_set_name_t gdb_ports;
  struct inferior_status inf_status;
  struct cleanup *old_chain;
  kern_return_t ret;

  /* No need to save current selected frame info, since this routine is
   * called only when the inferior executes its first instruction, or upon
   * attaching to the inferior.  In either case, there is no previously
   * selected frame.
   */
  save_inferior_status (&inf_status, 0);
  old_chain = make_cleanup (restore_inferior_status_after_error, &inf_status);

  if (gdb_dyld_port == PORT_NULL) {
      /* MVS: dyld port needs to be opened and closed for each inferior pid */
      /*    Otherwise, detach does not work (inferior hangs on this port) */
      mach_call (
        port_allocate (task_self (), &gdb_dyld_port),
        "port_allocate", "subscribeDyldEvents");
      mach_call(
        port_set_add(task_self(), gdb_ports, gdb_dyld_port),
        "port_set_add", "subscribeDyldEvents");
  }
  
  ret = _dyld_debug_add_event_subscriber(inferior_task, 
					 2000, 2000, 
					 0, gdb_dyld_port);


  discard_cleanups (old_chain);
  restore_inferior_status(&inf_status);

  if (ret == KERN_SUCCESS)
    drainDyldEvents ();
}

void
initLoaded()
{
    if (!lookup_minimal_symbol(RLD_NLOADED_STATES, NULL, NULL)) {
//	printf("Can't find dynamic state in executable.\n");
	return;
    }
    
    nLoadedAddress = addressOfSymbol(RLD_NLOADED_STATES);
    loadedAddress = addressOfSymbol(RLD_LOADED_STATE);
    changedAddress = (CORE_ADDR)addressOfSymbol(RLD_LOADED_STATE_CHANGED);
    loadedInited = 1;

    entryBreakpointState = STATE_INIT;
    entryPointAddress = INVALID_ADDRESS;
}

static unsigned long
getLastStateHeader(unsigned long size, unsigned long header_size)
{
    staticLoaded->sizeWithSymbols = size;
    return (unsigned long)staticLoaded->headerAddr;
}

BOOL loadSymbolsForInfo(LoadInfo *loadInfo, char **theNames)
{
    unsigned long maxAddr;
    struct load_command *cmd;
    int i;
    SegmentManager *manager;
    NXStream *errorStream = NXOpenFile(fileno(stderr), NX_WRITEONLY);

    staticLoaded = loadInfo;
    if (!baseFileLoaded) {
	rld_load_basefile(NULL, get_exec_file(YES));
	baseFileLoaded = YES;
    }
    rld_address_func(getLastStateHeader);
    if (rld_load(errorStream, &loadInfo->header, theNames,
                 RLD_DEBUG_OUTPUT_FILENAME) == 1) {
	staticLoaded = NULL;
	maxAddr = (unsigned long)loadInfo->headerAddr;
	for (cmd = (struct load_command *)(loadInfo->header + 1), i = 0;
	     i < loadInfo->header->ncmds;
	     ((void *)cmd) += cmd->cmdsize, i++) {
	    if (cmd->cmd == LC_SEGMENT) {
		struct segment_command *segCmd = (struct segment_command *)cmd;
		unsigned long topAddr = segCmd->vmaddr + segCmd->vmsize;
		
		if (topAddr > maxAddr)
		    maxAddr = topAddr;
	    }
	}
	loadInfo->sizeWithoutSymbols = maxAddr
	                               - (unsigned long)loadInfo->headerAddr;
	manager = [SegmentManager newHeader: loadInfo->header
	                           withSize: loadInfo->sizeWithSymbols];
	addExecManager(manager);
	loadInfo->objfile = (void *)symfile_add(manager, 0, 0, 0, 0, 0);
	loadInfo->loadOK = YES;
	NXClose(errorStream);
	return 0;
    } else {
	NXFlush(errorStream);
	NXClose(errorStream);
	printf("gdb: error loading symbols from");
	for (i = 0; theNames[i]; i++) {
	    if (i > 0)
		printf(",");
	    printf(" %s", theNames[i]);
	}
	printf(".\n");
	loadInfo->loadOK = NO;
	return 1;
    }
}

static BOOL
loadSymbols(struct rld_loaded_state *state, LoadInfo *loadInfo)
{
    char **names, **theNames;
    int i;
    
    loadInfo->headerAddr = state->header_addr;
    loadInfo->names = state->object_filenames;
    names = [regionManager pointerFor: state->object_filenames
                             withSize: sizeof(char *)
			               * state->nobject_filenames];
			     
    theNames = alloca(sizeof(char *) * (state->nobject_filenames + 1));
    for (i = 0; i < state->nobject_filenames; i++)
	theNames[i] = WARPSTRING(names[i]);
    theNames[state->nobject_filenames] = NULL;
    return loadSymbolsForInfo(loadInfo, theNames);
}

static void
loadStatesUntil(int newNLoaded)
{
    struct rld_loaded_state *loadedState;
    struct rld_loaded_state **myLoadedAddress;
    
    if (loaded) {
	if (newNLoaded > nAlloced) {
	    nAlloced *= 2;
	    loaded = realloc(loaded, sizeof(*loaded) * nAlloced);
	}
    } else {
	nAlloced = 10;
	loaded = malloc(sizeof(*loaded) * nAlloced);
    }
    
    if ((myLoadedAddress = WARP(loadedAddress))
        && (loadedState = [regionManager pointerFor: *myLoadedAddress
				           withSize: sizeof(*loadedState)
					             * newNLoaded])) {
	for (; (nLoaded < newNLoaded); nLoaded++) {
	    loadSymbols(loadedState + nLoaded, loaded + nLoaded);
	}
	
	if (doLoadBreakpoints) {
//	    extern void resolve_unresolved_breakpoints();
	    
//	    resolve_unresolved_breakpoints();
	}
    } else
	error("Problem getting at dynamic loading information.");
}

void
addLoadedObjectModule (struct objfile *obj)
{
  LoadInfo *load;

  if (loaded) {
    if (nLoaded == nAlloced) {
      nAlloced *= 2;
      loaded = realloc(loaded, sizeof(*loaded) * nAlloced);
    }
  } else {
    nAlloced = 10;
    loaded = malloc(sizeof(*loaded) * nAlloced);
  }

  load = loaded + nLoaded;
  nLoaded += 1;
 
  load->objfile = obj;
  load->headerAddr = [(id) obj->manager getMinimumAddressForSegment:"__TEXT"];
  load->sizeWithoutSymbols 
    = [obj->manager getMaximumTextAddress] - (unsigned) load->headerAddr;
  load->loadOK = YES;
}

static void
unloadSymbols(LoadInfo *loaded)
{
    if (loaded->loadOK) {
	SegmentManager *manager;
	removeBreakpoints((CORE_ADDR)loaded->headerAddr,
	                  (CORE_ADDR)(loaded->headerAddr
			           + loaded->sizeWithoutSymbols));
	manager = loaded->objfile->manager;
	free_objfile(loaded->objfile);
        removeExecManager(manager);
	rld_unload(NULL);	
    }
}

void
unloadStatesFrom(int newNLoaded)
{
    while (nLoaded > newNLoaded) {
	nLoaded--;
	unloadSymbols(loaded + nLoaded);
    }
}

void
reallyGetLoadedSymbols()
{
    int newNLoaded;
    int *myNLoadedAddress;
    
    if (!inferior_task)
	error("Program must be running to load the dynamic symbols.");
    
    if (loadedInited) {
	myNLoadedAddress = WARP(nLoadedAddress);
	if (myNLoadedAddress) {
	    newNLoaded = *myNLoadedAddress;
	    if (newNLoaded > nLoaded) {
		loadStatesUntil(newNLoaded);
	    } else if (newNLoaded < nLoaded) {
		unloadStatesFrom(newNLoaded);
	    }
	} else {
	    error("Problem getting at dynamic loading information.");
	}
    }
}

void
getLoadedSymbols()
{
    if (doLoadSymbols)
	reallyGetLoadedSymbols();
}

void
_initialize_loadSymbols()
{
    add_com("load-symbols", no_class, &reallyGetLoadedSymbols,
            "Talk to rld and load in all symbols that rld knows about.");
    add_show_from_set(
      add_set_cmd("autoload-symbols", no_class, var_boolean, 
                (char *) &doLoadSymbols,
                "Set automatic loading of symbols of dynamic code.",
		&setlist),
      &showlist);		
    add_show_from_set(
      add_set_cmd("autoload-breakpoints", no_class, var_boolean, 
                (char *) &doLoadBreakpoints,
                "Set automatic reseting of breakpoints in dynamic code.",
		&setlist),
      &showlist);		
}
