#import "gdb.h"
#import "objfiles.h"
#import "symfile.h"
#import "TellDisplayer.h"
#import <mach-o/nlist.h>
#import <mach-o/dyld_debug.h>

#import "LazyRegionManager.h"
#import "terminal.h"
#import <mach/cthreads.h>
#import <mach/notify.h>
#import <mach/exception.h>
#import <mach/mach_exception.h>

#import <objc/objc-runtime.h>

#import <sys/ptrace.h>
#import <sys/signal.h>
#import <sys/wait.h>

struct target_task_state {
  port_t	 debug_port;
  thread_t	 debug_thread;
  unsigned int   debug_thread_resume_count;
  unsigned int   task_resume_count;
  thread_array_t threads;
  unsigned int   thread_count;
};


extern struct symbol *step_start_function;
extern int stop_print_frame;
extern int stop_soon_quietly;
extern int breakpoints_inserted;
extern int breakpoints_failed;

static cthread_t signalThread = 0;
static port_t signalPort = PORT_NULL;
static mutex_t childrenMutex = 0;
static condition_t childrenCondition;
static BOOL childrenExist;
static port_t gdb_notify_port;
static port_t inferior_exception_port, inferior_old_exception_port;
static int signalSuspendCount = 0;
static int lazyRead = 0;
static int debug_dyld = 0;
static BOOL handle_exception;
static BOOL areStepping = 0;
const static char breakpoint_insn[] = BREAKPOINT;
static char shadow[sizeof(breakpoint_insn)];

#ifdef IMPRECISE_EXECEPTIONS
static CORE_ADDR breakPC = 0;
#endif /* IMPRECISE_EXECEPTIONS */

port_set_name_t gdb_ports;	/* MVS: make external; gdb_dyld_port must be
				inserted and removed repeatedly. */
thread_t catch_thread;
int catch_exception;
int catch_code;
int catch_subcode;
task_t catch_task;
struct breakpoint *catch_data_break = NULL;
RegionManager *regionManager;
task_t inferior_task;
thread_t current_thread = 0, step_thread = 0, last_current_thread = 0;
int kernel_attached = NO;
boolean_t stopped_in_ptrace;
int read_memory_out_of_bounds_ok = 0;

port_t gdb_dyld_port = PORT_NULL;	/* MVS: null will mean needs to be initialized */

void create_inferior_for_task(task_t task, BOOL isLazy);
void create_inferior_hook(char *args, int pid);
void NeXT_mourn_inferior ();

static void setToZero(pInt)
     int *pInt;
{
  *pInt = 0;
}

/* find symbol name from address in dylib stub table */

const static char *
get_symbol_stub_function_name (pc)
     CORE_ADDR pc;
{
  char *name;
  struct objfile *obj;

  /* find the relevant objfile... */
  ALL_OBJFILES(obj) {
    if (pc >= obj->start && pc < obj->end) {	/* in this obj */
      SegmentManager *manager = (SegmentManager*) obj->manager;
      const struct section *sec = 
	[manager getSeg:"__TEXT" sect:"__picsymbol_stub"];

      /* find obj and sec that has a (pic)symbol stub */
      if (sec == 0)
	sec = [manager getSeg:"__TEXT" sect:"__symbol_stub"];

      if (sec != 0)  {
	unsigned long slide = [manager getSlide];
	CORE_ADDR start     = slide + sec->addr ;
	CORE_ADDR end       = start + sec->size;

	if (pc >= start && pc < end) {	/* is pc in one of it's stubs? */
	  struct symtab_command*   symCmd   = [manager symCmd];
	  struct dysymtab_command* dysymCmd = [manager dysymCmd];

	    if (dysymCmd && symCmd) {	  /* if we find both... */
	      const char *base      = (const char*) [manager getMachHeader];
	      struct nlist *symbols = (struct nlist*) (base + symCmd->symoff);
	      int *indirects        = (int*) (base + dysymCmd->indirectsymoff);
	      const char *strings   = (const char*) (base + symCmd->stroff);
	      unsigned long stub_size = sec->reserved2;
	      int index;

	      index = indirects[sec->reserved1 + (pc - start) / stub_size];
	      return (strings + symbols[index].n_un.n_strx);
	    }
	    else
	      error ("couldn't find symbol tables...");
	}
      }
    }
  }
  return 0;
}

/* Given an address, if the address is in a dylib stub table, 
 * return the address of the actual function (or NULL).
 * If the name parameter is non-null, use it to return the name
 * of the function in question.
 */

CORE_ADDR
get_symbol_stub_real_address (pc, name)
     CORE_ADDR pc;
     const char **name;
{
  struct symbol *sym;
  struct minimal_symbol *msym;
  const char *lname = get_symbol_stub_function_name (pc);

  if (name)
    *name = lname;
  if (lname == NULL)
    return 0;

  pc = 0;		/* found a name, now find a symbol and address */
  sym = lookup_symbol (lname, 0, VAR_NAMESPACE, 0, 0);
  if (sym == NULL && lname[0] == '_')
    sym = lookup_symbol (lname + 1, 0, VAR_NAMESPACE, 0, 0);
  if (sym) {
    pc = BLOCK_START(SYMBOL_BLOCK_VALUE(sym));
  } else {	/* no sym, try for an msym */
    msym = lookup_minimal_symbol (lname, NULL, NULL);
    if (msym == 0 && lname[0] == '_')
      msym = lookup_minimal_symbol (lname + 1, NULL, NULL);
    if (msym) {
      pc = SYMBOL_VALUE_ADDRESS (msym);
    }
  }
  return pc;
}

/* This function simply calls ptrace with the given arguments.  
   It exists so that all calls to ptrace are isolated in this 
   machine-dependent file. */
int
call_ptrace (request, pid, arg3, arg4)
     int request, pid, arg3, arg4;
{
  return ptrace (request, pid, arg3, arg4);
}

CORE_ADDR getImp(pc)
     CORE_ADDR pc;
{
  unsigned object, sel;
  if (getFirstTwoIntArgs(&object, &sel)) {
    CORE_ADDR imp = find_implementation(object, sel);
    return imp;
  } else
    return 0;
}

