/* This file is part of GDB.

GDB 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 1, or (at your option)
any later version.

GDB 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 GDB; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

#import "gdb.h"
/* #import "symtab.h" gdb.h imports this already: MVS */
#import "objfiles.h"
#include <errno.h>

#import <mach/cthreads.h>
//#import <machine/reg.h>
#import <mach-o/nlist.h>

#import <mach-o/loader.h>

#import <objc/maptable.h>
#import <objc/hashtable.h>

#ifdef USE_HAPTABLE
#import "haptable.h"
#endif /* USE_HAPTABLE */

task_t gdb_task;
cthread_t gdb_thread;

#ifndef USE_HAPTABLE
static NXZone *stringHashZone;
static NXHashTable *stringHash;
#else
static NXZone *stringHapZone;
static NXHapTable *stringHap;
#endif

static char *lastSavedString = NULL;
static int debugPrint = 0;

char *find_opath_alias();


#define N_TEXT 4
#define N_DATA 6
#define N_BSS 8

int any_cplusplus = 0;
int force_cplusplus = 0;

char *getShlibName(libCmd, originalName)
     struct fvmlib_command *libCmd;
     char **originalName;
{
  char *name, *path, *subpath, *alias;
  int n;
  name = (void *)libCmd + libCmd->fvmlib.name.offset;
  if (path = (char *)find_opath_alias(name, &n)) {
    subpath = name + n;
    alias = xmalloc(strlen(path) + strlen(subpath) + 1);
    strcpy(alias, path);
    strcat(alias, subpath);
    if (originalName)
      *originalName = name;
    return alias;
  } else {
    if (originalName)
      *originalName = NULL;
    return name;
  }
}

BOOL static isExecutable(header)
     struct mach_header *header;
{
  return (header->filetype == MH_EXECUTE) || 
    (header->filetype == MH_OBJECT)       || 
    (header->filetype == MH_PRELOAD);
}

void symbolFileAddShlibs(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 = getShlibName((struct fvmlib_command *)loadCmd,
			    &originalName);
	shlibManager = [SegmentManager newShlib: name cpuType: cpuType()];
	if (shlibManager)
	  symfile_add(shlibManager, 0, 0, 0, 0, 0);
	else
	  error("Could not open `%s' as a shlib.", name);
      }
    }
  }
}

void symbol_file_add_shlibs(manager)
     SegmentManager *manager;
{
  struct mach_header *header = [manager getMachHeader];
    int nCmds;
  struct load_command *loadCmd;
  char *name;
  if (isExecutable(header)) {
    for (nCmds = header->ncmds,
	 loadCmd = (struct load_command *)(header + 1);
	 nCmds;
	 nCmds--, ((void *)loadCmd) += loadCmd->cmdsize) {
      if (loadCmd->cmd == LC_LOADFVMLIB) {
	name = getShlibName((struct fvmlib_command *)loadCmd, NULL);
	symbol_file_add(name, 1, 0, 0, 0, 0);
      }
    }
  }
}

typedef struct _MiscPSymtab {
  unsigned int val;
  struct partial_symtab *pst;
} MiscPSymtab;

static int miscIndex = 0;
static int numAlloced = 0;
#define INITIAL_ALLOC 500;
static MiscPSymtab *miscPSymtabs = NULL;

void
record_minimal_symbol_psymtab(val, pst)
     unsigned int val;
     struct partial_symtab *pst;
{
  if (val == 0 || isShlibJump(val))
    return;

  if (!numAlloced) {
    numAlloced = INITIAL_ALLOC;
    miscPSymtabs = xmalloc(numAlloced * sizeof(*miscPSymtabs));
  } else if (miscIndex == numAlloced) {
    numAlloced *= 2;
    miscPSymtabs = xrealloc(miscPSymtabs, numAlloced * sizeof(*miscPSymtabs));
  }
  miscPSymtabs[miscIndex].val = val;
  miscPSymtabs[miscIndex].pst = pst;
  miscIndex++;
}

