/* Native-dependent code for emx
   Copyright 1995-1996 Eberhard Mattes.

This file is part of GDB.

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 2 of the License, 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, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include "defs.h"
#include "symtab.h"
#include "gdbcore.h"
#include "symfile.h"
#include "objfiles.h"
#include "complain.h"
#include "gdb-stabs.h"
#include "gdbcmd.h"
#include "emx-nat.h"
#include "inferior.h"
#include <stddef.h>
#include <signal.h>
#include <string.h>
#include <sys/ptrace.h>
#include <sys/user.h>
#include <sys/wait.h>
#include <sys/uflags.h>

struct dll_break
{
  struct dll_break *next;
  char *name;
  int num;
};

int close_sessions = 0;
int switch_sessions = 1;
static int show_dlls = 0;

static int dll_break_num = 0;
static struct dll_break *dll_break_list = NULL;

static int desc_num = 0;
struct desc *desc_list = NULL;

/* See dbxread.c */
extern struct sym_fns aout_sym_fns;

static struct cmd_list_element *descendantlist;

static struct desc *find_desc ();


static void load_dll_symbols (struct ptn_module *pmod)
{
  struct objfile *objfile;
  struct section_offsets *section_offsets;
  struct obj_section *s;
  const char *sec_name;
  struct cleanup *old_chain;
  bfd *abfd;
  long adjust;

  /* Note that pmod->name is already an absolute file name. */

  abfd = bfd_openr (pmod->name, gnutarget);
  if (!abfd)
    return;
  printf_filtered ("Reading symbols from %s...", pmod->name);

  abfd->cacheable = true;
  if (!bfd_check_format (abfd, bfd_object))
    {
      printf_filtered ("\nCan't read symbols: %s.\n",
                       bfd_errmsg (bfd_get_error ()));
      bfd_close (abfd);
      return;
    }

  objfile = allocate_objfile (abfd, 0);

  /* The following code is partially borrowed from syms_from_objfile()
     and find_sym_fns(). */

  init_entry_point_info (objfile);

  if (bfd_get_flavour (objfile -> obfd) != aout_sym_fns.sym_flavour)
    {
      printf_filtered ("\nSymbol format `%s' unknown.\n",
                       bfd_get_target (objfile -> obfd));
      return;
    }
  objfile -> sf = &aout_sym_fns;

  /* Make sure that partially constructed symbol tables will be cleaned up
     if an error occurs during symbol reading.  */
  old_chain = make_cleanup (free_objfile, objfile);

  /* Initialize symbol reading routines for this objfile, allow complaints to
     appear for this new file, and record how verbose to be, then do the
     initial symbol reading for this file. */

  (*objfile -> sf -> sym_init) (objfile);
  clear_complaints (1, 1);

  section_offsets = (*objfile -> sf -> sym_offsets) (objfile, 0);

  /* This code is the reason for doing all this inline instead of
     calling syms_from_objfile(). */

  for (s = objfile->sections; s < objfile->sections_end; ++s)
    {
      sec_name = bfd_section_name (abfd, s->the_bfd_section);
      if (strcmp (sec_name, ".text") == 0)
        {
          adjust = pmod->text_start - s->addr;
          s->addr += adjust;
          s->endaddr += adjust;
          ANOFFSET (section_offsets, SECT_OFF_TEXT) = adjust;
        }
      else if (strcmp (sec_name, ".data") == 0)
        {
          adjust = pmod->data_start - s->addr;
          s->addr += adjust;
          s->endaddr += adjust;
          ANOFFSET (section_offsets, SECT_OFF_DATA) = adjust;
          if (ANOFFSET (section_offsets, SECT_OFF_BSS) == 0)
            ANOFFSET (section_offsets, SECT_OFF_BSS) = adjust;
        }
      else if (strcmp (sec_name, ".bss") == 0)
        {
          adjust = pmod->bss_start - s->addr;
          s->addr += adjust;
          s->endaddr += adjust;
          ANOFFSET (section_offsets, SECT_OFF_BSS) = adjust;
        }
    }

  objfile->section_offsets = section_offsets;

  (*objfile -> sf -> sym_read) (objfile, section_offsets, 0);

  if (!have_partial_symbols () && !have_full_symbols ())
    printf_filtered ("(no debugging symbols found)...\n");

  /* Mark the objfile has having had initial symbol read attempted.  Note
     that this does not mean we found any symbols... */

  objfile -> flags |= OBJF_SYMS;

  /* Discard cleanups as symbol reading was successful.  */

  discard_cleanups (old_chain);

  /* Call this after reading in a new symbol table to give target
     dependant code a crack at the new symbols.  For instance, this
     could be used to update the values of target-specific symbols GDB
     needs to keep track of (such as _sigtramp, or whatever).  */

  TARGET_SYMFILE_POSTREAD (objfile);

  new_symfile_objfile (objfile, 0, 0);
}