CORE_ADDR getImpForMsgSuper(pc)
     CORE_ADDR pc;
{
  unsigned sel, super;
  if (getFirstTwoIntArgs(&super, &sel)) {
    struct objc_super *mySuper = WARP((struct objc_super *)super);
    Class class = mySuper ? mySuper->class : NULL;
    CORE_ADDR imp = class ? find_implementation_from_class(class, sel) : 0;
    return imp;
  } else
    return 0;
}

/* isLibPrefix returns true if the symbol name begins with "_lib" or ".lib"
 */

static BOOL
isLibPrefix(name)
     const char *name;
{
  return (name[0] == '.' || name[0] == '_') &&
          name[1] == 'l'                    &&
	  name[2] == 'i'                    &&
	  name[3] == 'b';
}

/* The following data structure, "Ignore", is used to detect method calls
 * (thru ObjC runtime lib functions objc_msgSend, objc_msgSendSuper, etc.),
 * and ultimately find the method being called.
 */

typedef struct _Ignore {
  char *name;
  CORE_ADDR (*stop_at)(CORE_ADDR);
  CORE_ADDR begin;
  CORE_ADDR end;
} Ignore;

static Ignore ignored[] = {
  {"_objc_msgSend",                &getImp,            0, 0},
  {"_objc_msgSendSuper",           &getImpForMsgSuper, 0, 0},
  {"_objc_getClass",               NULL,               0, 0},
  {"_objc_getMetaClass",           NULL,               0, 0}
};

static int numIgnored = 0;
#define NUMIGNORED (sizeof(ignored) / sizeof(*ignored))
static int foundIndices = NO;

/* The following function, "find_objc_msgsend", fills in the data structure
 * "objc_msgs" by finding the addresses of each of the (currently four) 
 * functions that it holds (of which objc_msgSend is the first).  This
 * must be called each time symbols are loaded, in case the functions
 * have moved for some reason.
 */

void
find_objc_msgsend ()
{
  for (numIgnored = 0; numIgnored < NUMIGNORED; numIgnored++) {
    struct minimal_symbol *func = 
             lookup_minimal_symbol(ignored[numIgnored].name, NULL, NULL);

    /* maybe no underscore? */
    if (func == 0 && ignored[numIgnored].name[0] == '_')
      func = lookup_minimal_symbol(ignored[numIgnored].name + 1, NULL, NULL);
    if (func) {
      ignored[numIgnored].begin = SYMBOL_VALUE_ADDRESS (func);
      do {
	ignored[numIgnored].end = SYMBOL_VALUE_ADDRESS (++ func);
      } while (ignored[numIgnored].begin == ignored[numIgnored].end);
    }
  }
  foundIndices = YES;
}

/*
 * find_objc_msgcall (replaces pc_off_limits)
 *
 * ALL that this function now does is to determine whether the input 
 * address ("pc") is the address of one of the Objective C message
 * dispatch functions (mainly objc_msgSend or objc_msgSendSuper), and
 * if so, it returns the address of the method that will be called.
 *
 * The old function "pc_off_limits" used to do a lot of other things
 * in addition, such as detecting shared library jump stubs and 
 * returning the address of the shlib function that would be called.
 * That functionality has been moved into the SKIP_TRAMPOLINE_CODE
 * and IN_SOLIB_TRAMPOLINE macros, which are resolved in the target-
 * dependent modules.
 */

int
find_objc_msgcall (pc, new_pc)
     CORE_ADDR pc;
     CORE_ADDR *new_pc;
{
  int i;
  for (i = 0; i < numIgnored; i++) {
    if ((ignored[i].begin <= pc) && (pc < ignored[i].end)) {
      if (ignored[i].stop_at && new_pc)
	*new_pc = (*ignored[i].stop_at)(pc);
      return YES;
    }
  }
  *new_pc = 0;
  return NO;
}

int process_notify_msg(msg)
     notification_t *msg;
{
  if ((msg->notify_header.msg_id == NOTIFY_PORT_DELETED)
      && (msg->notify_port == inferior_task))
    return 1;
  else
    return 0;
}

kern_return_t
catch_exception_raise(port, thread, task, exception, code, subcode)
     port_t port;
     thread_t thread;
     task_t task;
{
  port_t replyPort;
/*  mach_NeXT_exception("catch_exception_raise", exception, code, subcode); */
  catch_task = task;
  catch_thread = thread;
  catch_exception = exception;
  catch_code = code;
  catch_subcode = subcode;
  if (task == inferior_task) {
    kern_return_t ret = task_suspend(task);
    handle_exception = YES;
    mach_call(ret, "task_suspend", "catch_exception_raise");
    return ret;
  } else {
    handle_exception = NO;
    mach_NeXT_exception("gdb: forwarding exception ", exception, 
			code, subcode);
    return KERN_FAILURE;
  }
}

struct msg {
  msg_header_t    header;
  int             data[20];
};

typedef struct _SignalMessage {
  msg_header_t header;
  msg_type_t   type;
  int          pid;
  int		 status;
} SignalMsg;

#define SIGNAL_MSG 0
#define STOP_MSG 1

void putExceptionPortBack(dummy)
{
  port_set_add(task_self(), gdb_ports, inferior_exception_port);
}