int cmpMiscPSymtabs(arg1, arg2)
     const void *arg1, *arg2;
{
  const MiscPSymtab *mpst1 = arg1, *mpst2 = arg2;
  if (mpst1->val < mpst2->val)
    return -1;
  else if (mpst1->val == mpst2->val)
    return 0;
  else
    return 1;
}

void
combine_minimal_symbols_psymtabs(objfile)
     struct objfile *objfile;
{
  int count;
  MiscPSymtab *miscPSymtab;
  struct minimal_symbol *minSym, *lastMinSym, *endOfMinSyms;

  if (miscIndex) {
    qsort(miscPSymtabs, miscIndex, sizeof(*miscPSymtabs), &cmpMiscPSymtabs);
    minSym = objfile->msymbols;
    endOfMinSyms = minSym + objfile->minimal_symbol_count;
    for (count = miscIndex, miscPSymtab = miscPSymtabs;
	 count;
	 count--, miscPSymtab++) {
      for (lastMinSym = minSym;
	   (minSym < endOfMinSyms)
	   && (SYMBOL_VALUE_ADDRESS(minSym) != miscPSymtab->val);
	   minSym++)
	;
      if (minSym < endOfMinSyms) {
	MSYMBOL_INFO(minSym) = (char *)miscPSymtab->pst;
	minSym++;
      } else {
	minSym = lastMinSym;
	printf("Value with psymtab but no minimal symbol: %d in %s.\n",
	       miscPSymtab->val, miscPSymtab->pst->filename);
      }
    }
    free (miscPSymtabs);
    miscPSymtabs = NULL;
    miscIndex = 0;
    numAlloced = 0;
  }
}

#if 0
struct symtab *
symtab_for_misc_function(ind)
     int ind;
{
  struct misc_function *misc_function;
  struct partial_symtab *psymtab;
  misc_function = misc_function_vector + ind;
  psymtab = misc_function->misc_info;
  if (psymtab)
    return PSYMTAB_TO_SYMTAB(psymtab);
  else
    return NULL;
}

#endif /* 0 */

static NXMapTable *prologueMap = NULL;

typedef union _ProEpInfo {
  unsigned int info;
  struct _Offsets {
    unsigned int prologue : 10;
    unsigned int epilogue : 22;
  } offsets;
} ProEpInfo;

void
mark_prologue(function, start, end)
     CORE_ADDR function, start, end;
{
  ProEpInfo proEpInfo;

  proEpInfo.offsets.prologue = start - function;
  proEpInfo.offsets.epilogue =  end - function;

  NXMapInsert(prologueMap, (void *)function, (void *)proEpInfo.info);
}

CORE_ADDR
skip_prologue_by_symbol(function)
{
  CORE_ADDR prologue;
  ProEpInfo proEpInfo;

  proEpInfo.info = (unsigned)NXMapGet(prologueMap, (void *)function);
  if (proEpInfo.info)
    return function + proEpInfo.offsets.prologue;
  else
    return 0;
}

CORE_ADDR
skip_epilogue(function)
{
  ProEpInfo proEpInfo;

  proEpInfo.info = (unsigned)NXMapGet(prologueMap, (void *)function);
  if (proEpInfo.info)
    return (function + proEpInfo.offsets.epilogue);
  else
    return 0;
}

#ifdef NeXT	/* temp kludge: name conflict with enum in defs.h */
#define end_command End_Command
#endif	/* NeXT */