/* Partially borrowed from inftarget.c */
/* Wait for child to do something.  Return pid of child, or -1 in case
   of error; store status through argument pointer OURSTATUS.  */

int
child_wait (pid, ourstatus)
     int pid;
     struct target_waitstatus *ourstatus;
{
  int save_errno;
  int status, n;
  union ptn_data ptnd;
  const char *name;
  struct dll_break *pb;
  struct desc *pd;

  while (1)
    {
      set_sigint_trap();	/* Causes SIGINT to be passed on to the
                                   attached process. */
      set_sigio_trap ();

      pid = wait (&status);
      save_errno = errno;

      clear_sigio_trap ();

      clear_sigint_trap();

      if (pid == -1)
        {
          if (save_errno == EINTR)
            continue;
          fprintf_unfiltered (gdb_stderr, "Child process unexpectedly missing: %s.\n",
                              safe_strerror (save_errno));
          /* Claim it exited with unknown signal.  */
          ourstatus->kind = TARGET_WAITKIND_SIGNALLED;
          ourstatus->value.sig = TARGET_SIGNAL_UNKNOWN;
          return -1;
        }
      if (PTRACE_GETPID (pid) != PTRACE_GETPID (inferior_pid))
        continue;

      /* wait() reports thread 0 on termination of the process --
         pretend that it's thread 1 to avoid confusing GDB. */

      if (PTRACE_GETTID (inferior_pid) != 0 && PTRACE_GETTID (pid) == 0)
        pid = inferior_pid;

      if (!WIFSTOPPED (status) || WSTOPSIG (status) != SIGPTRACENOTIFY)
        break;

      memset (&ptnd, 0, sizeof (ptnd));
      n = ptrace (PTRACE_NOTIFICATION, inferior_pid,
                  (int)&ptnd, (int)sizeof (ptnd));
      if (n < 0)
        break;
      switch (n)
        {
        case PTN_THREAD_NEW:
          store_waitstatus (ourstatus, status);
          return pid;
        case PTN_THREAD_END:
          if (annotation_level > 1)
            printf_filtered ("\n\032\032thread-end %u\n",
                             PTRACE_PIDTID (PTRACE_GETPID (inferior_pid),
                                            ptnd.thread.tid));
          printf_filtered ("[Thread terminated: %u]\n", ptnd.thread.tid);
          break;
        case PTN_MODULE_LOAD:
          if (!(ptnd.module.flags & PTNMOD_DLL))
            break;
          if (show_dlls)
            {
              printf_filtered ("[Load DLL: %s]\n", ptnd.module.name);
              if (ptnd.module.text_size != 0)
                printf_filtered ("[.text: 0x%.8lx - 0x%.8lx]\n",
                                 ptnd.module.text_start,
                                 (ptnd.module.text_start
                                  + ptnd.module.text_size));
              if (ptnd.module.data_size != 0)
                printf_filtered ("[.data: 0x%.8lx - 0x%.8lx]\n",
                                 ptnd.module.data_start,
                                 (ptnd.module.data_start
                                  + ptnd.module.data_size));
              if (ptnd.module.bss_size != 0)
                printf_filtered ("[.bss:  0x%.8lx - 0x%.8lx]\n",
                                 ptnd.module.bss_start,
                                 (ptnd.module.bss_start
                                  + ptnd.module.bss_size));
            }
          if (ptnd.module.text_size != 0 && (ptnd.module.flags & PTNMOD_AOUT))
            load_dll_symbols (&ptnd.module);
          name = _getname (ptnd.module.name);
          for (pb = dll_break_list; pb; pb = pb->next)
            if (_fncmp (pb->name, name) == 0)
              {
                terminal_ours ();
                printf_filtered ("Stop-on-load breakpoint %d hit (%s).\n",
                                 pb->num, ptnd.module.name);
                normal_stop ();
                /* TODO: This is dirty */
                return_to_top_level (RETURN_QUIT);
              }
          break;
        case PTN_MODULE_FREE:
          if (show_dlls)
            printf_filtered ("[Free DLL: %s]\n", ptnd.module.name);
          break;
        case PTN_PROC_NEW:
          _fnslashify (ptnd.proc.name);
          printf_unfiltered ("[New process: %u %s]\n",
                             ptnd.proc.pid, ptnd.proc.name);
          if (!(ptnd.proc.flags & PTNPROC_AOUT)
              || _osmode != OS2_MODE
              || (pd = find_desc (ptnd.proc.name,
                                  ptnd.proc.flags & PTNPROC_FORK)) == NULL
              || !os2_debug_descendant (ptnd.proc.pid, ptnd.proc.name, pd,
                                        ptnd.proc.fork_addr))
            {
              /* Let the child go. */

              ptrace (PTRACE_ATTACH, ptnd.proc.pid, 0, 0);
              ptrace (PTRACE_DETACH, ptnd.proc.pid, 1, 0);
            }
          break;
        default:
          printf_filtered ("[Unknown notification: %d]\n", n);
          break;
        }
      terminal_inferior ();
      if (ptrace (PTRACE_CONT, pid, 0, 0) < 0)
        break;
    }
  store_waitstatus (ourstatus, status);
  return pid;
}