void drain_exceptions()
{
  struct msg inMsg, outMsg;
  int noMore, ret;
  struct cleanup *oldChain;

  mach_call_no_inferior(port_set_remove(task_self(), inferior_exception_port),
			"port_set_remove", "drain_exceptions");
  oldChain = make_cleanup(putExceptionPortBack, 0);
  for (noMore = NO; !noMore;) {
    inMsg.header.msg_local_port = inferior_exception_port;
    inMsg.header.msg_size = sizeof(inMsg);
    ret = msg_receive(&inMsg.header, RCV_TIMEOUT, 0);
    if (ret == KERN_SUCCESS) {
      debugPrintf("Before exc_server, incoming remote port is %x.\n",
		  inMsg.header.msg_remote_port);
      ret = exc_server(&inMsg, &outMsg);
      debugPrintf("exc_server returned %d.\n", ret);
      debugPrintf("After exc_server, outgoing remote port is %x.\n",
		  outMsg.header.msg_remote_port);
      if (ret && handle_exception) {
	msg_send(&outMsg.header, MSG_OPTION_NONE, 0);
	task_resume(inferior_task);
	if (catch_exception != EXC_BREAKPOINT) {
	  char exception_string[100];
	  strcpy(exception_string,
		 mach_NeXT_exception_string (catch_exception,
					     catch_code,
					     catch_subcode));
	  terminal_ours_for_output ();
	  printf("Program generated(%d): %s.\n",
		 catch_exception,
		 exception_string);
	}
      } else if (!handle_exception) {
	inMsg.header.msg_local_port  = inMsg.header.msg_remote_port ;
	inMsg.header.msg_remote_port = inferior_old_exception_port;
	mach_call_no_error(msg_send(&inMsg.header, MSG_OPTION_NONE, 0), 
			   "msg_send", "drain_exceptions");
      }
    } else
      noMore = YES;
  }
  mach_call_no_inferior(port_set_add(task_self(), gdb_ports, 
				     inferior_exception_port),
			"port_set_add", "drain_exceptions");
  discard_cleanups(oldChain);
}

#if 0
void
child_create_inferior(exec_file, allargs, env)
     char *exec_file, *allargs, **env;
{
  int pid;
  char *shell_command;
  extern int sys_nerr;
  extern char *sys_errlist[];
  extern struct target_ops child_ops;
  int len;
  int pending_execs;
  int debug_setpgrp;
  int fildes[2];
  char dummy = 0;

  len = 5 + strlen (exec_file) + 1 + strlen (allargs) + 1 + /*slop*/ 10;
  shell_command = (char *) alloca (len);
  sprintf(shell_command, "exec %s %s", exec_file, allargs);

  pipe(fildes);

  pid = vfork ();

  if (pid < 0)
    perror_with_name ("vfork");

  if (pid == 0)
    {
#ifdef TIOCGPGRP
      /* Run inferior in a separate process group.  */
      debug_setpgrp = setpgrp (getpid (), getpid ());
      if (0 != debug_setpgrp)
	 perror("setpgrp failed in child");
#endif /* TIOCGPGRP */

      /* Tell the terminal handling subsystem what tty we plan to run on;
	 it will now switch to that one if non-null.  */

      new_tty ();

      /* Changing the signal handlers for the inferior after
	 a vfork can also change them for the superior, so we don't mess
	 with signals here.  See comments in
	 initialize_signals for how we get the right signal handlers
	 for the inferior.  */

      read(fildes[0], &dummy, 1);
      close(fildes[0]);
      call_ptrace (0, 0, 0, 0);		/* "Trace me, Dr. Memory!" */
      execle ("/bin/sh", "/bin/sh", "-c", shell_command, (char *)0, env);

      fprintf (stderr, "Cannot exec %s: %s.\n", "/bin/sh",
	       errno < sys_nerr ? sys_errlist[errno] : "unknown error");
      fflush (stderr);
      _exit (0177);
    }

  /* Now that we have a child process, make it our target.  */
  push_target (&child_ops);

  create_inferior_hook(NULL, pid);

/* The process was started by the fork that created it,
   but it will have stopped one instruction after execing the shell.
   Here we must get it up to actual execution of the real program.  */

  inferior_pid = pid;		/* Needed for wait_for_inferior stuff below */

  clear_proceed_status ();

  /* We will get a trace trap after one instruction.
     Continue it automatically.  Eventually (after shell does an exec)
     it will get another trace trap.  Then insert breakpoints and continue.  */

  pending_execs = 2;

  init_wait_for_inferior ();

  /* Set up the "saved terminal modes" of the inferior
     based on what modes we are starting it with.  */
  target_terminal_init ();

  /* Install inferior's terminal modes.  */
  target_terminal_inferior ();

  write(fildes[1], &dummy, 1);
  close(fildes[1]);
  task_resume(inferior_task);

  while (1)
    {
      stop_soon_quietly = 1;	/* Make wait_for_inferior be quiet */
      wait_for_inferior ();
      if (stop_signal != SIGTRAP)
	{
	  /* Let shell child handle its own signals in its own way */
	  /* FIXME, what if child has exit()ed?  Must exit loop somehow */
	  resume (0, stop_signal);
	}
      else
	{
	  /* We handle SIGTRAP, however; it means child did an exec.  */
	  if (0 == --pending_execs)
	    break;
	  resume (0, 0);		/* Just make it go on */
	}
    }
  stop_soon_quietly = 0;

  initLoaded();

  /* Should this perhaps just be a "proceed" call?  FIXME */
  insert_step_breakpoint ();
  insertLoadBreakpoint();

  if (gdb_dyld_port == PORT_NULL) {
    /* MVS: dyld port needs to be initialized */
    mach_call (port_allocate (task_self (), &gdb_dyld_port),
	       "port_allocate", "child_create_inferior");
    mach_call(port_set_add(task_self(), gdb_ports, gdb_dyld_port),
	      "port_set_add", "child_create_inferior");
  }
  _dyld_debug_add_event_subscriber(inferior_task, 
				   2000, 2000, 
				   0, gdb_dyld_port);

  breakpoints_failed = insert_breakpoints ();
  if (!breakpoints_failed)
    {
      breakpoints_inserted = 1;
      target_terminal_inferior();
      /* Start the child program going on its first instruction, single-
	 stepping if we need to.  */
      resume (bpstat_should_step (), 0);
      wait_for_inferior ();
      normal_stop ();
    }
}

void
NeXT_create_inferior(execFile, allArgs, env)
     char *execFile, *allArgs, **env;
{
  child_create_inferior(execFile, allArgs, env);
}

#endif 0

int mach_oh_wait(pid, status)
     struct target_wait_status *status;
{
  int ret, gotMsg = NO, isGone = NO;
  struct msg inMsg, outMsg;
  SignalMsg *signalMsg;
  struct breakpoint *db = NULL;
  BOOL stepping = areStepping;
  static CORE_ADDR retPC = 0;
  extern BOOL safetiesInserted;