static void
end_command(args, from_tty)
     char *args;
     int from_tty;
{
  ProEpInfo proEpInfo;
  struct symtab_and_line sal;
  CORE_ADDR function;
  struct block *block;
    
  if (args)
    error ("The \"end\" command does not take any arguments.");
  if (!target_has_execution)
    error ("The program is not running.");
  if (selected_frame == NULL)
    error ("No selected frame.");

  block = block_for_pc(stop_pc);
  if (block != 0)	/* MVS: don't know why it fails, but it does... */
    function = BLOCK_START(block);
  if (block == 0 || function == 0)
    error("end: internal error, can't fulfill request");
  proEpInfo.info = (unsigned)NXMapGet(prologueMap, (void *)function);
  if (proEpInfo.info) {
    struct breakpoint *breakpoint;
    struct cleanup *old_chain;
    if (stop_pc == function + proEpInfo.offsets.epilogue)
      error("Already at end of function.");
    if (stop_pc > function + proEpInfo.offsets.epilogue)
      error("Already past end of function.");
    sal = find_pc_line(function + proEpInfo.offsets.epilogue, 0);
    breakpoint = set_momentary_breakpoint (sal, selected_frame, bp_until);
    old_chain = make_cleanup(delete_breakpoint, breakpoint);
    printf_filtered ("Run till end of ");
    print_stack_frame (selected_frame, selected_frame_level, 0);
    clear_proceed_status ();
    proceed (-1, -1, 0);
    do_cleanups(old_chain);
  } else
    error("Couldn't find end of function.");
}

void
lock_execute_command(lock)
{
  static mutex_t mutex = NULL;
  static cthread_t lockedThread = 0;
  if (!mutex)
    mutex = mutex_alloc();
  if (lock && (lockedThread != cthread_self())) {
    mutex_lock(mutex);
    lockedThread = cthread_self();
  } else {
    lockedThread = 0;
    mutex_unlock(mutex);
  }
}

void
execute_command_with_lock(args, from_tty)
     char *args;
     int from_tty;
{
  struct cleanup *cleanups;
  lock_execute_command(YES);
  cleanups = make_cleanup(lock_execute_command, NO);
  execute_command(args, from_tty);
  do_cleanups(cleanups);
}
    
struct opaths {
  char *old, *new;
  struct opaths *next;
} *opaths;

void
opath_alias_command(paths, from_tty)
     char *paths;
     int from_tty;
{
  char *eqsign;
  struct opaths *opath;
  char *old, *new;

  if (!paths) {
    printf("Object File Path Aliases:\n");
    for(opath = opaths; opath; opath = opath->next) 
      printf("%s -> %s\n", opath->old, opath->new);
    return;
  }
  
  /* Should be of the form oldpath=newpath. */
  if (!(eqsign = index(paths, '='))) {
    fprintf(
      stderr,
      "Bad argument to -opath: %s, should be in format oldpath = newpath.\n",
      paths
    );
    return;
  }
  
  /* Skip leading whitespace */
  while(*paths == ' ' || *paths == '\t') paths++;
  old = paths;
  while (*paths != ' ' && *paths != '\t' && *paths != '=') paths++;
  *paths = 0;
  paths = eqsign+1;
  while(*paths == ' ' || *paths == '\t') paths++;
  new = paths;
  while (*paths != ' ' && *paths != '\t' && *paths != '\0') paths++;
  *paths = 0;

  opath = (struct opaths *)malloc(sizeof(struct opaths));
  opath->new = (char *)malloc(strlen(new) + 1);
  strcpy(opath->new, new);
  opath->old = (char *)malloc(strlen(old) + 1);
  strcpy(opath->old, old);
  opath->next = opaths;
  opaths = opath;
}

static void
opath_unalias_command (path, from_tty)
     char *path;
     int from_tty;
{
  struct opaths *opath;

  if (!path)
    error_no_arg ("old pathname");
  /* Skip leading whitespace */
  while(*path == ' ' || *path == '\t') path++;

  if (opaths) {
    if (!strcmp(opaths->old, path)) {
      opaths = opaths->next;
      return;
    } else {
      for(opath = opaths; opath->next; opath = opath->next) {
	if (!strcmp(opath->next->old, path)) {
	  struct opaths *o;
	  free(opath->next->old);
	  free(opath->next->new);
	  o = opath->next->next;
	  free(opath->next);
	  opath->next = o;
	  return;
	}
      }
    }
  }
  fprintf(stderr, "Can't find %s in opath alias list.\n", path);
}