/* Return nonzero if the given thread is still alive.  */
int
child_thread_alive (pid)
     int pid;
{
  return (ptrace (PTRACE_PEEKUSER, pid,
                  offsetof (struct user, u_ar0), 0) != -1);
}


char *
emx_pid_to_str (pid)
     int pid;
{
  static char buf[40];

  if (PTRACE_GETPID (pid) == PTRACE_GETPID (inferior_pid)
      && PTRACE_GETTID (pid) != 0)
    sprintf (buf, "thread %u", PTRACE_GETTID (pid));
  else if (PTRACE_GETTID (pid) == 0)
    sprintf (buf, "process %u", PTRACE_GETPID (pid));
  else
    sprintf (buf, "process %u thread %u",
             PTRACE_GETPID (pid), PTRACE_GETTID (pid));
  return buf;
}


void
emx_thread_enable_disable (pid, enable)
     int pid, enable;
{
  if (ptrace (enable ? PTRACE_THAW : PTRACE_FREEZE, pid, 0, 0) == -1)
    perror_with_name ("ptrace");
}


static void
dll_break_info (args, from_tty)
    char *args;
    int from_tty;
{
  struct dll_break *p;

  for (p = dll_break_list; p; p = p->next)
    printf_filtered ("%d %s\n", p->num, p->name);
}


static void
dll_break_command (arg, from_tty)
    char *arg;
    int from_tty;
{
  struct dll_break *p, **pp;
  char *name;

  if (!arg)
    error ("DLL name expected");

  if (arg != _getname (arg))
    error ("The DLL name should not include a path");

  name = xmalloc (strlen (arg) + 5);
  strcpy (name, arg);
  _defext (name, "dll");

  for (p = dll_break_list; p; p = p->next)
    if (_fncmp (p->name, name) == 0)
      {
        free (name);
        error ("Stop-on-load breakpoint already set");
      }
  p = xmalloc (sizeof (*p));
  p->name = name;
  p->num = ++dll_break_num;
  p->next = NULL;
  for (pp = &dll_break_list; *pp; pp = &(*pp)->next)
    ;
  *pp = p;
  printf_filtered ("Stop-on-load breakpoint %d set for %s.\n",
                   p->num, p->name);
}


static void
dll_clear_command (arg, from_tty)
    char *arg;
    int from_tty;
{
  struct dll_break *p, **pp;
  int num;

  if (!arg)
    error ("Breakpoint number expected");
  num = atoi (arg);
  if (num <= 0)
    error ("Invalid breakpoint number");
  for (pp = &dll_break_list; *pp; pp = &(*pp)->next)
    {
      p = *pp;
      if (p->num == num)
        {
          printf_filtered ("Stop-on-load breakpoint for %s cleared.\n",
                           p->name);
          *pp = p->next;
          free (p->name);
          free (p);
          return;
        }
    }
  error ("Invalid breakpoint number.  Use \"info dll-break\" to get a list.");
}