  if (pid == -1)
    pid = inferior_pid;
  stopped_in_ptrace = NO;
  while (!gotMsg) {
    inMsg.header.msg_local_port = gdb_ports;
    inMsg.header.msg_size = sizeof(inMsg);
    ret = msg_receive(&inMsg.header, 0, 0);
    if (ret == KERN_SUCCESS) {
      if (inMsg.header.msg_local_port == gdb_notify_port) {
	if (process_notify_msg((notification_t *)&inMsg)) {
	  if (attach_flag) {
	    gotMsg = YES;
	    isGone = PROCESS_DISAPPEARED;
	  }
	}
      } else if (inMsg.header.msg_local_port == inferior_exception_port) {
	debugPrintf("Before exc_server, incoming remote port is %x.\n",
		    inMsg.header.msg_remote_port);
	ret = exc_server(&inMsg, &outMsg);
	debugPrintf("exc_server returned %d.\n", ret);
	debugPrintf("After exc_server, outgoing remote port is %x.\n",
		    outMsg.header.msg_remote_port);
	if (ret && handle_exception) {
	  if (catch_thread != current_thread) {
	    last_current_thread = current_thread;
	    current_thread = catch_thread;
	    registers_changed();
	    flush_cached_frames();
	  }
	  ret = mach_call_no_error(msg_send(&outMsg.header, 
					    MSG_OPTION_NONE, 0),
				   "msg_send", "mach_oh_wait 1");
	  prepare_threads_after_stop();
	  if (catch_exception == EXC_BAD_ACCESS) {
	    db = is_data_break(catch_subcode);
#ifdef IMPRECISE_EXECEPTIONS
	    if (db)
	      breakPC = read_pc();
#endif /* IMPRECISE_EXECEPTIONS	*/
	    if (db == (struct breakpoint *)0xFFFFFFFF) {
	      remove_breakpoints ();
	      breakpoints_inserted = 0;
	      registers_changed();
	      child_resume(pid, 1, 0);
	    } else if (db != 0) {
	      catch_data_break = db;
	      gotMsg = YES;
	    } else
	      gotMsg = YES;
	  } else if (catch_exception == EXC_BREAKPOINT) {
#ifdef IMPRECISE_EXECEPTIONS
	    if (breakPC) {			
	      [regionManager putDataAt: (void *)breakPC
				   for: sizeof(breakpoint_insn)
				  from: (void *)shadow];
	      write_pc(read_pc() - DECR_PC_AFTER_BREAK);
	      breakPC = 0;
	    }
#endif /* IMPRECISE_EXECEPTIONS */
	    if (db) {
	      db = NULL;
	      registers_changed();
	      if (stepping)
		child_resume(pid, 1, 0);
	      else {
		insert_breakpoints ();
		breakpoints_inserted = 1;
		child_resume(pid, 0, 0);
	      }
	    } else if (safetiesInserted &&
		       isSafetyBreakpoint(read_pc() - DECR_PC_AFTER_BREAK)) {
	      retPC = INITIAL_RET_PC();
	      removeSafetyBreakpoints();
	      [regionManager flushMarkedPages];
	      remove_breakpoints ();
	      breakpoints_inserted = 0;
	      [regionManager getDataAt: (void *)retPC
				   for: sizeof(breakpoint_insn)
				  into: (void *)shadow];
	      [regionManager putDataAt: (void *)retPC
				   for: sizeof(breakpoint_insn)
				  from: (void *)breakpoint_insn];
	      write_pc(read_pc() - DECR_PC_AFTER_BREAK);
	      registers_changed();
	      child_resume(pid, 0, 0);
	    } else if (retPC && (read_pc() - DECR_PC_AFTER_BREAK == retPC)) {
	      [regionManager putDataAt: (void *)retPC
				   for: sizeof(breakpoint_insn)
				  from: (void *)shadow];
	      retPC = 0;
	      insert_breakpoints ();
	      breakpoints_inserted = 1;
	      write_pc(read_pc() - DECR_PC_AFTER_BREAK);
	      registers_changed();
	      child_resume(pid, 0, 0);
	    } else
	      gotMsg = YES;
	  } else
	    gotMsg = YES;

	  if (gotMsg && (catch_thread != current_thread)) {
	    last_current_thread = current_thread;
	    current_thread = catch_thread;
	    registers_changed();
	    flush_cached_frames();
	  }
	} else if (!handle_exception) {
	  inMsg.header.msg_local_port  = inMsg.header.msg_remote_port ;
	  inMsg.header.msg_remote_port = inferior_old_exception_port;
	  mach_call_no_error(msg_send(&inMsg.header, MSG_OPTION_NONE, 0), 
			     "msg_send", "mach_oh_wait 2");
	}
      } else if (inMsg.header.msg_local_port == signalPort) {
	signalMsg = (SignalMsg *)&inMsg;
	if (signalMsg->pid == pid) {
	  if (signalMsg->header.msg_id == SIGNAL_MSG) {
	    union wait *w = (union wait *) &signalMsg->status;

	    store_waitstatus(status, signalMsg->status);
/*           printf("Heard about signal %d for pid %d.\n", w->w_stopsig, */
/*		    inferior_pid); */
	    gotMsg = YES;
	    if (WIFEXITED(*w))
	      isGone = PROCESS_EXITED;
	    else if (!WIFSTOPPED(*w))
	      isGone = PROCESS_TERMINATED;
/*	    else */
/*	      prepare_threads_after_stop(); */
	    stopped_in_ptrace = YES;
	  } else if (signalMsg->header.msg_id == STOP_MSG){
	    gotMsg = YES;
	    mach_call(task_suspend(inferior_task),
		      "task_suspend", "mach_oh_wait 3");
	    drain_exceptions();
	    prepare_threads_after_stop();
	    catch_exception = -1;
	  }
	}
      } else if (inMsg.header.msg_local_port == gdb_dyld_port) {
	struct _dyld_event_message_request *request 
	  = (struct _dyld_event_message_request *)&inMsg;
	struct _dyld_event_message_reply reply;

	registers_changed ();
	_dyld_event_server(request, &reply);
	registers_changed ();
	mach_call (msg_send(&reply.head, MSG_OPTION_NONE, 0),
		   "msg_send", "mach_oh_wait 4");
      }
    }
  }
  return (int)isGone;
}