char *
find_opath_alias(string, ncharsubst)
     char *string;
     int *ncharsubst;
{
  struct opaths *opath;

  /* Do O-path aliasing. */
  for(opath = opaths; opath; opath = opath->next) {
    if (!strncmp(opath->old, string, strlen(opath->old))) {
      char *newname, *subpath;
      
      *ncharsubst = strlen(opath->old);
      return(opath->new);
    }
  }
  return 0;
}

static char *getNextString(len)
{
  static char *topOfStrings = NULL;
  static int stringBytesLeft = 0;
  char *nextString;

  if (len > stringBytesLeft) {
    stringBytesLeft = vm_page_size;
    vm_allocate(task_self(), (vm_address_t *) &topOfStrings, 
		stringBytesLeft, 1);
  }
  nextString = topOfStrings;
  stringBytesLeft -= len;
  topOfStrings += len;
  return nextString;
}

char *
stringSave(string)
     const char *string;
{
  if (!string)
    return NULL;
#ifndef USE_HAPTABLE
  if ((string != lastSavedString)
      && !(lastSavedString = NXHashGet(stringHash, string))) 
#else
  if ((string != lastSavedString)
      && !(lastSavedString = NXHapGet(stringHap, string))) 
#endif
    {
      int n = strlen(string);
      lastSavedString = getNextString(n + 1);
      bcopy(string, lastSavedString, n + 1);
#ifndef USE_HAPTABLE
      NXHashInsert(stringHash, lastSavedString);
#else
      NXHapInsert(stringHap, lastSavedString);
#endif
    }
  return lastSavedString;
}

char *
stringSaveNoCopy(string)
     char *string;
{
  if (!string)
    return NULL;

#ifndef USE_HAPTABLE
  lastSavedString = NXHashInsert(stringHash, string);
#else
  lastSavedString =  NXHapInsert(stringHap, string);
#endif
  return lastSavedString;
}

char *
stringSave2(s1, s2)
     char *s1, *s2;
{
  char s3[strlen(s1) + strlen(s2) + 1];	/* CAUTION: gcc extension! */

  strcpy(s3, s1);
  strcat(s3, s2);
  return stringSave(s3);
}

char *
stringSaveN(string, n)
     const char *string;
     int n;
{
  if (!string)
    return NULL;

  if (string != lastSavedString) {
    char newString[n + 1];
    bcopy(string, newString, n);
    newString[n] = '\0';
#ifndef USE_HAPTABLE
    if (!(lastSavedString = NXHashGet(stringHash, newString)))
#else
    if (!(lastSavedString = NXHapGet(stringHap, newString)))
#endif
      {
	lastSavedString = getNextString(n + 1);
	bcopy(string, lastSavedString, n);
	lastSavedString[n] = '\0';
#ifndef USE_HAPTABLE
	NXHashInsert(stringHash, lastSavedString);
#else
	NXHapInsert(stringHap, lastSavedString);
#endif
      }
  }
  return lastSavedString;
}

static int pstrcmp(arg1, arg2)
     const void *arg1;
     const void *arg2;
{
  return strcmp(*(char **)arg1, *(char **)arg2); 
}

static void
dumpStrings(args, from_tty)
     char *args;
     int from_tty;
{
  FILE *stringsFile = fopen(args, "w+");
#ifndef USE_HAPTABLE
  int count = NXCountHashTable(stringHash), i = 0;
  NXHashState state = NXInitHashState(stringHash);
#else
  int count = NXCountHapTable(stringHap), i = 0;
  NXHapState state = NXInitHapState(stringHap);
#endif
  char *strings[sizeof(char *) * count], *string;

#ifndef USE_HAPTABLE
  while (NXNextHashState(stringHash, &state, (void **)&string))
#else
  while (NXNextHapState(stringHap, &state, (void **)&string))
#endif 0
    strings[i++] = string;
  qsort(strings, count, sizeof(*strings), pstrcmp);
  for (i = 0; i < count; i++) {
    fprintf(stringsFile, "%s\n", strings[i]);
  }
  fclose(stringsFile);
}