static struct desc *find_desc (char *name, int fork_flag)
{
  struct desc *p;
  char *base;

  if (fork_flag)
    for (p = desc_list; p; p = p->next)
      if (p->dn == DN_FORK)
        return p;
  for (p = desc_list; p; p = p->next)
    if (p->dn == DN_ABS && _fncmp (p->name, name) == 0)
      return p;
  base = _getname (name);
  for (p = desc_list; p; p = p->next)
    if (p->dn == DN_NAME && _fncmp (p->name, base) == 0)
      return p;
  for (p = desc_list; p; p = p->next)
    if (p->dn == DN_ALL)
      return p;
  return NULL;
}


/* ARGSUSED */
static void
descendant_command (args, from_tty)
     char *args;
     int from_tty;
{
  printf_filtered ("\"descendant\" must be followed by the name of a subcommand.\n");
  help_list (descendantlist, "descendant ", all_commands, gdb_stdout);
}


static char *desc_mode_text[] =
{
  "fs", "window", "cont"
};

#define DESC_MODE_COUNT (sizeof (desc_mode_text) / sizeof (desc_mode_text[0]))

static void
descendants_info (args, from_tty)
    char *args;
    int from_tty;
{
  struct desc *p;

  for (p = desc_list; p; p = p->next)
    printf_filtered ("%d %s %s%s %s\n",
                     p->num, p->name, desc_mode_text[p->mode],
                     p->init ? " init" : "",
                     p->options);
}


static char *split1 (char **arg)
{
  char *arg1;
  char *sep;

  sep = strpbrk (*arg, " \t");
  if (sep != NULL)
    {
      arg1 = savestring (*arg, sep - *arg);
      *arg = sep + strspn (sep, " \t");
    }
  else
    {
      arg1 = strsave (*arg);
      *arg = "";
    }
  return arg1;
}


static void
descendant_add_command (args, from_tty)
    char *args;
    int from_tty;
{
  struct desc *p, **pp;
  char fullpath[260];
  char *arg1, *name;
  char init;
  int i, m;
  size_t len;
  enum desc_debug mode;
  enum desc_name dn;

  if (!args)
    error ("Program name and debugging mode expected");

  arg1 = split1 (&args);

  if (arg1 != _getname (arg1))
    {
      dn = DN_ABS;
      if (_abspath (fullpath, arg1, sizeof (fullpath)) != 0)
        {
          free (arg1);
          error ("Invalid program name");
        }
      name = strsave (fullpath);
      free (arg1);
    }
  else
    {
      name = arg1;
      if (strcmp (name, "*") == 0)
        dn = DN_ALL;
      else if (stricmp (name, "fork") == 0)
        dn = DN_FORK;
      else
        dn = DN_NAME;
    }

  arg1 = split1 (&args);

  mode = (enum desc_debug)-1; len = strlen (arg1); m = 0;
  for (i = 0; i < DESC_MODE_COUNT; ++i)
    if (strnicmp (arg1, desc_mode_text[i], len) == 0)
      {
        mode = (enum desc_debug)i;
        ++m;
      }
  free (arg1);
  if (m != 1)
    {
      free (name);
      error ("Invalid descendant debugging mode specified");
    }

  init = 0;
  if (*args != 0 && *args != '-')
    {
      arg1 = split1 (&args);
      len = strlen (arg1);
      if (strnicmp (arg1, "init", len) == 0 && dn == DN_FORK
          && mode != DD_CONT)
        init = 1;
      else
        {
          free (name); free (arg1);
          error ("Invalid descendant debugging mode specified");
        }
    }

  for (p = desc_list; p; p = p->next)
    if (_fncmp (p->name, name) == 0)
      {
        free (name);
        if (p->mode == mode && p->init == init
            && strcmp (p->options, args) == 0)
          error ("Descendant already configured");
        p->mode = mode;
        p->init = init;
        free (p->options);
        p->options = strsave (args);
        printf_filtered ("Debugging mode of descendant %s updated.\n",
                         p->name);
        return;
      }
  p = xmalloc (sizeof (*p));
  p->name = name;
  p->num = ++desc_num;
  p->dn = dn;
  p->mode = mode;
  p->init = init;
  p->options = strsave (args);
  p->next = NULL;
  for (pp = &desc_list; *pp; pp = &(*pp)->next)
    ;
  *pp = p;
  printf_filtered ("Debugging of descendant %s enabled (number %d).\n",
                   p->name, p->num);
}