void drainDyldEvents ()
{
  struct _dyld_event_message_request request;
  struct _dyld_event_message_reply reply;
  int gotAll = NO;
  kern_return_t ret;
  struct target_task_state state;

  mach_call (port_set_remove (task_self (),gdb_dyld_port), 
	     "port_set_remove", "drainDyldEvents");

  Xprepare_msg_send (inferior_task, &state);

  while (!gotAll)
    {
      request.head.msg_local_port = gdb_dyld_port;
      request.head.msg_size = sizeof(request);

      mach_call (msg_receive(&request.head, 0, 0),
		 "msg_receive", "drainDyldEvents");

      if (request.event.type == DYLD_PAST_EVENTS_END)
	{
	  gotAll = YES;
	}

      _dyld_event_server(&request, &reply);

      mach_call (msg_send(&reply.head, MSG_OPTION_NONE, 0),
		 "msg_send", "drainDyldEvents");

    }

  Xfinish_msg_send (inferior_task, &state);

  mach_call(port_set_add(task_self(), gdb_ports, gdb_dyld_port),
	    "port_set_add", "drainDyldEvents");

  registers_changed ();
}

static CORE_ADDR dyld_address = 0;
static CORE_ADDR dyld_slide = 0;

void _dyld_event_server_callback(subscriber, event)
    port_t subscriber;
    struct dyld_event event;
{
  if (event.type == DYLD_IMAGE_ADDED)  {
    CORE_ADDR header_addr = (CORE_ADDR) event.arg[0].header;
    struct mach_header *header;
    unsigned slide = event.arg[0].vmaddr_slide;
    int add = 0;
    SegmentManager *manager;
    BOOL foundIt = NO;
    int i;
    struct load_command *loadCmd;
    struct dylib_command *newCommand = NULL;
    struct segment_command *newSegCommand = NULL;

    [regionManager invalidate];
    header = WARP ((struct mach_header*)header_addr);

    switch (header->filetype) {
    case MH_DYLIB:
      for (i = 0, loadCmd = (struct load_command *)(header + 1);
	   i < header->ncmds;
	   i++, ((void *)loadCmd) += loadCmd->cmdsize) {
	if (loadCmd->cmd == LC_ID_DYLIB)
	  newCommand = (struct dylib_command *)loadCmd;
	if (loadCmd->cmd == LC_SEGMENT
	    && !strcmp (((struct segment_command*)loadCmd)->segname, SEG_TEXT))
	  newSegCommand = ((struct segment_command*)loadCmd);
      }
      if (newCommand)
	printf("%s", newCommand->dylib.name.offset + (char *)newCommand);
      add = 1;
      break;
    case MH_DYLINKER:
      dyld_address = header_addr;
      dyld_slide = slide;
      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;
    case MH_BUNDLE:
      printf ("Bundle");
      add = 1;
      break;
    default:
      printf ("Ignored Object Module");
    }
    printf (" at 0x%x offset 0x%x\n", header_addr, slide);
    if (add) {
      if (newCommand) {
	struct objfile *obj;

	ALL_OBJFILES (obj) {
	  SegmentManager *manager = (SegmentManager*) obj->manager;
	  struct mach_header *oldHeader = [manager getMachHeader];
	  struct dylib_command *oldCommand = NULL;
	  struct segment_command *oldSegCommand = NULL;

	  for (i = 0, loadCmd = (struct load_command *)(oldHeader + 1);
	       i < oldHeader->ncmds;
	       i++, ((void *)loadCmd) += loadCmd->cmdsize) {
	    if (loadCmd->cmd == LC_ID_DYLIB)
	      oldCommand = (struct dylib_command *) loadCmd;
	    if (loadCmd->cmd == LC_SEGMENT && 
		!strcmp(((struct segment_command *) loadCmd)->segname, 
			SEG_TEXT))
	      oldSegCommand = ((struct segment_command*)loadCmd);
	  }

	  if (oldCommand && 
	      (strcmp(newCommand->dylib.name.offset + (char *) newCommand,
		      oldCommand->dylib.name.offset + (char *) oldCommand) 
	       == 0)) {
	    if ((newCommand->dylib.timestamp == oldCommand->dylib.timestamp)
		&& oldSegCommand
		&& newSegCommand
		&& (oldSegCommand->vmaddr + [manager getSlide] ==
		    newSegCommand->vmaddr + slide)) {
	      extern void dyld_breakpoint_re_set ();
	      dyld_breakpoint_re_set ();
	      add = 0;
	    } else {
	      invalidate_breakpoints_between(obj->start, obj->end);
	      free_objfile(obj);
	      removeExecManager(manager);
	    }
	    break;
	  }
	}
      }
      if (add) {
	extern int forceRegionManager;
	struct cleanup *oldChain;
	struct objfile *obj;

	header = [regionManager copyLoadedImage: 
		  (struct mach_header*) header_addr withSlide:slide];
	manager = [SegmentManager newHeader:header withSlide:0];
	forceRegionManager = 1;

	oldChain = make_cleanup(setToZero, &forceRegionManager);
	addExecManager(manager);
	obj = symfile_add (manager, 0, 0, 0, 0, 0);
	obj->flags |= OBJF_DYLIB;
	discard_cleanups(oldChain);
	forceRegionManager = 0;
      }
    }
  }
#ifdef DYLD_DEBUG
  else if (event.type == DYLD_MODULE_BOUND)
    printf("DYLD Module bound: 0x%08x -- 0x%08x -- %4d\n", 
	   event.arg[0].header, 
	   event.arg[0].vmaddr_slide, 
	   event.arg[0].module_index);
  else if (event.type == DYLD_MODULE_REMOVED)
    printf("DYLD Module removed.\n");
  else if (event.type == DYLD_MODULE_REPLACED)
    printf("DYLD Module replaced.\n");
  else if (event.type == DYLD_PAST_EVENTS_END)
    printf("DYLD past events end.\n");
#endif /* DYLD_DEBUG */
}

#ifdef __DYNAMIC__
/*
 * The following symbol is referenced by libsys symbolically (instead of
 * through undefined reference). To get strip(1) to know this symbol is not
 * to be stripped it needs to have the REFERENCED_DYNAMICALLY bit
 * (0x10) set. This would have been done automaticly by ld(1) if this symbol
 * was referenced through an undefined symbol.
 */