static void
dumpStringSize(args, from_tty)
     char *args;
     int from_tty;
{
#ifndef USE_HAPTABLE
  NXHashState state = NXInitHashState(stringHash);
#else
  NXHapState state = NXInitHapState(stringHap);
#endif
  char *string;
  NXHashState pageState;
  NXHashTable *pageHash = NXCreateHashTable(NXPtrPrototype, 0, NULL);
  FILE *stringsFile = (args && *args) ? fopen(args, "w+") : stdout;
  void *page, *startPage, *endPage;
  int len, stringBytes = 0, stringPages;
#ifndef USE_HAPTABLE
  while (NXNextHashState(stringHash, &state, (void **)&string))
#else
  while (NXNextHapState(stringHap, &state, (void **)&string))
#endif
    {
      len = strlen(string) + 1;
      startPage = (void *)trunc_page(string);
      endPage = (void *)trunc_page(string + len);
      NXHashInsert(pageHash, startPage);
      if (endPage != startPage)
	NXHashInsert(pageHash, startPage);
      stringBytes += len;
    }
  stringPages = NXCountHashTable(pageHash);
  fprintf(stringsFile,
	  "%d strings.  Total bytes: %d. Total pages: %d (%d bytes)\n", 
#ifndef USE_HAPTABLE
	  NXCountHashTable(stringHash), stringBytes, 
#else
	  NXCountHapTable(stringHap), stringBytes, 
#endif USE_HAPTABLE
	  stringPages, stringPages * vm_page_size);
  pageState = NXInitHashState(pageHash);
  while (NXNextHashState(pageHash, &pageState, &page))
    fprintf(stringsFile, "%p\n", page);
  if (args && *args)
    fclose(stringsFile);
  NXFreeHashTable(pageHash);
}

void
_initialize_string()
{
  static BOOL initialized = NO;
  if (!initialized) {
#ifndef USE_HAPTABLE
    stringHashZone = NXCreateZone(vm_page_size, vm_page_size, YES);
    NXNameZone(stringHashZone, "StringHashZone");
    stringHash = NXCreateHashTableFromZone(NXStrPrototype, 0, NULL, 
					   stringHashZone);
#else
    stringHapZone = NXCreateZone(vm_page_size, vm_page_size, YES);
    NXNameZone(stringHapZone, "StringHapZone");
    stringHap = NXCreateHapTableFromZone(NXStrValueHapPrototype, 0, 
					 stringHapZone);
#endif 
    initialized = YES;
  }
}
	
void
memory_error (status, memaddr)
     int status;
     CORE_ADDR memaddr;
{

  if (status == EIO)
    {
      /* Actually, address between memaddr and memaddr + len
	 was out of bounds. */
      error ("Cannot access memory: address 0x%x out of bounds.", memaddr);
    }
  else
    {
      if (status >= sys_nerr || status < 0)
	error ("Error accessing memory address 0x%x: unknown error (%d).",
	       memaddr, status);
      else
	error ("Error accessing memory address 0x%x: %s.",
	       memaddr, sys_errlist[status]);
    }
}

/* Same as target_read_memory, but report an error if can't read.  */
void
read_memory (memaddr, myaddr, len)
     CORE_ADDR memaddr;
     char *myaddr;
     int len;
{
  int status;
  status = target_read_memory (memaddr, myaddr, len);
  if (status != 0)
    memory_error (status, memaddr);
}

/* Same as target_write_memory, but report an error if can't write.  */
void
write_memory (memaddr, myaddr, len)
     CORE_ADDR memaddr;
     char *myaddr;
     int len;
{
  int status;

  status = target_write_memory (memaddr, myaddr, len);
  if (status != 0)
    memory_error (status, memaddr);
}