static void
descendant_clear_command (arg, from_tty)
    char *arg;
    int from_tty;
{
  struct desc *p, **pp;
  int num;

  if (!arg)
    error ("Descendant number expected");
  num = atoi (arg);
  if (num <= 0)
    error ("Invalid descendant number");
  for (pp = &desc_list; *pp; pp = &(*pp)->next)
    {
      p = *pp;
      if (p->num == num)
        {
          printf_filtered ("Debugging of descendant %s disabled.\n",
                           p->name);
          *pp = p->next;
          free (p->name);
          free (p->options);
          free (p);
          return;
        }
    }
  error ("Invalid descendant number.  Use \"info descendants\" to get a list.");
}


static char **
descendant_add_completer (text, prefix)
     char *text;
     char *prefix;
{
  char *p, **result;
  int word, i, n;
  size_t len;

  p = text; word = 0;
  while (p != prefix)
    {
      if (*p == ' ' || *p == '\t')
        {
          ++word;
          while (p != prefix && (*p == ' ' || *p == '\t'))
            ++p;
        }
      else
        ++p;
    }
  if (word == 0)
    return filename_completer (text, prefix);
  if (word != 1)
    return NULL;

  result = xmalloc ((DESC_MODE_COUNT + 1) * sizeof (*result));
  n = 0; len = strlen (prefix);

  for (i = 0; i < DESC_MODE_COUNT; ++i)
    if (strnicmp (desc_mode_text[i], prefix, len) == 0)
      result[n++] = strsave (desc_mode_text[i]);

  result[n] = NULL;
  return result;
}


void
_initialize_emx_native ()
{
  struct cmd_list_element *c;

  add_show_from_set
    (add_set_cmd ("close", class_run, var_boolean,
                  (char *)&close_sessions,
                  "Set whether to automatically close the next child session.",
                  &setlist),
      &showlist);

  add_show_from_set
    (add_set_cmd ("show-dlls", class_run, var_boolean,
                  (char *)&show_dlls,
                  "Set whether to show DLLs as they are loaded and freed.",
                  &setlist),
      &showlist);

  add_show_from_set
    (add_set_cmd ("switch", class_run, var_boolean,
                  (char *)&switch_sessions,
                  "Set whether to switch to the child session when running "
                  "the inferior.",
                  &setlist),
     &showlist);

  add_info ("dll-break", dll_break_info,
	    "List all stop-on-load breakpoints set with `dll-break'.");

  c = add_cmd ("dll-break", class_breakpoint, dll_break_command,
               "Set a stop-on-load breakpoint on a DLL.", &cmdlist);
  c->completer = filename_completer;

  add_com ("dll-clear", class_breakpoint, dll_clear_command,
	   "Clear stop-on-load breakpoint NUM.");

  add_prefix_cmd ("descendant", class_run, descendant_command,
                  "Debug descendant processes.", &descendantlist,
                  "descendant ", 0, &cmdlist);

  c = add_cmd ("add", class_run, descendant_add_command,
               "Arrange for debugging of the descendant process NAME.\n\
If NAME is `*', all descendant processes will be debugged.\n\
If NAME is `fork', child processes created with fork() will be debugged.\n\
The second argument selects the debugging mode for the descendant:\n\
  window  run GDB in a windowed session,\n\
  fs      run GDB in a full-screen session,\n\
  cont    do not run GDB automatically, prepare for attaching.\n\
If NAME is `fork', you can follow the second argument with `init'\n\
to debug the initialization code of the child process.\n\
Any following arguments are passed as command line options to GDB.",
           &descendantlist);
  c->completer = descendant_add_completer;

  add_cmd ("clear", class_run, descendant_clear_command,
           "Disable debugging of descendant NUMBER.",
           &descendantlist);

  add_info ("descendants", descendants_info,
	    "List information about descendants to be debugged.");

  _uflags (_UF_PTRACE_MODEL, _UF_PTRACE_MULTITHREAD);
}