asm(".desc __dyld_event_server_callback, 0x10");
#endif /* __DYNAMIC__ */

static void unloadDylibs()
{
  struct objfile *obj, *nxt = 0;
  ALL_OBJFILES_SAFE (obj,nxt) {
    SegmentManager *manager = (SegmentManager*) obj->manager;
    struct mach_header *oldHeader = [manager getMachHeader];

    if (oldHeader->filetype == MH_DYLIB) {
      CORE_ADDR headerAddr = (CORE_ADDR) [manager 
					  originalPointerFor: oldHeader];
      invalidate_breakpoints_between(headerAddr,
				     [manager getMaximumTextAddress]);
    }

    else if (oldHeader->filetype == MH_BUNDLE) {
      free_objfile(obj);
      removeExecManager(manager);
    }
  }
}

maybeAddDyld ()
{
  if (debug_dyld)
    {
      SegmentManager *manager;
      struct mach_header *header;

      header = WARP ((struct mach_header*)dyld_address);
      if (dyld_address
	  && header 
	  && header->magic == MH_MAGIC
	  && header->filetype == MH_DYLINKER)
	{
	  printf ("Adding dynamic linker...\n");
	  header = [regionManager
		    copyLoadedImage:(struct mach_header*)dyld_address
			  withSlide:dyld_slide];
	  manager = [SegmentManager newHeader:header withSlide:0];
	  addExecManager(manager);
	  addLoadedObjectModule (symfile_add (manager, 0, 0, 0, 0, 0));
	}
      else
	{
	  printf ("Rerun to enable dyld debugging\n");
	}
    }
}

void buildSignalMsg(signalMsg, msg_id)
     SignalMsg *signalMsg;
     int msg_id;
{
  signalMsg->header.msg_simple = TRUE;
  signalMsg->header.msg_size = sizeof(SignalMsg);
  signalMsg->header.msg_type = MSG_TYPE_NORMAL;
  signalMsg->header.msg_local_port = PORT_NULL;
  signalMsg->header.msg_remote_port = signalPort;
  signalMsg->header.msg_id = msg_id;

  signalMsg->type.msg_type_name = MSG_TYPE_INTEGER_32;
  signalMsg->type.msg_type_size = 32;
  signalMsg->type.msg_type_number = 2;
  signalMsg->type.msg_type_inline = TRUE;
  signalMsg->type.msg_type_longform = FALSE;
  signalMsg->type.msg_type_deallocate = FALSE;
}

void
waitForSignal(dummy)
     void *dummy;
{
  union wait w;
  int waitPid;
  SignalMsg signalMsg;
  buildSignalMsg(&signalMsg, SIGNAL_MSG);
  while (1) {
/*  printf("Waiting for signals.\n"); */
    waitPid = wait(&w);
    if (waitPid == -1) {
/*    printf("There are no children to wait on.\n"); */
      mutex_lock(childrenMutex);
      childrenExist = NO;
      while (!childrenExist)
	condition_wait(childrenCondition, childrenMutex);
      mutex_unlock(childrenMutex);
    } else {
/*    printf("Got signal %d for pid %d.\n", w.w_stopsig, waitPid); */
      signalMsg.pid = waitPid;
      signalMsg.status = w.w_status;
      msg_send(&signalMsg.header, 0, 0);
    }
  }
}

void
inferiorSigInt()
{
  SignalMsg signalMsg;

  buildSignalMsg(&signalMsg, STOP_MSG);
  signalMsg.pid = inferior_pid;
  msg_send(&signalMsg.header, 0, 0);
}

void
kill_inferior_fast ()
{
  kern_return_t ret;
  struct target_waitstatus w;

  if (inferior_pid == 0)
    return;
  else if (inferior_pid == -1) {
    unloadStatesFrom(0);
    unloadDylibs();
    if (mach_call_no_error(task_terminate(inferior_task),
			   "task_terminate", "kill_inferior_fast"))
      return;
  } else {
    unloadStatesFrom(0);
    unloadDylibs();
#ifdef i386
    /*
     * BKM - XXX 4/20/92
     * This is needed to prevent a race condition between the child's
     * death and possible arrival of a breakpoint exception (which would
     * make our exception handler suspend the task).  This is caused
     * by a bug in the 386 kernel which needs to be fixed (i.e. PT_KILL
     * allows some threads to return to user mode).
     */
    clear_trace_bit(current_thread);
#endif
    if (attach_flag) {
      attach_flag = 0;
      kill (inferior_pid, SIGHUP);
      if (mach_call_no_error(task_resume(inferior_task),
			     "task_resume", "kill_inferior_fast 1"))
	return;
    } else {
      if (!stopped_in_ptrace) {
	kill (inferior_pid, SIGHUP);
	if (mach_call_no_error(task_resume(inferior_task),
			       "task_resume", "kill_inferior_fast 2"))
	  return;
	if (!mach_oh_wait(-1, &w)) {
	  ptrace(PT_KILL, inferior_pid, 0, 0);
	  mach_oh_wait(-1, &w);
	}
      } else {
	ptrace(PT_KILL, inferior_pid, 0, 0);
	mach_oh_wait(-1, &w);
      }
    }
  }
}

void
kill_inferior ()
{
  kill_inferior_fast ();
  NeXT_mourn_inferior ();
  tell_displayer_state_changed(DBG_STATE_INFERIOR_EXITED);
}

void
child_resume (pid, step, signal)
     int pid, step;
     enum target_signal signal;
{
  kern_return_t ret;