/* Read an integer from debugged memory, given address and number of bytes.  */

LONGEST
read_memory_integer (memaddr, len)
     CORE_ADDR memaddr;
     int len;
{
  char buf[sizeof (LONGEST)];

  read_memory (memaddr, buf, len);
  return extract_signed_integer (buf, len);
}

unsigned LONGEST
read_memory_unsigned_integer (memaddr, len)
     CORE_ADDR memaddr;
     int len;
{
  char buf[sizeof (unsigned LONGEST)];

  read_memory (memaddr, buf, len);
  return extract_unsigned_integer (buf, len);
}

/* mread -- read memory (unsigned) and apply a bitmask */

unsigned long
mread(addr, len, mask)
     CORE_ADDR addr;
     unsigned long len, mask;
{
  long ret = read_memory_unsigned_integer (addr, len);

  if (mask)
    ret &= mask;

  return ret;
}

int debugPrintf(const char *fmt, ...)
{
  va_list ap;
  if (debugPrint) {
    int ret;
    va_start(ap, fmt);
    ret = vprintf(fmt, ap);
    va_end(ap);
    return ret;
  } else
    return 0;
}

void validateRegister(regno)
{
  int i;
  extern char register_valid[];
  if (regno == -1) {
    for (i = 0; i < NUM_REGS; i++) {
      if (!register_valid[i])
	fetch_inferior_registers(i);
    }
  } else if (!register_valid[regno])
    fetch_inferior_registers(regno);
}
    
static int profInited = 0;

static void startProfiling(args, from_tty)
     char *args;
     int from_tty;
{
  if (profInited)
    moncontrol(1);
  else {
    moninit();
    profInited = 1;
  }
}

static void stopProfiling(args, from_tty)
     char *args;
     int from_tty;
{
  if (args && *args)
    monoutput(args);
  else
    monoutput("gmon.out");
  moncontrol(0);
}

/*
 * symbols_changed_hook:
 * update anything needful when new symbols have been 
 * loaded, eg. by the addition of a new shared library.
 * I'm sure there's a better, more GNU-ish way to do this.   MVS 
 */
void symbols_changed_hook (struct objfile * obj)
{ 
  find_objc_msgsend ();
}

void 
_initialize_next()
{
  _initialize_maint_cmds();
  prologueMap = NXCreateMapTable(NXPtrValueMapPrototype, 0);
  add_com("start-profile", class_obscure, startProfiling,
          "Begin profiling.");

  add_com("stop-profile", class_obscure, stopProfiling,
          "End profiling and dump gmon info into [FILE].");

  add_com("end", class_breakpoint, end_command,
	  "Execute until end of current function.");
  add_com ("opath-alias", class_obscure, opath_alias_command,
	   "Add path alias for lazy symbol lookup from .o file. OLDPATH=NEWPATH");
  add_com_alias ("opath", "opath-alias", class_obscure, 1);
  add_com ("opath-unalias", class_obscure, opath_unalias_command,
	   "Delete path alias for symbol lookup from .o file. OLDPATH");
  add_cmd ("strings", class_maintenance, dumpStrings,
	   "Print dump of all strings to OUTFILE.",
	   &maintenanceprintlist);
  add_cmd ("stringsize", class_maintenance, dumpStringSize,
	   "Print size of strings.",
	   &maintenanceprintlist);
  add_show_from_set(
		    add_set_cmd("debug-print", class_obscure, var_boolean, 
				(char *)&debugPrint,
				"Set if printing debugger debug statements.",
				&setlist),
		    &showlist);		
  add_show_from_set(
		    add_set_cmd("force_cplusplus", class_obscure, var_boolean, 
				(char *)&force_cplusplus,
				"Set if you know better than the debugger about C++",
				&setlist),
		    &showlist);		
  gdb_task = task_self();
  gdb_thread = cthread_self();
}