  if (pid == -1)
    pid = inferior_pid;	/* use default pid */
  areStepping = step;
  errno = 0;
  [regionManager invalidate];
#ifdef IMPRECISE_EXECEPTIONS
  if (breakPC) {
    [regionManager getDataAt: (void *)breakPC
			 for: sizeof(breakpoint_insn)
			into: (void *)shadow];
    [regionManager putDataAt: (void *)breakPC
			 for: sizeof(breakpoint_insn)
			from: (void *)breakpoint_insn];
    prepare_threads_before_run(0);
  } else
    prepare_threads_before_run(step);
#else /* IMPRECISE_EXECEPTIONS */
  prepare_threads_before_run(step);
#endif /* IMPRECISE_EXECEPTIONS */
  if (stopped_in_ptrace) {
    call_ptrace (step ? PTRACE_SINGLESTEP : PTRACE_CONT,
		 pid, 1, target_signal_to_host (signal));
    if (errno)
      perror_with_name ("ptrace");
    else {
      tell_displayer_state_changed(DBG_STATE_INFERIOR_RUNNING);
    }
  } else {
    if ((ret = task_resume (inferior_task)) != KERN_SUCCESS) {
      fprintf(stderr, "Error resuming inferior.\n");
      mach_error("task_resume", ret);
      error("resume: task_resume");
    }
    tell_displayer_state_changed(DBG_STATE_INFERIOR_RUNNING);
  }
}

/* Start debugging the process whose number is PID.  */
int
attach (pid)
     int pid;
{
  errno = 0;
  attach_flag = 1;
  return pid;
}

/* Stop debugging the process whose number is PID
   and continue it with signal number SIGNAL.
   SIGNAL = 0 means just continue it.  */

/* ARGSUSED */
void
detach (signal)
     int signal;
{
  unloadStatesFrom(0);
  removeLoadBreakpoint();
  if (gdb_dyld_port != PORT_NULL) {
    /* MVS: the dyld port must be removed on detaching */
    /* Otherwise the inferior will hang, trying to talk to this port */
    /* We set the port to null, so that it will be re-initialized */
    /* with a new inferior later. */
    port_deallocate (task_self (), gdb_dyld_port);
    gdb_dyld_port = PORT_NULL;
  }
  if (kernel_attached) {
    mach_call(task_detach(inferior_task), "task_resume", "detach 1");
    kernel_attached = NO;
  } else
    mach_call(task_resume(inferior_task), "task_resume", "detach 2");
  attach_flag = 0;
}

/* Copy LEN bytes to or from inferior's memory starting at MEMADDR
   to debugger memory starting at MYADDR.   Copy to inferior if
   WRITE is nonzero.

   Returns the length copied, which is either the LEN argument or zero.
   This xfer function does not do partial moves, since child_ops
   doesn't allow memory operations to cross below us in the target stack
   anyway.  */

int
child_xfer_memory (memaddr, myaddr, len, write, target)
     CORE_ADDR memaddr;
     char *myaddr;
     int len, write;
     struct target_ops *target;
{
  if (write)
    return [regionManager putDataAt: (void *)memaddr for: len from: myaddr];
  else {
    int ret = [regionManager getDataAt: (void *)memaddr for: len into: myaddr];
    if (!ret && read_memory_out_of_bounds_ok) {
/*    printf ("Bad read attempt at %p for %d bytes.\n", memaddr, len); */
      bzero(myaddr, len);
      ret = 1;
    }
    return ret;
  }
}

static void
badTaskFromRegionManager()
{
  NeXT_mourn_inferior();
  error("The program you were running has been terminated.");
}

void 
NeXT_mourn_inferior ()
{
  static int inMourning = 0;

  if (inMourning) {
    /* This means that we got an error while mourning.  Don't try it again. */
    inMourning = 0;
  } else {
    inMourning = 1;
    unloadStatesFrom(0);
    unloadDylibs();
    inferior_task = TASK_NULL;
    regionManager = [regionManager free];
    clearPrintStream();
    child_mourn_inferior ();
    inMourning = 0;
    stopped_in_ptrace = 0;
  }
}

void
start_inferior_hook()
{
}

void
create_inferior_for_task(task, isLazy)
     task_t task;
     BOOL isLazy;
{
  thread_array_t thread_list;
  unsigned int thread_count;
  extern int stop_print_frame;

  inferior_task = task;
  mach_call(port_allocate(task_self(), &inferior_exception_port),
	    "port_allocate", "create_inferior_for_task");
  mach_call(task_get_exception_port(inferior_task, 
				    &inferior_old_exception_port),
	    "task_get_special_port", "create_inferior_for_task");
  mach_call(task_set_exception_port(inferior_task, inferior_exception_port),
	    "task_set_special_port", "create_inferior_for_task");
  mach_call_no_inferior(port_set_add(task_self(), gdb_ports, 
				     inferior_exception_port),
			"port_set_add", "create_inferior_for_task");
  mach_call(task_threads(inferior_task, &thread_list, &thread_count),
	    "task_threads", "create_inferior_for_task");
  last_current_thread = current_thread = thread_list[0];
  mach_call(vm_deallocate(task_self(), (vm_address_t)thread_list, 
			  (vm_size_t)(thread_count * sizeof(int))),
	    "vm_deallocate", "create_inferior_for_task");
  regionManager = [(lazyRead || isLazy) ? 
		   [LazyRegionManager class] : [RegionManager class] 
		   newTask: inferior_task readInRegions: NO];
  [regionManager setTaskGoneCallBack: &badTaskFromRegionManager];
  if (kernel_attached)
    [regionManager combineRegions: NO];
  current_target.to_wait = &mach_oh_wait;
  current_target.to_load = NULL;
  current_target.to_mourn_inferior = &NeXT_mourn_inferior;
  //    insertLoadBreakpoint();
  clearThreadState();
}

task_t findPidServer(host)
     char *host;
{
  task_t pidServer = TASK_NULL;

  if (netname_look_up(name_server_port, host, "PIDSERVER", &pidServer)
      != KERN_SUCCESS) {
    if (vfork()) {
      int tries, found;

      for (tries = 0, found = NO; (tries < 20) && !found; tries++) {
	usleep(500000);
	found = (netname_look_up(name_server_port, host, "PIDSERVER", 
				 &pidServer)
		 == KERN_SUCCESS);
      }
    } else
      execl("/usr/ucb/rsh", "/usr/ucb/rsh", host, "-n", 
	    "/usr/lib/pidServer", NULL);
  }
  return pidServer;
}

void
create_inferior_hook(args, pid)
     char *args;
     int pid;
{
  task_t infTask;
  char *space, *host, *colon;
  struct cleanup *oldChain;

  oldChain = make_cleanup(setToZero, &inferior_pid);
  if (pid == -1) {
    colon = strchr(args, ':');
    if (colon) {
      task_t pidServer;
      *colon = '\0';
      host = args;
      pidServer = findPidServer(host);
      if (!pidServer)
	error("Problem finding pid server on %s.", host);
      else {
	int remotePid = atoi(colon + 1);

	if (task_by_unix_pid(pidServer, remotePid, &infTask) == KERN_SUCCESS)
	  lazyRead = 1;
	else
	  error("Problem finding task for pid %d on %s",
		remotePid, host);
      }
    } else {
      space = strchr(args, ' ');
      if (space) {
	for (host = space; *host && (*host == ' '); host++);
	*space = '\0';
      } else
	host = "";
      if (netname_look_up(name_server_port, host, args, &infTask)
	  != KERN_SUCCESS)
	error("Problem finding task named %s on %s",
	      args,
	      *host ? host : "localhost");
    }
  } else {
    if (task_by_unix_pid(task_self(), pid, &infTask) != KERN_SUCCESS)
      error("Problem attaching to pid %d.", pid);
  }

  kernel_attached = NO;
  create_inferior_for_task(infTask, NO);

  discard_cleanups(oldChain);

  if (attach_flag) {
    prepare_threads_after_stop();
    getLoadedSymbols();
    insertLoadBreakpoint();
    flush_cached_frames();
    registers_changed ();
    task_suspend(inferior_task);
    stop_pc = read_pc();
    step_start_function = find_pc_function(stop_pc);
    stop_print_frame = 1;
    reread_exec();	/* MVS: see if any symbols need updating */
  } else {
    mutex_lock(childrenMutex);
    childrenExist = YES;
    condition_signal(childrenCondition);
    mutex_unlock(childrenMutex);
  }
}

static char *remoteHost = NULL;

int isTask(task)
     task_t task;
{
  struct task_basic_info taskInfo;
  unsigned int taskInfoCount;
  taskInfoCount = TASK_BASIC_INFO_COUNT;
  return ((task_info(task,
		     TASK_BASIC_INFO,
		     (task_info_t)&taskInfo,
		     &taskInfoCount) == KERN_SUCCESS) ? YES : NO);
}

void
suspendSignalThread()
{
  if (signalThread) {
    if (!signalSuspendCount)
      thread_suspend(cthread_thread(signalThread));
    signalSuspendCount++;
  }
}

void
resumeSignalThread()
{
  if (signalThread) {
    if (signalSuspendCount)
      thread_resume(cthread_thread(signalThread));
    signalSuspendCount--;
  }
}

void 
_initialize_next_inferior()
{
  mach_call(port_set_allocate(task_self(), &gdb_ports),
	    "port_set_allocate", "_initialize_next_inferior");
  mach_call(port_allocate(task_self(), &gdb_notify_port),
	    "port_allocate", "_initialize_next_inferior");
  mach_call(task_set_notify_port(task_self(), gdb_notify_port),
	    "task_set_notify_port",  "_initialize_next_inferior");
  mach_call(port_set_add(task_self(), gdb_ports, gdb_notify_port),
	    "port_set_add", "_initialize_next_inferior");
  mach_call(port_allocate(task_self(), &signalPort),
	    "port_allocate", "_initialize_next_inferior");
  mach_call(port_set_backlog(task_self(), signalPort, PORT_BACKLOG_MAX),
	    "port_set_backlog", "_initialize_next_inferior");
  mach_call(port_set_add(task_self(), gdb_ports, signalPort),
	    "port_set_add", "_initialize_next_inferior");
  /* Note: gdb_dyld_port no longer initialized here.  MVS */
  childrenCondition = condition_alloc();
  childrenMutex = mutex_alloc();
  signalThread = cthread_fork((cthread_fn_t)&waitForSignal, NULL);
  cthread_set_name(signalThread, "signal thread");
  cthread_detach(signalThread);
#if 0
  add_show_from_set(add_set_cmd("host", class_run, var_string_noescape, 
				(char *)&remoteHost,
				"Set name of host to run program on.\n"
				"Currently only works for mach tasks.",
				&setlist),
		    &showlist);
#endif 0      	
  add_show_from_set(add_set_cmd("lazy-read", class_run, var_boolean, 
				(char *)&lazyRead,
				"Set if inferior's memory is read lazily.",
				&setlist),
		    &showlist);
  add_show_from_set(add_set_cmd("debug-dyld", class_files, var_boolean, 
				(char *)&debug_dyld,
				"Set dynamic link-editor debugging.",
				&setlist),
		    &showlist);

  if (foundIndices)	/* de-initialize Ignored[] */
    foundIndices = numIgnored = 0;
}

void save_inferior_status_hook(inf_status)
     struct inferior_status *inf_status;
{
  inf_status->extra_data[0] = current_thread;
  inf_status->extra_data[1] = step_thread;
}

void restore_inferior_status_hook(inf_status)
     struct inferior_status *inf_status;
{
  current_thread = inf_status->extra_data[0];
  step_thread = inf_status->extra_data[1];
}

int
mach_set_execute (start, size)
     CORE_ADDR start;
     unsigned size;
{
  vm_address_t addr = start;
  vm_size_t    siz = size;
  vm_prot_t    prot;
  vm_prot_t max_protection;
  vm_inherit_t inheritance;
  boolean_t shared;
  port_t object_name;
  vm_offset_t offset;

  mach_call (vm_region (inferior_task, &addr, &siz, &prot, &max_protection,
			&inheritance, &shared, &object_name, &offset),
	     "vm_region",
	     "mach_set_execute");

  mach_call (vm_protect (inferior_task, start, size, 0, 
			 prot | VM_PROT_EXECUTE),
	     "vm_protect",
	     "mach_set_execute");

  return (int)prot;
}


void
mach_unset_execute (start, size, prev_prot)
     CORE_ADDR start;
     unsigned size;
     vm_prot_t prev_prot;
{
  mach_call (vm_protect (inferior_task, start, size, 0, prev_prot),
	     "vm_protect",
	     "mach_unset_execute");
}
