/* GDB stub for Itanium OpenVMS
   Copyright (C) 2012-2022 Free Software Foundation, Inc.

   Contributed by Tristan Gingold, AdaCore.

   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 3 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, see <http://www.gnu.org/licenses/>.  */

/* On VMS, the debugger (in our case the stub) is loaded in the process and
   executed (via SYS$IMGSTA) before the main entry point of the executable.
   In UNIX parlance, this is like using LD_PRELOAD and debug via installing
   SIGTRAP, SIGSEGV... handlers.

   This is currently a partial implementation.  In particular, modifying
   registers is currently not implemented, as well as inferior procedure
   calls.

   This is written in very low-level C, in order not to use the C runtime,
   because it may have weird consequences on the program being debugged.
*/

#if __INITIAL_POINTER_SIZE != 64
#error "Must be compiled with 64 bit pointers"
#endif

#define __NEW_STARLET 1
#include <descrip.h>
#include <iledef.h>
#include <efndef.h>
#include <in.h>
#include <inet.h>
#include <iodef.h>
#include <ssdef.h>
#include <starlet.h>
#include <stsdef.h>
#include <tcpip$inetdef.h>

#include <lib$routines.h>
#include <ots$routines.h>
#include <str$routines.h>
#include <libdef.h>
#include <clidef.h>
#include <iosbdef.h>
#include <dvidef.h>
#include <lnmdef.h>
#include <builtins.h>
#include <prtdef.h>
#include <psldef.h>
#include <chfdef.h>

#include <lib_c/imcbdef.h>
#include <lib_c/ldrimgdef.h>
#include <lib_c/intstkdef.h>
#include <lib_c/psrdef.h>
#include <lib_c/ifddef.h>
#include <lib_c/eihddef.h>

#include <stdarg.h>
#include <pthread_debug.h>

#define VMS_PAGE_SIZE 0x2000
#define VMS_PAGE_MASK (VMS_PAGE_SIZE - 1)

/* Declared in lib$ots.  */
extern void ots$fill (void *addr, size_t len, unsigned char b);
extern void ots$move (void *dst, size_t len, const void *src);
extern int ots$strcmp_eql (const void *str1, size_t str1len,
			   const void *str2, size_t str2len);

/* Stub port number.  */
static unsigned int serv_port = 1234;

/* DBGEXT structure.  Not declared in any header.  */
struct dbgext_control_block
{
  unsigned short dbgext$w_function_code;
#define DBGEXT$K_NEXT_TASK	      3
#define DBGEXT$K_STOP_ALL_OTHER_TASKS 31
#define DBGEXT$K_GET_REGS 33
  unsigned short dbgext$w_facility_id;
#define CMA$_FACILITY 64
  unsigned int dbgext$l_status;
  unsigned int dbgext$l_flags;
  unsigned int dbgext$l_print_routine;
  unsigned int dbgext$l_evnt_code;
  unsigned int dbgext$l_evnt_name;
  unsigned int dbgext$l_evnt_entry;
  unsigned int dbgext$l_task_value;
  unsigned int dbgext$l_task_number;
  unsigned int dbgext$l_ada_flags;
  unsigned int dbgext$l_stop_value;
#define dbgext$l_priority   dbgext$l_stop_value;
#define dbgext$l_symb_addr  dbgext$l_stop_value;
#define dbgext$l_time_slice dbgext$l_stop_value;
  unsigned int dbgext$l_active_registers;
};

#pragma pointer_size save
#pragma pointer_size 32

/* Pthread handler.  */
static int (*dbgext_func) (struct dbgext_control_block *blk);

#pragma pointer_size restore

/* Set to 1 if thread-aware.  */
static int has_threads;

/* Current thread.  */
static pthread_t selected_thread;
static pthreadDebugId_t selected_id;

/* Internal debugging flags.  */
struct debug_flag
{
  /* Name of the flag (as a string descriptor).  */
  const struct dsc$descriptor_s name;
  /* Value.  */
  int val;
};

/* Macro to define a debugging flag.  */
#define DEBUG_FLAG_ENTRY(str) \
  { { sizeof (str) - 1, DSC$K_DTYPE_T, DSC$K_CLASS_S, str }, 0}

static struct debug_flag debug_flags[] =
{
  /* Disp packets exchanged with gdb.  */
  DEBUG_FLAG_ENTRY("packets"),
#define trace_pkt (debug_flags[0].val)
  /* Display entry point informations.  */
  DEBUG_FLAG_ENTRY("entry"),
#define trace_entry (debug_flags[1].val)
  /* Be verbose about exceptions.  */
  DEBUG_FLAG_ENTRY("excp"),
#define trace_excp (debug_flags[2].val)
  /* Be verbose about unwinding.  */
  DEBUG_FLAG_ENTRY("unwind"),
#define trace_unwind (debug_flags[3].val)
  /* Display image at startup.  */
  DEBUG_FLAG_ENTRY("images"),
#define trace_images (debug_flags[4].val)
  /* Display pthread_debug info.  */
  DEBUG_FLAG_ENTRY("pthreaddbg")
#define trace_pthreaddbg (debug_flags[5].val)
};

#define NBR_DEBUG_FLAGS (sizeof (debug_flags) / sizeof (debug_flags[0]))

/* Connect inet device I/O channel.  */
static unsigned short conn_channel;

/* Widely used hex digit to ascii.  */
static const char hex[] = "0123456789abcdef";

/* Socket characteristics.  Apparently, there are no declaration for it in
   standard headers.  */
struct sockchar
{
  unsigned short prot;
  unsigned char type;
  unsigned char af;
};

/* Chain of images loaded.  */
extern IMCB* ctl$gl_imglstptr;

/* IA64 integer register representation.  */
union ia64_ireg
{
  unsigned __int64 v;
  unsigned char b[8];
};

/* IA64 register numbers, as defined by ia64-tdep.h.  */
#define IA64_GR0_REGNUM		0
#define IA64_GR32_REGNUM	(IA64_GR0_REGNUM + 32)

/* Floating point registers; 128 82-bit wide registers.  */
#define IA64_FR0_REGNUM		128

/* Predicate registers; There are 64 of these one bit registers.  It'd
   be more convenient (implementation-wise) to use a single 64 bit
   word with all of these register in them.  Note that there's also a
   IA64_PR_REGNUM below which contains all the bits and is used for
   communicating the actual values to the target.  */
#define IA64_PR0_REGNUM		256

/* Branch registers: 8 64-bit registers for holding branch targets.  */
#define IA64_BR0_REGNUM		320

/* Virtual frame pointer; this matches IA64_FRAME_POINTER_REGNUM in
   gcc/config/ia64/ia64.h.  */
#define IA64_VFP_REGNUM		328

/* Virtual return address pointer; this matches
   IA64_RETURN_ADDRESS_POINTER_REGNUM in gcc/config/ia64/ia64.h.  */
#define IA64_VRAP_REGNUM	329

/* Predicate registers: There are 64 of these 1-bit registers.  We
   define a single register which is used to communicate these values
   to/from the target.  We will somehow contrive to make it appear
   that IA64_PR0_REGNUM thru IA64_PR63_REGNUM hold the actual values.  */
#define IA64_PR_REGNUM		330

/* Instruction pointer: 64 bits wide.  */
#define IA64_IP_REGNUM		331

/* Process Status Register.  */
#define IA64_PSR_REGNUM		332

/* Current Frame Marker (raw form may be the cr.ifs).  */
#define IA64_CFM_REGNUM		333

/* Application registers; 128 64-bit wide registers possible, but some
   of them are reserved.  */
#define IA64_AR0_REGNUM		334
#define IA64_KR0_REGNUM		(IA64_AR0_REGNUM + 0)
#define IA64_KR7_REGNUM		(IA64_KR0_REGNUM + 7)

#define IA64_RSC_REGNUM		(IA64_AR0_REGNUM + 16)
#define IA64_BSP_REGNUM		(IA64_AR0_REGNUM + 17)
#define IA64_BSPSTORE_REGNUM	(IA64_AR0_REGNUM + 18)
#define IA64_RNAT_REGNUM	(IA64_AR0_REGNUM + 19)
#define IA64_FCR_REGNUM		(IA64_AR0_REGNUM + 21)
#define IA64_EFLAG_REGNUM	(IA64_AR0_REGNUM + 24)
#define IA64_CSD_REGNUM		(IA64_AR0_REGNUM + 25)
#define IA64_SSD_REGNUM		(IA64_AR0_REGNUM + 26)
#define IA64_CFLG_REGNUM	(IA64_AR0_REGNUM + 27)
#define IA64_FSR_REGNUM		(IA64_AR0_REGNUM + 28)
#define IA64_FIR_REGNUM		(IA64_AR0_REGNUM + 29)
#define IA64_FDR_REGNUM		(IA64_AR0_REGNUM + 30)
#define IA64_CCV_REGNUM		(IA64_AR0_REGNUM + 32)
#define IA64_UNAT_REGNUM	(IA64_AR0_REGNUM + 36)
#define IA64_FPSR_REGNUM	(IA64_AR0_REGNUM + 40)
#define IA64_ITC_REGNUM		(IA64_AR0_REGNUM + 44)
#define IA64_PFS_REGNUM		(IA64_AR0_REGNUM + 64)
#define IA64_LC_REGNUM		(IA64_AR0_REGNUM + 65)
#define IA64_EC_REGNUM		(IA64_AR0_REGNUM + 66)

/* NAT (Not A Thing) Bits for the general registers; there are 128 of
   these.  */
#define IA64_NAT0_REGNUM	462

/* Process registers when a condition is caught.  */
struct ia64_all_regs
{
  union ia64_ireg gr[32];
  union ia64_ireg br[8];
  union ia64_ireg ip;
  union ia64_ireg psr;
  union ia64_ireg bsp;
  union ia64_ireg cfm;
  union ia64_ireg pfs;
  union ia64_ireg pr;
};

static struct ia64_all_regs excp_regs;
static struct ia64_all_regs sel_regs;
static pthread_t sel_regs_pthread;

/* IO channel for the terminal.  */
static unsigned short term_chan;

/* Output buffer and length.  */
static char term_buf[128];
static int term_buf_len;

/* Buffer for communication with gdb.  */
static unsigned char gdb_buf[sizeof (struct ia64_all_regs) * 2 + 64];
static unsigned int gdb_blen;

/* Previous primary handler.  */
static void *prevhnd;

/* Entry point address and bundle.  */
static unsigned __int64 entry_pc;
static unsigned char entry_saved[16];

/* Write on the terminal.  */

static void
term_raw_write (const char *str, unsigned int len)
{
  unsigned short status;
  struct _iosb iosb;

  status = sys$qiow (EFN$C_ENF,           /* Event flag.  */
		     term_chan,           /* I/O channel.  */
		     IO$_WRITEVBLK,       /* I/O function code.  */
		     &iosb,               /* I/O status block.  */
		     0,                   /* Ast service routine.  */
		     0,                   /* Ast parameter.  */
		     (char *)str,         /* P1 - buffer address.  */
		     len,                 /* P2 - buffer length.  */
		     0, 0, 0, 0);

  if (status & STS$M_SUCCESS)
    status = iosb.iosb$w_status;
  if (!(status & STS$M_SUCCESS))
    LIB$SIGNAL (status);
}

/* Flush ther term buffer.  */

static void
term_flush (void)
{
  if (term_buf_len != 0)
    {
      term_raw_write (term_buf, term_buf_len);
      term_buf_len = 0;
    }
}

/* Write a single character, without translation.  */

static void
term_raw_putchar (char c)
{
  if (term_buf_len == sizeof (term_buf))
    term_flush ();
  term_buf[term_buf_len++] = c;
}

/* Write character C.  Translate '\n' to '\n\r'.  */

static void
term_putc (char c)
{
  if (c < 32)
    switch (c)
      {
      case '\r':
      case '\n':
	break;
      default:
	c = '.';
	break;
      }
  term_raw_putchar (c);
  if (c == '\n')
    {
      term_raw_putchar ('\r');
      term_flush ();
    }
}

/* Write a C string.  */

static void
term_puts (const char *str)
{
  while (*str)
    term_putc (*str++);
}

/* Write LEN bytes from STR.  */

static void
term_write (const char *str, unsigned int len)
{
  for (; len > 0; len--)
    term_putc (*str++);
}

/* Write using FAO formatting.  */

static void
term_fao (const char *str, unsigned int str_len, ...)
{
  int cnt;
  va_list vargs;
  int i;
  __int64 *args;
  int status;
  struct dsc$descriptor_s dstr =
    { str_len, DSC$K_DTYPE_T, DSC$K_CLASS_S, (__char_ptr32)str };
  char buf[128];
  $DESCRIPTOR (buf_desc, buf);

  va_start (vargs, str_len);
  va_count (cnt);
  args = (__int64 *) __ALLOCA (cnt * sizeof (__int64));
  cnt -= 2;
  for (i = 0; i < cnt; i++)
    args[i] = va_arg (vargs, __int64);

  status = sys$faol_64 (&dstr, &buf_desc.dsc$w_length, &buf_desc, args);
  if (status & 1)
    {
      /* FAO !/ already insert a line feed.  */
      for (i = 0; i < buf_desc.dsc$w_length; i++)
	{
	  term_raw_putchar (buf[i]);
	  if (buf[i] == '\n')
	    term_flush ();
	}
    }
      
  va_end (vargs);
}

#define TERM_FAO(STR, ...) term_fao (STR, sizeof (STR) - 1, __VA_ARGS__)

/* New line.  */

static void
term_putnl (void)
{
  term_putc ('\n');
}

/* Initialize terminal.  */

static void
term_init (void)
{
  unsigned int status,i;
  unsigned short len;
  char resstring[LNM$C_NAMLENGTH];
  static const $DESCRIPTOR (tabdesc, "LNM$FILE_DEV");
  static const $DESCRIPTOR (logdesc, "SYS$OUTPUT");
  $DESCRIPTOR (term_desc, resstring);
  ILE3 item_lst[2];

  item_lst[0].ile3$w_length = LNM$C_NAMLENGTH;
  item_lst[0].ile3$w_code = LNM$_STRING;
  item_lst[0].ile3$ps_bufaddr = resstring;
  item_lst[0].ile3$ps_retlen_addr = &len;
  item_lst[1].ile3$w_length = 0;
  item_lst[1].ile3$w_code = 0;

  /* Translate the logical name.  */
  status = SYS$TRNLNM (0,          	  /* Attr of the logical name.  */
		       (void *) &tabdesc, /* Logical name table.  */
		       (void *) &logdesc, /* Logical name.  */
		       0,          /* Access mode.  */
		       item_lst);  /* Item list.  */
  if (!(status & STS$M_SUCCESS))
    LIB$SIGNAL (status);

  term_desc.dsc$w_length = len;

  /* Examine 4-byte header.  Skip escape sequence.  */
  if (resstring[0] == 0x1B)
    {
      term_desc.dsc$w_length -= 4;
      term_desc.dsc$a_pointer += 4;
    }

  /* Assign a channel.  */
  status = sys$assign (&term_desc,   /* Device name.  */
		       &term_chan,   /* I/O channel.  */
		       0,            /* Access mode.  */
		       0);
  if (!(status & STS$M_SUCCESS))
    LIB$SIGNAL (status);
}

/* Convert from native endianness to network endianness (and vice-versa).  */

static unsigned int
wordswap (unsigned int v)
{
  return ((v & 0xff) << 8) | ((v >> 8) & 0xff);
}

/* Initialize the socket connection, and wait for a client.  */

static void
sock_init (void)
{
  struct _iosb iosb;
  unsigned int status;

  /* Listen channel and characteristics.  */
  unsigned short listen_channel;
  struct sockchar listen_sockchar;

  /* Client address.  */
  unsigned short cli_addrlen;
  struct sockaddr_in cli_addr;
  ILE3 cli_itemlst;

  /* Our address.  */
  struct sockaddr_in serv_addr;
  ILE2 serv_itemlst;

  /* Reuseaddr option value (on).  */
  int optval = 1;
  ILE2 sockopt_itemlst;
  ILE2 reuseaddr_itemlst;

  /* TCP/IP network pseudodevice.  */
  static const $DESCRIPTOR (inet_device, "TCPIP$DEVICE:");

  /* Initialize socket characteristics.  */
  listen_sockchar.prot = TCPIP$C_TCP;
  listen_sockchar.type = TCPIP$C_STREAM;
  listen_sockchar.af   = TCPIP$C_AF_INET;

  /* Assign I/O channels to network device.  */
  status = sys$assign ((void *) &inet_device, &listen_channel, 0, 0);
  if (status & STS$M_SUCCESS)
    status = sys$assign ((void *) &inet_device, &conn_channel, 0, 0);
  if (!(status & STS$M_SUCCESS))
    {
      term_puts ("Failed to assign I/O channel(s)\n");
      LIB$SIGNAL (status);
    }

  /* Create a listen socket.  */
  status = sys$qiow (EFN$C_ENF,           /* Event flag.  */
		     listen_channel,      /* I/O channel.  */
		     IO$_SETMODE,         /* I/O function code.  */
		     &iosb,               /* I/O status block.  */
		     0,                   /* Ast service routine.  */
		     0,                   /* Ast parameter.  */
		     &listen_sockchar,    /* P1 - socket characteristics.  */
		     0, 0, 0, 0, 0);
  if (status & STS$M_SUCCESS)
    status = iosb.iosb$w_status;
  if (!(status & STS$M_SUCCESS))
    {
      term_puts ("Failed to create socket\n");
      LIB$SIGNAL (status);
    }

  /* Set reuse address option.  */
  /* Initialize reuseaddr's item-list element.  */
  reuseaddr_itemlst.ile2$w_length   = sizeof (optval);
  reuseaddr_itemlst.ile2$w_code     = TCPIP$C_REUSEADDR;
  reuseaddr_itemlst.ile2$ps_bufaddr = &optval;

  /* Initialize setsockopt's item-list descriptor.  */
  sockopt_itemlst.ile2$w_length   = sizeof (reuseaddr_itemlst);
  sockopt_itemlst.ile2$w_code     = TCPIP$C_SOCKOPT;
  sockopt_itemlst.ile2$ps_bufaddr = &reuseaddr_itemlst;

  status = sys$qiow (EFN$C_ENF,       /* Event flag.  */
		     listen_channel,  /* I/O channel.  */
		     IO$_SETMODE,     /* I/O function code.  */
		     &iosb,           /* I/O status block.  */
		     0,               /* Ast service routine.  */
		     0,               /* Ast parameter.  */
		     0,               /* P1.  */
		     0,               /* P2.  */
		     0,               /* P3.  */
		     0,               /* P4.  */
		     (__int64) &sockopt_itemlst, /* P5 - socket options.  */
		     0);
  if (status & STS$M_SUCCESS)
    status = iosb.iosb$w_status;
  if (!(status & STS$M_SUCCESS))
    {
      term_puts ("Failed to set socket option\n");
      LIB$SIGNAL (status);
    }

  /* Bind server's ip address and port number to listen socket.  */
  /* Initialize server's socket address structure.  */
  ots$fill (&serv_addr, sizeof (serv_addr), 0);
  serv_addr.sin_family = TCPIP$C_AF_INET;
  serv_addr.sin_port = wordswap (serv_port);
  serv_addr.sin_addr.s_addr = TCPIP$C_INADDR_ANY;

  /* Initialize server's item-list descriptor.  */
  serv_itemlst.ile2$w_length   = sizeof (serv_addr);
  serv_itemlst.ile2$w_code     = TCPIP$C_SOCK_NAME;
  serv_itemlst.ile2$ps_bufaddr = &serv_addr;

  status = sys$qiow (EFN$C_ENF,           /* Event flag.  */
		     listen_channel,      /* I/O channel.  */
		     IO$_SETMODE,         /* I/O function code.  */
		     &iosb,               /* I/O status block.  */
		     0,                   /* Ast service routine.  */
		     0,                   /* Ast parameter.  */
		     0,                   /* P1.  */
		     0,                   /* P2.  */
		     (__int64) &serv_itemlst, /* P3 - local socket name.  */
		     0, 0, 0);
  if (status & STS$M_SUCCESS)
    status = iosb.iosb$w_status;
  if (!(status & STS$M_SUCCESS))
    {
      term_puts ("Failed to bind socket\n");
      LIB$SIGNAL (status);
    }

  /* Set socket as a listen socket.  */
  status = sys$qiow (EFN$C_ENF,           /* Event flag.  */
		     listen_channel,      /* I/O channel.  */
		     IO$_SETMODE,         /* I/O function code.  */
		     &iosb,               /* I/O status block.  */
		     0,                   /* Ast service routine.  */
		     0,                   /* Ast parameter.  */
		     0,                   /* P1.  */
		     0,                   /* P2.  */
		     0,                   /* P3.  */
		     1,                   /* P4 - connection backlog.  */
		     0, 0);
  if (status & STS$M_SUCCESS)
    status = iosb.iosb$w_status;
  if (!(status & STS$M_SUCCESS))
    {
      term_puts ("Failed to set socket passive\n");
      LIB$SIGNAL (status);
    }

  /* Accept connection from a client.  */
  TERM_FAO ("Waiting for a client connection on port: !ZW!/",
	    wordswap (serv_addr.sin_port));

  status = sys$qiow (EFN$C_ENF,              /* Event flag.  */
		     listen_channel,         /* I/O channel.  */
		     IO$_ACCESS|IO$M_ACCEPT, /* I/O function code.  */
		     &iosb,                  /* I/O status block.  */
		     0,                      /* Ast service routine.  */
		     0,                      /* Ast parameter.  */
		     0,                      /* P1.  */
		     0,                      /* P2.  */
		     0,                      /* P3.  */
		     (__int64) &conn_channel, /* P4 - I/O channel for conn.  */
		     0, 0);

  if (status & STS$M_SUCCESS)
    status = iosb.iosb$w_status;
  if (!(status & STS$M_SUCCESS))
    {
      term_puts ("Failed to accept client connection\n");
      LIB$SIGNAL (status);
    }

  /* Log client connection request.  */
  cli_itemlst.ile3$w_length = sizeof (cli_addr);
  cli_itemlst.ile3$w_code = TCPIP$C_SOCK_NAME;
  cli_itemlst.ile3$ps_bufaddr = &cli_addr;
  cli_itemlst.ile3$ps_retlen_addr = &cli_addrlen;
  ots$fill (&cli_addr, sizeof(cli_addr), 0);
  status = sys$qiow (EFN$C_ENF,           /* Event flag.  */
		     conn_channel,        /* I/O channel.  */
		     IO$_SENSEMODE,       /* I/O function code.  */
		     &iosb,               /* I/O status block.  */
		     0,                   /* Ast service routine.  */
		     0,                   /* Ast parameter.  */
		     0,                   /* P1.  */
		     0,                   /* P2.  */
		     0,                   /* P3.  */
		     (__int64) &cli_itemlst,  /* P4 - peer socket name.  */
		     0, 0);
  if (status & STS$M_SUCCESS)
    status = iosb.iosb$w_status;
  if (!(status & STS$M_SUCCESS))
    {
      term_puts ("Failed to get client name\n");
      LIB$SIGNAL (status);
    }

  TERM_FAO ("Accepted connection from host: !UB.!UB,!UB.!UB, port: !UW!/",
	    (cli_addr.sin_addr.s_addr >> 0) & 0xff,
	    (cli_addr.sin_addr.s_addr >> 8) & 0xff,
	    (cli_addr.sin_addr.s_addr >> 16) & 0xff,
	    (cli_addr.sin_addr.s_addr >> 24) & 0xff,
	    wordswap (cli_addr.sin_port));
}

/* Close the socket.  */

static void
sock_close (void)
{
  struct _iosb iosb;
  unsigned int status;

  /* Close socket.  */
  status = sys$qiow (EFN$C_ENF,           /* Event flag.  */
		     conn_channel,        /* I/O channel.  */
		     IO$_DEACCESS,        /* I/O function code.  */
		     &iosb,               /* I/O status block.  */
		     0,                   /* Ast service routine.  */
		     0,                   /* Ast parameter.  */
		     0, 0, 0, 0, 0, 0);

  if (status & STS$M_SUCCESS)
    status = iosb.iosb$w_status;
  if (!(status & STS$M_SUCCESS))
    {
      term_puts ("Failed to close socket\n");
      LIB$SIGNAL (status);
    }

  /* Deassign I/O channel to network device.  */
  status = sys$dassgn (conn_channel);

  if (!(status & STS$M_SUCCESS))
    {
      term_puts ("Failed to deassign I/O channel\n");
      LIB$SIGNAL (status);
    }
}

/* Mark a page as R/W.  Return old rights.  */

static unsigned int
page_set_rw (unsigned __int64 startva, unsigned __int64 len,
	     unsigned int *oldprot)
{
  unsigned int status;
  unsigned __int64 retva;
  unsigned __int64 retlen;

  status = SYS$SETPRT_64 ((void *)startva, len, PSL$C_USER, PRT$C_UW,
			  (void *)&retva, &retlen, oldprot);
  return status;
}

/* Restore page rights.  */

static void
page_restore_rw (unsigned __int64 startva, unsigned __int64 len,
		unsigned int prot)
{
  unsigned int status;
  unsigned __int64 retva;
  unsigned __int64 retlen;
  unsigned int oldprot;

  status = SYS$SETPRT_64 ((void *)startva, len, PSL$C_USER, prot,
			  (void *)&retva, &retlen, &oldprot);
  if (!(status & STS$M_SUCCESS))
    LIB$SIGNAL (status);
}

/* Get the TEB (thread environment block).  */

static pthread_t
get_teb (void)
{
  return (pthread_t)__getReg (_IA64_REG_TP);
}

/* Enable thread scheduling if VAL is true.  */

static unsigned int
set_thread_scheduling (int val)
{
  struct dbgext_control_block blk;
  unsigned int status;

  if (!dbgext_func)
    return 0;

  blk.dbgext$w_function_code = DBGEXT$K_STOP_ALL_OTHER_TASKS;
  blk.dbgext$w_facility_id = CMA$_FACILITY;
  blk.dbgext$l_stop_value = val;

  status = dbgext_func (&blk);
  if (!(status & STS$M_SUCCESS))
    {
      TERM_FAO ("set_thread_scheduling error, val=!SL, status=!XL!/",
		val, blk.dbgext$l_status);
      lib$signal (status);
    }

  return blk.dbgext$l_stop_value;
}

/* Get next thread (after THR).  Start with 0.  */

static unsigned int
thread_next (unsigned int thr)
{
  struct dbgext_control_block blk;
  unsigned int status;

  if (!dbgext_func)
    return 0;

  blk.dbgext$w_function_code = DBGEXT$K_NEXT_TASK;
  blk.dbgext$w_facility_id = CMA$_FACILITY;
  blk.dbgext$l_ada_flags = 0;
  blk.dbgext$l_task_value = thr;

  status = dbgext_func (&blk);
  if (!(status & STS$M_SUCCESS))
    lib$signal (status);

  return blk.dbgext$l_task_value;
}

/* Pthread Debug callbacks.  */

static int
read_callback (pthreadDebugClient_t context,
	       pthreadDebugTargetAddr_t addr,
	       pthreadDebugAddr_t buf,
	       size_t size)
{
  if (trace_pthreaddbg)
    TERM_FAO ("read_callback (!XH, !XH, !SL)!/", addr, buf, size);
  ots$move (buf, size, addr);
  return 0;
}

static int
write_callback (pthreadDebugClient_t context,
		pthreadDebugTargetAddr_t addr,
		pthreadDebugLongConstAddr_t buf,
		size_t size)
{
  if (trace_pthreaddbg)
    TERM_FAO ("write_callback (!XH, !XH, !SL)!/", addr, buf, size);
  ots$move (addr, size, buf);
  return 0;
}

static int
suspend_callback (pthreadDebugClient_t context)
{
  /* Always suspended.  */
  return 0;
}

static int
resume_callback (pthreadDebugClient_t context)
{
  /* So no need to resume.  */
  return 0;
}

static int
kthdinfo_callback (pthreadDebugClient_t context,
		   pthreadDebugKId_t kid,
		   pthreadDebugKThreadInfo_p thread_info)
{
  if (trace_pthreaddbg)
    term_puts ("kthinfo_callback");
  return ENOSYS;
}

static int
hold_callback (pthreadDebugClient_t context,
	       pthreadDebugKId_t kid)
{
  if (trace_pthreaddbg)
    term_puts ("hold_callback");
  return ENOSYS;
}

static int
unhold_callback (pthreadDebugClient_t context,
		 pthreadDebugKId_t kid)
{
  if (trace_pthreaddbg)
    term_puts ("unhold_callback");
  return ENOSYS;
}

static int
getfreg_callback (pthreadDebugClient_t context,
		  pthreadDebugFregs_t *reg,
		  pthreadDebugKId_t kid)
{
  if (trace_pthreaddbg)
    term_puts ("getfreg_callback");
  return ENOSYS;
}

static int
setfreg_callback (pthreadDebugClient_t context,
		  const pthreadDebugFregs_t *reg,
		  pthreadDebugKId_t kid)
{
  if (trace_pthreaddbg)
    term_puts ("setfreg_callback");
  return ENOSYS;
}

static int
getreg_callback (pthreadDebugClient_t context,
		 pthreadDebugRegs_t *reg,
		 pthreadDebugKId_t kid)
{
  if (trace_pthreaddbg)
    term_puts ("getreg_callback");
  return ENOSYS;
}

static int
setreg_callback (pthreadDebugClient_t context,
		 const pthreadDebugRegs_t *reg,
		 pthreadDebugKId_t kid)
{
  if (trace_pthreaddbg)
    term_puts ("setreg_callback");
  return ENOSYS;
}

static int
output_callback (pthreadDebugClient_t context, 
		 pthreadDebugConstString_t line)
{
  term_puts (line);
  term_putnl ();
  return 0;
}

static int
error_callback (pthreadDebugClient_t context, 
		 pthreadDebugConstString_t line)
{
  term_puts (line);
  term_putnl ();
  return 0;
}

static pthreadDebugAddr_t
malloc_callback (pthreadDebugClient_t caller_context, size_t size)
{
  unsigned int status;
  unsigned int res;
  int len;

  len = size + 16;
  status = lib$get_vm (&len, &res, 0);
  if (!(status & STS$M_SUCCESS))
    LIB$SIGNAL (status);
  if (trace_pthreaddbg)
    TERM_FAO ("malloc_callback (!UL) -> !XA!/", size, res);
  *(unsigned int *)res = len;
  return (char *)res + 16;
}

static void
free_callback (pthreadDebugClient_t caller_context, pthreadDebugAddr_t address)
{
  unsigned int status;
  unsigned int res;
  int len;

  res = (unsigned int)address - 16;
  len = *(unsigned int *)res;
  if (trace_pthreaddbg)
    TERM_FAO ("free_callback (!XA)!/", address);
  status = lib$free_vm (&len, &res, 0);
  if (!(status & STS$M_SUCCESS))
    LIB$SIGNAL (status);
}

static int
speckthd_callback (pthreadDebugClient_t caller_context,
		   pthreadDebugSpecialType_t type,
		   pthreadDebugKId_t *kernel_tid)
{
  return ENOTSUP;
}

static pthreadDebugCallbacks_t pthread_debug_callbacks = {
  PTHREAD_DEBUG_VERSION,
  read_callback,
  write_callback,
  suspend_callback,
  resume_callback,
  kthdinfo_callback,
  hold_callback,
  unhold_callback,
  getfreg_callback,
  setfreg_callback,
  getreg_callback,
  setreg_callback,
  output_callback,
  error_callback,
  malloc_callback,
  free_callback,
  speckthd_callback
};

/* Name of the pthread shared library.  */
static const $DESCRIPTOR (pthread_rtl_desc, "PTHREAD$RTL");

/* List of symbols to extract from pthread debug library.  */
struct pthread_debug_entry
{
  const unsigned int namelen;
  const __char_ptr32 name;
  __void_ptr32 func;
};

#define DEBUG_ENTRY(str) { sizeof(str) - 1, str, 0 }

static struct pthread_debug_entry pthread_debug_entries[] = {
  DEBUG_ENTRY("pthreadDebugContextInit"),
  DEBUG_ENTRY("pthreadDebugThdSeqInit"),
  DEBUG_ENTRY("pthreadDebugThdSeqNext"),
  DEBUG_ENTRY("pthreadDebugThdSeqDestroy"),
  DEBUG_ENTRY("pthreadDebugThdGetInfo"),
  DEBUG_ENTRY("pthreadDebugThdGetInfoAddr"),
  DEBUG_ENTRY("pthreadDebugThdGetReg"),
  DEBUG_ENTRY("pthreadDebugCmd")
};

/* Pthread debug context.  */
static pthreadDebugContext_t debug_context;

/* Wrapper around pthread debug entry points.  */

static int
pthread_debug_thd_seq_init (pthreadDebugId_t *id)
{
  return ((int (*)())pthread_debug_entries[1].func)
    (debug_context, id);
}

static int
pthread_debug_thd_seq_next (pthreadDebugId_t *id)
{
  return ((int (*)())pthread_debug_entries[2].func)
    (debug_context, id);
}

static int
pthread_debug_thd_seq_destroy (void)
{
  return ((int (*)())pthread_debug_entries[3].func)
    (debug_context);
}

static int
pthread_debug_thd_get_info (pthreadDebugId_t id,
			    pthreadDebugThreadInfo_t *info)
{
  return ((int (*)())pthread_debug_entries[4].func)
    (debug_context, id, info);
}

static int
pthread_debug_thd_get_info_addr (pthread_t thr,
				 pthreadDebugThreadInfo_t *info)
{
  return ((int (*)())pthread_debug_entries[5].func)
    (debug_context, thr, info);
}

static int
pthread_debug_thd_get_reg (pthreadDebugId_t thr,
			   pthreadDebugRegs_t *regs)
{
  return ((int (*)())pthread_debug_entries[6].func)
    (debug_context, thr, regs);
}

static int
stub_pthread_debug_cmd (const char *cmd)
{
  return ((int (*)())pthread_debug_entries[7].func)
    (debug_context, cmd);
}

/* Show all the threads.  */

static void
threads_show (void)
{
  pthreadDebugId_t id;
  pthreadDebugThreadInfo_t info;
  int res;

  res = pthread_debug_thd_seq_init (&id);
  if (res != 0)
    {
      TERM_FAO ("seq init failed, res=!SL!/", res);
      return;
    }
  while (1)
    {
      if (pthread_debug_thd_get_info (id, &info) != 0)
	{
	  TERM_FAO ("thd_get_info !SL failed!/", id);
	  break;
	}
      if (pthread_debug_thd_seq_next (&id) != 0)
	break;
    }
  pthread_debug_thd_seq_destroy ();
}

/* Initialize pthread support.  */

static void
threads_init (void)
{
  static const $DESCRIPTOR (dbgext_desc, "PTHREAD$DBGEXT");
  static const $DESCRIPTOR (pthread_debug_desc, "PTHREAD$DBGSHR");
  static const $DESCRIPTOR (dbgsymtable_desc, "PTHREAD_DBG_SYMTABLE");
  int pthread_dbgext;
  int status;
  void *dbg_symtable;
  int i;
  void *caller_context = 0;

  status = lib$find_image_symbol
    ((void *) &pthread_rtl_desc, (void *) &dbgext_desc,
     (int *) &dbgext_func);
  if (!(status & STS$M_SUCCESS))
    LIB$SIGNAL (status);
  
  status = lib$find_image_symbol
    ((void *) &pthread_rtl_desc, (void *) &dbgsymtable_desc,
     (int *) &dbg_symtable);
  if (!(status & STS$M_SUCCESS))
    LIB$SIGNAL (status);

  /* Find entry points in pthread_debug.  */
  for (i = 0;
       i < sizeof (pthread_debug_entries) / sizeof (pthread_debug_entries[0]);
       i++)
    {
      struct dsc$descriptor_s sym =
	{ pthread_debug_entries[i].namelen,
	  DSC$K_DTYPE_T, DSC$K_CLASS_S,
	  pthread_debug_entries[i].name };
      status = lib$find_image_symbol
	((void *) &pthread_debug_desc, (void *) &sym,
	 (int *) &pthread_debug_entries[i].func);
      if (!(status & STS$M_SUCCESS))
	lib$signal (status);
    }

  if (trace_pthreaddbg)
    TERM_FAO ("debug symtable: !XH!/", dbg_symtable);
  status = ((int (*)()) pthread_debug_entries[0].func)
    (&caller_context, &pthread_debug_callbacks, dbg_symtable, &debug_context);
  if (status != 0)
    TERM_FAO ("cannot initialize pthread_debug: !UL!/", status);
  TERM_FAO ("pthread debug done!/", 0);
}

/* Convert an hexadecimal character to a nibble.  Return -1 in case of
   error.  */

static int
hex2nibble (unsigned char h)
{
  if (h >= '0' && h <= '9')
    return h - '0';
  if (h >= 'A' && h <= 'F')
    return h - 'A' + 10;
  if (h >= 'a' && h <= 'f')
    return h - 'a' + 10;
  return -1;
}

/* Convert an hexadecimal 2 character string to a byte.  Return -1 in case
   of error.  */

static int
hex2byte (const unsigned char *p)
{
  int h, l;

  h = hex2nibble (p[0]);
  l = hex2nibble (p[1]);
  if (h == -1 || l == -1)
    return -1;
  return (h << 4) | l;
}

/* Convert a byte V to a 2 character strings P.  */

static void
byte2hex (unsigned char *p, unsigned char v)
{
  p[0] = hex[v >> 4];
  p[1] = hex[v & 0xf];
}

/* Convert a quadword V to a 16 character strings P.  */

static void
quad2hex (unsigned char *p, unsigned __int64 v)
{
  int i;
  for (i = 0; i < 16; i++)
    {
      p[i] = hex[v >> 60];
      v <<= 4;
    }
}

static void
long2pkt (unsigned int v)
{
  int i;

  for (i = 0; i < 8; i++)
    {
      gdb_buf[gdb_blen + i] = hex[(v >> 28) & 0x0f];
      v <<= 4;
    }
  gdb_blen += 8;
}

/* Generate an error packet.  */

static void
packet_error (unsigned int err)
{
  gdb_buf[1] = 'E';
  byte2hex (gdb_buf + 2, err);
  gdb_blen = 4;
}

/* Generate an OK packet.  */

static void
packet_ok (void)
{
  gdb_buf[1] = 'O';
  gdb_buf[2] = 'K';
  gdb_blen = 3;
}

/* Append a register to the packet.  */

static void
ireg2pkt (const unsigned char *p)
{
  int i;

  for (i = 0; i < 8; i++)
    {
      byte2hex (gdb_buf + gdb_blen, p[i]);
      gdb_blen += 2;
    }
}

/* Append a C string (ASCIZ) to the packet.  */

static void
str2pkt (const char *str)
{
  while (*str)
    gdb_buf[gdb_blen++] = *str++;
}

/* Extract a number fro the packet.  */

static unsigned __int64
pkt2val (const unsigned char *pkt, unsigned int *pos)
{
  unsigned __int64 res = 0;
  unsigned int i;

  while (1)
    {
      int r = hex2nibble (pkt[*pos]);

      if (r < 0)
	return res;
      res = (res << 4) | r;
      (*pos)++;
    }
}

/* Append LEN bytes from B to the current gdb packet (encode in binary).  */

static void
mem2bin (const unsigned char *b, unsigned int len)
{
  unsigned int i;
  for (i = 0; i < len; i++)
    switch (b[i])
      {
      case '#':
      case '$':
      case '}':
      case '*':
      case 0:
	gdb_buf[gdb_blen++] = '}';
	gdb_buf[gdb_blen++] = b[i] ^ 0x20;
	break;
      default:
	gdb_buf[gdb_blen++] = b[i];
	break;
      }
}

/* Append LEN bytes from B to the current gdb packet (encode in hex).  */

static void
mem2hex (const unsigned char *b, unsigned int len)
{
  unsigned int i;
  for (i = 0; i < len; i++)
    {
      byte2hex (gdb_buf + gdb_blen, b[i]);
      gdb_blen += 2;
    }
}

/* Handle the 'q' packet.  */

static void
handle_q_packet (const unsigned char *pkt, unsigned int pktlen)
{
  /* For qfThreadInfo and qsThreadInfo.  */
  static unsigned int first_thread;
  static unsigned int last_thread;

  static const char xfer_uib[] = "qXfer:uib:read:";
#define XFER_UIB_LEN (sizeof (xfer_uib) - 1)
  static const char qfthreadinfo[] = "qfThreadInfo";
#define QFTHREADINFO_LEN (sizeof (qfthreadinfo) - 1)
  static const char qsthreadinfo[] = "qsThreadInfo";
#define QSTHREADINFO_LEN (sizeof (qsthreadinfo) - 1)
  static const char qthreadextrainfo[] = "qThreadExtraInfo,";
#define QTHREADEXTRAINFO_LEN (sizeof (qthreadextrainfo) - 1)
  static const char qsupported[] = "qSupported:";
#define QSUPPORTED_LEN (sizeof (qsupported) - 1)

  if (pktlen == 2 && pkt[1] == 'C')
    {
      /* Current thread.  */
      gdb_buf[0] = '$';
      gdb_buf[1] = 'Q';
      gdb_buf[2] = 'C';
      gdb_blen = 3;
      if (has_threads)
	long2pkt ((unsigned long) get_teb ());
      return;
    }
  else if (pktlen > XFER_UIB_LEN
      && ots$strcmp_eql (pkt, XFER_UIB_LEN, xfer_uib, XFER_UIB_LEN))
    {
      /* Get unwind information block.  */
      unsigned __int64 pc;
      unsigned int pos = XFER_UIB_LEN;
      unsigned int off;
      unsigned int len;
      union
      {
	unsigned char bytes[32];
	struct
	{
	  unsigned __int64 code_start_va;
	  unsigned __int64 code_end_va;
	  unsigned __int64 uib_start_va;
	  unsigned __int64 gp_value;
	} data;
      } uei;
      int res;
      int i;

      packet_error (0);

      pc = pkt2val (pkt, &pos);
      if (pkt[pos] != ':')
	return;
      pos++;
      off = pkt2val (pkt, &pos);
      if (pkt[pos] != ',' || off != 0)
	return;
      pos++;
      len = pkt2val (pkt, &pos);
      if (pkt[pos] != '#' || len != 0x20)
	return;

      res = SYS$GET_UNWIND_ENTRY_INFO (pc, &uei.data, 0);
      if (res == SS$_NODATA || res != SS$_NORMAL)
	ots$fill (uei.bytes, sizeof (uei.bytes), 0);

      if (trace_unwind)
	{
	  TERM_FAO ("Unwind request for !XH, status=!XL, uib=!XQ, GP=!XQ!/",
		    pc, res, uei.data.uib_start_va, uei.data.gp_value);
	}

      gdb_buf[0] = '$';
      gdb_buf[1] = 'l';
      gdb_blen = 2;
      mem2bin (uei.bytes, sizeof (uei.bytes));
    }
  else if (pktlen == QFTHREADINFO_LEN
	   && ots$strcmp_eql (pkt, QFTHREADINFO_LEN,
			      qfthreadinfo, QFTHREADINFO_LEN))
    {
      /* Get first thread(s).  */
      gdb_buf[0] = '$';
      gdb_buf[1] = 'm';
      gdb_blen = 2;

      if (!has_threads)
	{
	  gdb_buf[1] = 'l';
	  return;
	}
      first_thread = thread_next (0);
      last_thread = first_thread;
      long2pkt (first_thread);
    }
  else if (pktlen == QSTHREADINFO_LEN
	   && ots$strcmp_eql (pkt, QSTHREADINFO_LEN,
			      qsthreadinfo, QSTHREADINFO_LEN))
    {
      /* Get subsequent threads.  */
      gdb_buf[0] = '$';
      gdb_buf[1] = 'm';
      gdb_blen = 2;
      while (dbgext_func)
	{
	  unsigned int res;
	  res = thread_next (last_thread);
	  if (res == first_thread)
	    break;
	  if (gdb_blen > 2)
	    gdb_buf[gdb_blen++] = ',';
	  long2pkt (res);
	  last_thread = res;
	  if (gdb_blen > sizeof (gdb_buf) - 16)
	    break;
	}

      if (gdb_blen == 2)
	gdb_buf[1] = 'l';
    }
  else if (pktlen > QTHREADEXTRAINFO_LEN
	   && ots$strcmp_eql (pkt, QTHREADEXTRAINFO_LEN,
			      qthreadextrainfo, QTHREADEXTRAINFO_LEN))
    {
      /* Get extra info about a thread.  */
      pthread_t thr;
      unsigned int pos = QTHREADEXTRAINFO_LEN;
      pthreadDebugThreadInfo_t info;
      int res;

      packet_error (0);
      if (!has_threads)
	return;

      thr = (pthread_t) pkt2val (pkt, &pos);
      if (pkt[pos] != '#')
	return;
      res = pthread_debug_thd_get_info_addr (thr, &info);
      if (res != 0)
	{
	  TERM_FAO ("qThreadExtraInfo (!XH) failed: !SL!/", thr, res);
	  return;
	}
      gdb_buf[0] = '$';
      gdb_blen = 1;
      mem2hex ((const unsigned char *)"VMS-thread", 11);
    }
  else if (pktlen > QSUPPORTED_LEN
	   && ots$strcmp_eql (pkt, QSUPPORTED_LEN,
			      qsupported, QSUPPORTED_LEN))
    {
      /* Get supported features.  */
      pthread_t thr;
      unsigned int pos = QSUPPORTED_LEN;
      pthreadDebugThreadInfo_t info;
      int res;
      
      /* Ignore gdb features.  */
      gdb_buf[0] = '$';
      gdb_blen = 1;

      str2pkt ("qXfer:uib:read+");
      return;
    }
  else
    {
      if (trace_pkt)
	{
	  term_puts ("unknown <: ");
	  term_write ((char *)pkt, pktlen);
	  term_putnl ();
	}
      return;
    }
}

/* Handle the 'v' packet.  */

static int
handle_v_packet (const unsigned char *pkt, unsigned int pktlen)
{
  static const char vcontq[] = "vCont?";
#define VCONTQ_LEN (sizeof (vcontq) - 1)

  if (pktlen == VCONTQ_LEN
      && ots$strcmp_eql (pkt, VCONTQ_LEN, vcontq, VCONTQ_LEN))
    {
      gdb_buf[0] = '$';
      gdb_blen = 1;

      str2pkt ("vCont;c;s");
      return 0;
    }
  else
    {
      if (trace_pkt)
	{
	  term_puts ("unknown <: ");
	  term_write ((char *)pkt, pktlen);
	  term_putnl ();
	}
      return 0;
    }
}

/* Get regs for the selected thread.  */

static struct ia64_all_regs *
get_selected_regs (void)
{
  pthreadDebugRegs_t regs;
  int res;

  if (selected_thread == 0 || selected_thread == get_teb ())
    return &excp_regs;

  if (selected_thread == sel_regs_pthread)
    return &sel_regs;

  /* Read registers.  */
  res = pthread_debug_thd_get_reg (selected_id, &regs);
  if (res != 0)
    {
      /* FIXME: return NULL ?  */
      return &excp_regs;
    }
  sel_regs_pthread = selected_thread;
  sel_regs.gr[1].v = regs.gp;
  sel_regs.gr[4].v = regs.r4;
  sel_regs.gr[5].v = regs.r5;
  sel_regs.gr[6].v = regs.r6;
  sel_regs.gr[7].v = regs.r7;
  sel_regs.gr[12].v = regs.sp;
  sel_regs.br[0].v = regs.rp;
  sel_regs.br[1].v = regs.b1;
  sel_regs.br[2].v = regs.b2;
  sel_regs.br[3].v = regs.b3;
  sel_regs.br[4].v = regs.b4;
  sel_regs.br[5].v = regs.b5;
  sel_regs.ip.v = regs.ip;
  sel_regs.bsp.v = regs.bspstore; /* FIXME: it is correct ?  */
  sel_regs.pfs.v = regs.pfs;
  sel_regs.pr.v = regs.pr;
  return &sel_regs;
}

/* Create a status packet.  */

static void
packet_status (void)
{
  gdb_blen = 0;
  if (has_threads)
    {
      str2pkt ("$T05thread:");
      long2pkt ((unsigned long) get_teb ());
      gdb_buf[gdb_blen++] = ';';
    }
  else
    str2pkt ("$S05");
}

/* Return 1 to continue.  */

static int
handle_packet (unsigned char *pkt, unsigned int len)
{
  unsigned int pos;

  /* By default, reply unsupported.  */
  gdb_buf[0] = '$';
  gdb_blen = 1;

  pos = 1;
  switch (pkt[0])
    {
    case '?':
      if (len == 1)
	{
	  packet_status ();
	  return 0;
	}
      break;
    case 'c':
      if (len == 1)
	{
	  /* Clear psr.ss.  */
	  excp_regs.psr.v &= ~(unsigned __int64)PSR$M_SS;
	  return 1;
	}
      else
	packet_error (0);
      break;
    case 'g':
      if (len == 1)
	{
	  unsigned int i;
	  struct ia64_all_regs *regs = get_selected_regs ();
	  unsigned char *p = regs->gr[0].b;

	  for (i = 0; i < 8 * 32; i++)
	    byte2hex (gdb_buf + 1 + 2 * i, p[i]);
	  gdb_blen += 2 * 8 * 32;
	  return 0;
	}
      break;
    case 'H':
      if (pkt[1] == 'g')
	{
	  int res;
	  unsigned __int64 val;
	  pthreadDebugThreadInfo_t info;
	  
	  pos++;
	  val = pkt2val (pkt, &pos);
	  if (pos != len)
	    {
	      packet_error (0);
	      return 0;
	    }
	  if (val == 0)
	    {
	      /* Default one.  */
	      selected_thread = get_teb ();
	      selected_id = 0;
	    }
	  else if (!has_threads)
	    {
	      packet_error (0);
	      return 0;
	    }
	  else
	    {
	      res = pthread_debug_thd_get_info_addr ((pthread_t) val, &info);
	      if (res != 0)
		{
		  TERM_FAO ("qThreadExtraInfo (!XH) failed: !SL!/", val, res);
		  packet_error (0);
		  return 0;
		}
	      selected_thread = info.teb;
	      selected_id = info.sequence;
	    }
	  packet_ok ();
	  break;
	}
      else if (pkt[1] == 'c'
	       && ((pkt[2] == '-' && pkt[3] == '1' && len == 4)
		   || (pkt[2] == '0' && len == 3)))
	{
	  /* Silently accept 'Hc0' and 'Hc-1'.  */
	  packet_ok ();
	  break;
	}
      else
	{
	  packet_error (0);
	  return 0;
	}
    case 'k':
      SYS$EXIT (SS$_NORMAL);
      break;
    case 'm':
      {
	unsigned __int64 addr;
	unsigned __int64 paddr;
	unsigned int l;
	unsigned int i;

	addr = pkt2val (pkt, &pos);
	if (pkt[pos] != ',')
	  {
	    packet_error (0);
	    return 0;
	  }
	pos++;
	l = pkt2val (pkt, &pos);
	if (pkt[pos] != '#')
	  {
	    packet_error (0);
	    return 0;
	  }

	/* Check access.  */
	i = l + (addr & VMS_PAGE_MASK);
	paddr = addr & ~VMS_PAGE_MASK;
	while (1)
	  {
	    if (__prober (paddr, 0) != 1)
	      {
		packet_error (2);
		return 0;
	      }
	    if (i < VMS_PAGE_SIZE)
	      break;
	    i -= VMS_PAGE_SIZE;
	    paddr += VMS_PAGE_SIZE;
	  }

	/* Transfer.  */
	for (i = 0; i < l; i++)
	  byte2hex (gdb_buf + 1 + 2 * i, ((unsigned char *)addr)[i]);
	gdb_blen += 2 * l;
      }
      break;
    case 'M':
      {
	unsigned __int64 addr;
	unsigned __int64 paddr;
	unsigned int l;
	unsigned int i;
	unsigned int oldprot;

	addr = pkt2val (pkt, &pos);
	if (pkt[pos] != ',')
	  {
	    packet_error (0);
	    return 0;
	  }
	pos++;
	l = pkt2val (pkt, &pos);
	if (pkt[pos] != ':')
	  {
	    packet_error (0);
	    return 0;
	  }
	pos++;
	page_set_rw (addr, l, &oldprot);

	/* Check access.  */
	i = l + (addr & VMS_PAGE_MASK);
	paddr = addr & ~VMS_PAGE_MASK;
	while (1)
	  {
	    if (__probew (paddr, 0) != 1)
	      {
		page_restore_rw (addr, l, oldprot);
		return 0;
	      }
	    if (i < VMS_PAGE_SIZE)
	      break;
	    i -= VMS_PAGE_SIZE;
	    paddr += VMS_PAGE_SIZE;
	  }

	/* Write.  */
	for (i = 0; i < l; i++)
	  {
	    int v = hex2byte (pkt + pos);
	    pos += 2;
	    ((unsigned char *)addr)[i] = v;
	  }

	/* Sync caches.  */
	for (i = 0; i < l; i += 15)
	  __fc (addr + i);
	__fc (addr + l);

	page_restore_rw (addr, l, oldprot);
	packet_ok ();
      }
      break;
    case 'p':
      {
	unsigned int num = 0;
	unsigned int i;
	struct ia64_all_regs *regs = get_selected_regs ();

	num = pkt2val (pkt, &pos);
	if (pos != len)
	  {
	    packet_error (0);
	    return 0;
	  }

	switch (num)
	  {
	  case IA64_IP_REGNUM:
	    ireg2pkt (regs->ip.b);
	    break;
	  case IA64_BR0_REGNUM:
	    ireg2pkt (regs->br[0].b);
	    break;
	  case IA64_PSR_REGNUM:
	    ireg2pkt (regs->psr.b);
	    break;
	  case IA64_BSP_REGNUM:
	    ireg2pkt (regs->bsp.b);
	    break;
	  case IA64_CFM_REGNUM:
	    ireg2pkt (regs->cfm.b);
	    break;
	  case IA64_PFS_REGNUM:
	    ireg2pkt (regs->pfs.b);
	    break;
	  case IA64_PR_REGNUM:
	    ireg2pkt (regs->pr.b);
	    break;
	  default:
	    TERM_FAO ("gdbserv: unhandled reg !UW!/", num);
	    packet_error (0);
	    return 0;
	  }
      }
      break;
    case 'q':
      handle_q_packet (pkt, len);
      break;
    case 's':
      if (len == 1)
	{
	  /* Set psr.ss.  */
	  excp_regs.psr.v |= (unsigned __int64)PSR$M_SS;
	  return 1;
	}
      else
	packet_error (0);
      break;
    case 'T':
      /* Thread status.  */
      if (!has_threads)
	{
	  packet_ok ();
	  break;
	}
      else
	{
	  int res;
	  unsigned __int64 val;
	  unsigned int fthr, thr;
	  
	  val = pkt2val (pkt, &pos);
	  /* Default is error (but only after parsing is complete).  */
	  packet_error (0);
	  if (pos != len)
	    break;

	  /* Follow the list.  This makes a O(n2) algorithm, but we don't really
	     have the choice.  Note that pthread_debug_thd_get_info_addr
	     doesn't look reliable.  */
	  fthr = thread_next (0);
	  thr = fthr;
	  do
	    {
	      if (val == thr)
		{
		  packet_ok ();
		  break;
		}
	      thr = thread_next (thr);
	    }
	  while (thr != fthr);
	}
      break;
    case 'v':
      return handle_v_packet (pkt, len);
      break;
    case 'V':
      if (len > 3 && pkt[1] == 'M' && pkt[2] == 'S' && pkt[3] == ' ')
	{
	  /* Temporary extension.  */
	  if (has_threads)
	    {
	      pkt[len] = 0;
	      stub_pthread_debug_cmd ((char *)pkt + 4);
	      packet_ok ();
	    }
	  else
	    packet_error (0);
	}
      break;
    default:
      if (trace_pkt)
	{
	  term_puts ("unknown <: ");
	  term_write ((char *)pkt, len);
	  term_putnl ();
	}
      break;
    }
  return 0;
}

/* Raw write to gdb.  */

static void
sock_write (const unsigned char *buf, int len)
{
  struct _iosb iosb;
  unsigned int status;

  /* Write data to connection.  */
  status = sys$qiow (EFN$C_ENF,           /* Event flag.  */
		     conn_channel,        /* I/O channel.  */
		     IO$_WRITEVBLK,       /* I/O function code.  */
		     &iosb,               /* I/O status block.  */
		     0,                   /* Ast service routine.  */
		     0,                   /* Ast parameter.  */
		     (char *)buf,         /* P1 - buffer address.  */
		     len,                 /* P2 - buffer length.  */
		     0, 0, 0, 0);
  if (status & STS$M_SUCCESS)
    status = iosb.iosb$w_status;
  if (!(status & STS$M_SUCCESS))
    {
      term_puts ("Failed to write data to gdb\n");
      LIB$SIGNAL (status);
    }
}

/* Compute the checksum and send the packet.  */

static void
send_pkt (void)
{
  unsigned char chksum = 0;
  unsigned int i;

  for (i = 1; i < gdb_blen; i++)
    chksum += gdb_buf[i];

  gdb_buf[gdb_blen] = '#';
  byte2hex (gdb_buf + gdb_blen + 1, chksum);

  sock_write (gdb_buf, gdb_blen + 3);

  if (trace_pkt > 1)
    {
      term_puts (">: ");
      term_write ((char *)gdb_buf, gdb_blen + 3);
      term_putnl ();
    }
}

/* Read and handle one command.  Return 1 is execution must resume.  */

static int
one_command (void)
{
  struct _iosb iosb;
  unsigned int status;
  unsigned int off;
  unsigned int dollar_off = 0;
  unsigned int sharp_off = 0;
  unsigned int cmd_off;
  unsigned int cmd_len;

  /* Wait for a packet.  */
  while (1)
    {
      off = 0;
      while (1)
	{
	  /* Read data from connection.  */
	  status = sys$qiow (EFN$C_ENF,           /* Event flag.  */
			     conn_channel,        /* I/O channel.  */
			     IO$_READVBLK,        /* I/O function code.  */
			     &iosb,               /* I/O status block.  */
			     0,                   /* Ast service routine.  */
			     0,                   /* Ast parameter.  */
			     gdb_buf + off,       /* P1 - buffer address.  */
			     sizeof (gdb_buf) - off, /* P2 - buffer leng.  */
			     0, 0, 0, 0);
	  if (status & STS$M_SUCCESS)
	    status = iosb.iosb$w_status;
	  if (!(status & STS$M_SUCCESS))
	    {
	      term_puts ("Failed to read data from connection\n" );
	      LIB$SIGNAL (status);
	    }

#ifdef RAW_DUMP
	  term_puts ("{: ");
	  term_write ((char *)gdb_buf + off, iosb.iosb$w_bcnt);
	  term_putnl ();
#endif

	  gdb_blen = off + iosb.iosb$w_bcnt;

	  if (off == 0)
	    {
	      /* Search for '$'.  */
	      for (dollar_off = 0; dollar_off < gdb_blen; dollar_off++)
		if (gdb_buf[dollar_off] == '$')
		  break;
	      if (dollar_off >= gdb_blen)
		{
		  /* Not found, discard the data.  */
		  off = 0;
		  continue;
		}
	      /* Search for '#'.  */
	      for (sharp_off = dollar_off + 1;
		   sharp_off < gdb_blen;
		   sharp_off++)
		if (gdb_buf[sharp_off] == '#')
		  break;
	    }
	  else if (sharp_off >= off)
	    {
	      /* Search for '#'.  */
	      for (; sharp_off < gdb_blen; sharp_off++)
		if (gdb_buf[sharp_off] == '#')
		  break;
	    }

	  /* Got packet with checksum.  */
	  if (sharp_off + 2 <= gdb_blen)
	    break;

	  off = gdb_blen;
	  if (gdb_blen == sizeof (gdb_buf))
	    {
	      /* Packet too large, discard.  */
	      off = 0;
	    }
	}

      /* Validate and acknowledge a packet.  */
      {
	unsigned char chksum = 0;
	unsigned int i;
	int v;

	for (i = dollar_off + 1; i < sharp_off; i++)
	  chksum += gdb_buf[i];
	v = hex2byte (gdb_buf + sharp_off + 1);
	if (v != chksum)
	  {
	    term_puts ("Discard bad checksum packet\n");
	    continue;
	  }
	else
	  {
	    sock_write ((const unsigned char *)"+", 1);
	    break;
	  }
      }
    }

  if (trace_pkt > 1)
    {
      term_puts ("<: ");
      term_write ((char *)gdb_buf + dollar_off, sharp_off - dollar_off + 1);
      term_putnl ();
    }

  cmd_off = dollar_off + 1;
  cmd_len = sharp_off - dollar_off - 1;

  if (handle_packet (gdb_buf + dollar_off + 1, sharp_off - dollar_off - 1) == 1)
    return 1;

  send_pkt ();
  return 0;
}

/* Display the condition given by SIG64.  */

static void
display_excp (struct chf64$signal_array *sig64, struct chf$mech_array *mech)
{
  unsigned int status;
  char msg[160];
  unsigned short msglen;
  $DESCRIPTOR (msg_desc, msg);
  unsigned char outadr[4];

  status = SYS$GETMSG (sig64->chf64$q_sig_name, &msglen, &msg_desc, 0, outadr);
  if (status & STS$M_SUCCESS)
    {
      char msg2[160];
      unsigned short msg2len;
      struct dsc$descriptor_s msg2_desc =
	{ sizeof (msg2), DSC$K_DTYPE_T, DSC$K_CLASS_S, msg2};
      msg_desc.dsc$w_length = msglen;
      status = SYS$FAOL_64 (&msg_desc, &msg2len, &msg2_desc,
			    &sig64->chf64$q_sig_arg1);
      if (status & STS$M_SUCCESS)
	term_write (msg2, msg2len);
    }
  else
    term_puts ("no message");
  term_putnl ();

  if (trace_excp > 1)
    {
      TERM_FAO (" Frame: !XH, Depth: !4SL, Esf: !XH!/",
		mech->chf$q_mch_frame, mech->chf$q_mch_depth,
		mech->chf$q_mch_esf_addr);
    }
}

/* Get all registers from current thread.  */

static void
read_all_registers (struct chf$mech_array *mech)
{
  struct _intstk *intstk =
    (struct _intstk *)mech->chf$q_mch_esf_addr;
  struct chf64$signal_array *sig64 =
    (struct chf64$signal_array *)mech->chf$ph_mch_sig64_addr;
  unsigned int cnt = sig64->chf64$w_sig_arg_count;
  unsigned __int64 pc = (&sig64->chf64$q_sig_name)[cnt - 2];

  excp_regs.ip.v = pc;
  excp_regs.psr.v = intstk->intstk$q_ipsr;
  /* GDB and linux expects bsp to point after the current register frame.
     Adjust.  */
  {
    unsigned __int64 bsp = intstk->intstk$q_bsp;
    unsigned int sof = intstk->intstk$q_ifs & 0x7f;
    unsigned int delta = ((bsp >> 3) & 0x3f) + sof;
    excp_regs.bsp.v = bsp + ((sof + delta / 0x3f) << 3);
  }
  excp_regs.cfm.v = intstk->intstk$q_ifs & 0x3fffffffff;
  excp_regs.pfs.v = intstk->intstk$q_pfs;
  excp_regs.pr.v = intstk->intstk$q_preds;
  excp_regs.gr[0].v = 0;
  excp_regs.gr[1].v = intstk->intstk$q_gp;
  excp_regs.gr[2].v = intstk->intstk$q_r2;
  excp_regs.gr[3].v = intstk->intstk$q_r3;
  excp_regs.gr[4].v = intstk->intstk$q_r4;
  excp_regs.gr[5].v = intstk->intstk$q_r5;
  excp_regs.gr[6].v = intstk->intstk$q_r6;
  excp_regs.gr[7].v = intstk->intstk$q_r7;
  excp_regs.gr[8].v = intstk->intstk$q_r8;
  excp_regs.gr[9].v = intstk->intstk$q_r9;
  excp_regs.gr[10].v = intstk->intstk$q_r10;
  excp_regs.gr[11].v = intstk->intstk$q_r11;
  excp_regs.gr[12].v = (unsigned __int64)intstk + intstk->intstk$l_stkalign;
  excp_regs.gr[13].v = intstk->intstk$q_r13;
  excp_regs.gr[14].v = intstk->intstk$q_r14;
  excp_regs.gr[15].v = intstk->intstk$q_r15;
  excp_regs.gr[16].v = intstk->intstk$q_r16;
  excp_regs.gr[17].v = intstk->intstk$q_r17;
  excp_regs.gr[18].v = intstk->intstk$q_r18;
  excp_regs.gr[19].v = intstk->intstk$q_r19;
  excp_regs.gr[20].v = intstk->intstk$q_r20;
  excp_regs.gr[21].v = intstk->intstk$q_r21;
  excp_regs.gr[22].v = intstk->intstk$q_r22;
  excp_regs.gr[23].v = intstk->intstk$q_r23;
  excp_regs.gr[24].v = intstk->intstk$q_r24;
  excp_regs.gr[25].v = intstk->intstk$q_r25;
  excp_regs.gr[26].v = intstk->intstk$q_r26;
  excp_regs.gr[27].v = intstk->intstk$q_r27;
  excp_regs.gr[28].v = intstk->intstk$q_r28;
  excp_regs.gr[29].v = intstk->intstk$q_r29;
  excp_regs.gr[30].v = intstk->intstk$q_r30;
  excp_regs.gr[31].v = intstk->intstk$q_r31;
  excp_regs.br[0].v = intstk->intstk$q_b0;
  excp_regs.br[1].v = intstk->intstk$q_b1;
  excp_regs.br[2].v = intstk->intstk$q_b2;
  excp_regs.br[3].v = intstk->intstk$q_b3;
  excp_regs.br[4].v = intstk->intstk$q_b4;
  excp_regs.br[5].v = intstk->intstk$q_b5;
  excp_regs.br[6].v = intstk->intstk$q_b6;
  excp_regs.br[7].v = intstk->intstk$q_b7;
}

/* Write all registers to current thread.  FIXME: not yet complete.  */

static void
write_all_registers (struct chf$mech_array *mech)
{
  struct _intstk *intstk =
    (struct _intstk *)mech->chf$q_mch_esf_addr;

  intstk->intstk$q_ipsr = excp_regs.psr.v;
}

/* Do debugging.  Report status to gdb and execute commands.  */

static void
do_debug (struct chf$mech_array *mech)
{
  struct _intstk *intstk =
    (struct _intstk *)mech->chf$q_mch_esf_addr;
  unsigned int old_ast;
  unsigned int old_sch;
  unsigned int status;

  /* Disable ast.  */
  status = sys$setast (0);
  switch (status)
    {
    case SS$_WASCLR:
      old_ast = 0;
      break;
    case SS$_WASSET:
      old_ast = 1;
      break;
    default:
      /* Should never happen!  */
      lib$signal (status);
    }

  /* Disable thread scheduling.  */
  if (has_threads)
    old_sch = set_thread_scheduling (0);

  read_all_registers (mech);

  /* Send stop reply packet.  */
  packet_status ();
  send_pkt ();

  while (one_command () == 0)
    ;

  write_all_registers (mech);

  /* Re-enable scheduling.  */
  if (has_threads)
    set_thread_scheduling (old_sch);

  /* Re-enable AST.  */
  status = sys$setast (old_ast);
  if (!(status & STS$M_SUCCESS))
    LIB$SIGNAL (status);
}

/* The condition handler.  That's the core of the stub.  */

static int
excp_handler (struct chf$signal_array *sig,
	      struct chf$mech_array *mech)
{
  struct chf64$signal_array *sig64 =
    (struct chf64$signal_array *)mech->chf$ph_mch_sig64_addr;
  unsigned int code = sig->chf$l_sig_name & STS$M_COND_ID;
  unsigned int cnt = sig64->chf64$w_sig_arg_count;
  unsigned __int64 pc;
  unsigned int ret;
  /* Self protection.  FIXME: Should be per thread ?  */
  static int in_handler = 0;

  /* Completely ignore some conditions (signaled indirectly by this stub).  */
  switch (code)
    {
    case LIB$_KEYNOTFOU & STS$M_COND_ID:
      return SS$_RESIGNAL_64;
    default:
      break;
    }

  /* Protect against recursion.  */
  in_handler++;
  if (in_handler > 1)
    {
      if (in_handler == 2)
	TERM_FAO ("gdbstub: exception in handler (pc=!XH)!!!/",
		  (&sig64->chf64$q_sig_name)[cnt - 2]);
      sys$exit (sig->chf$l_sig_name);
    }

  pc = (&sig64->chf64$q_sig_name)[cnt - 2];
  if (trace_excp)
    TERM_FAO ("excp_handler: code: !XL, pc=!XH!/", code, pc);

  /* If break on the entry point, restore the bundle.  */
  if (code == (SS$_BREAK & STS$M_COND_ID)
      && pc == entry_pc
      && entry_pc != 0)
    {
      static unsigned int entry_prot;

      if (trace_entry)
	term_puts ("initial entry breakpoint\n");
      page_set_rw (entry_pc, 16, &entry_prot);

      ots$move ((void *)entry_pc, 16, entry_saved);
      __fc (entry_pc);
      page_restore_rw (entry_pc, 16, entry_prot);
    }

  switch (code)
    {
    case SS$_ACCVIO & STS$M_COND_ID:
      if (trace_excp <= 1)
	display_excp (sig64, mech);
      /* Fall through.  */
    case SS$_BREAK  & STS$M_COND_ID:
    case SS$_OPCDEC & STS$M_COND_ID:
    case SS$_TBIT   & STS$M_COND_ID:
    case SS$_DEBUG  & STS$M_COND_ID:
      if (trace_excp > 1)
	{
	  int i;
	  struct _intstk *intstk =
	    (struct _intstk *)mech->chf$q_mch_esf_addr;

	  display_excp (sig64, mech);

	  TERM_FAO (" intstk: !XH!/", intstk);
	  for (i = 0; i < cnt + 1; i++)
	    TERM_FAO ("   !XH!/", ((unsigned __int64 *)sig64)[i]);
	}
      do_debug (mech);
      ret = SS$_CONTINUE_64;
      break;

    default:
      display_excp (sig64, mech);
      ret = SS$_RESIGNAL_64;
      break;
    }

  in_handler--;
  /* Discard selected thread registers.  */
  sel_regs_pthread = 0;
  return ret;
}

/* Setup internal trace flags according to GDBSTUB$TRACE logical.  */

static void
trace_init (void)
{
  unsigned int status, i, start;
  unsigned short len;
  char resstring[LNM$C_NAMLENGTH];
  static const $DESCRIPTOR (tabdesc, "LNM$DCL_LOGICAL");
  static const $DESCRIPTOR (logdesc, "GDBSTUB$TRACE");
  $DESCRIPTOR (sub_desc, resstring);
  ILE3 item_lst[2];

  item_lst[0].ile3$w_length = LNM$C_NAMLENGTH;
  item_lst[0].ile3$w_code = LNM$_STRING;
  item_lst[0].ile3$ps_bufaddr = resstring;
  item_lst[0].ile3$ps_retlen_addr = &len;
  item_lst[1].ile3$w_length = 0;
  item_lst[1].ile3$w_code = 0;

  /* Translate the logical name.  */
  status = SYS$TRNLNM (0,   		/* Attributes of the logical name.  */
		       (void *)&tabdesc,       /* Logical name table.  */
		       (void *)&logdesc,       /* Logical name.  */
		       0,              	       /* Access mode.  */
		       &item_lst);             /* Item list.  */
  if (status == SS$_NOLOGNAM)
    return;
  if (!(status & STS$M_SUCCESS))
    LIB$SIGNAL (status);

  start = 0;
  for (i = 0; i <= len; i++)
    {
      if ((i == len || resstring[i] == ',' || resstring[i] == ';')
	  && i != start)
	{
	  int j;

	  sub_desc.dsc$a_pointer = resstring + start;
	  sub_desc.dsc$w_length = i - start;

	  for (j = 0; j < NBR_DEBUG_FLAGS; j++)
	    if (str$case_blind_compare (&sub_desc, 
					(void *)&debug_flags[j].name) == 0)
	      {
		debug_flags[j].val++;
		break;
	      }
	  if (j == NBR_DEBUG_FLAGS)
	    TERM_FAO ("GDBSTUB$TRACE: unknown directive !AS!/", &sub_desc);

	  start = i + 1;
	}
    }

  TERM_FAO ("GDBSTUB$TRACE=!AD ->", len, resstring);
  for (i = 0; i < NBR_DEBUG_FLAGS; i++)
    if (debug_flags[i].val > 0)
      TERM_FAO (" !AS=!ZL", &debug_flags[i].name, debug_flags[i].val);
  term_putnl ();
}


/* Entry point.  */

static int
stub_start (unsigned __int64 *progxfer, void *cli_util,
	    EIHD *imghdr, IFD *imgfile,
	    unsigned int linkflag, unsigned int cliflag)
{
  static int initialized;
  int i;
  int cnt;
  int is_attached;
  IMCB *imcb;
  if (initialized)
    term_puts ("gdbstub: re-entry\n");
  else
    initialized = 1;

  /* When attached (through SS$_DEBUG condition), the number of arguments
     is 4 and PROGXFER is the PC at interruption.  */
  va_count (cnt);
  is_attached = cnt == 4;

  term_init ();

  /* Hello banner.  */
  term_puts ("Hello from gdb stub\n");

  trace_init ();

  if (trace_entry && !is_attached)
    {
      TERM_FAO ("xfer: !XH, imghdr: !XH, ifd: !XH!/",
		progxfer, imghdr, imgfile);
      for (i = -2; i < 8; i++)
	TERM_FAO ("  at !2SW: !XH!/", i, progxfer[i]);
    }

  /* Search for entry point.  */
  if (!is_attached)
    {
      entry_pc = 0;
      for (i = 0; progxfer[i]; i++)
	entry_pc = progxfer[i];

      if (trace_entry)
	{
	  if (entry_pc == 0)
	    {
	      term_puts ("No entry point\n");
	      return 0;
	    }
	  else
	    TERM_FAO ("Entry: !XH!/",entry_pc);
	}
    }
  else
    entry_pc = progxfer[0];

  has_threads = 0;
  for (imcb = ctl$gl_imglstptr->imcb$l_flink;
       imcb != ctl$gl_imglstptr;
       imcb = imcb->imcb$l_flink)
    {
      if (ots$strcmp_eql (pthread_rtl_desc.dsc$a_pointer,
			  pthread_rtl_desc.dsc$w_length,
			  imcb->imcb$t_log_image_name + 1,
			  imcb->imcb$t_log_image_name[0]))
	has_threads = 1;
			  
      if (trace_images)
	{
	  unsigned int j;
	  LDRIMG *ldrimg = imcb->imcb$l_ldrimg;
	  LDRISD *ldrisd;

	  TERM_FAO ("!XA-!XA ",
		    imcb->imcb$l_starting_address,
		    imcb->imcb$l_end_address);

	  switch (imcb->imcb$b_act_code)
	    {
	    case IMCB$K_MAIN_PROGRAM:
	      term_puts ("prog");
	      break;
	    case IMCB$K_MERGED_IMAGE:
	      term_puts ("mrge");
	      break;
	    case IMCB$K_GLOBAL_IMAGE_SECTION:
	      term_puts ("glob");
	      break;
	    default:
	      term_puts ("????");
	    }
	  TERM_FAO (" !AD !40AC!/",
		    1, "KESU" + (imcb->imcb$b_access_mode & 3),
		    imcb->imcb$t_log_image_name);

	  if ((long) ldrimg < 0 || trace_images < 2)
	    continue;
	  ldrisd = ldrimg->ldrimg$l_segments;
	  for (j = 0; j < ldrimg->ldrimg$l_segcount; j++)
	    {
	      unsigned int flags = ldrisd[j].ldrisd$i_flags;
	      term_puts ("   ");
	      term_putc (flags & 0x04 ? 'R' : '-');
	      term_putc (flags & 0x02 ? 'W' : '-');
	      term_putc (flags & 0x01 ? 'X' : '-');
	      term_puts (flags & 0x01000000 ? " Prot" : "     ");
	      term_puts (flags & 0x04000000 ? " Shrt" : "     ");
	      term_puts (flags & 0x08000000 ? " Shrd" : "     ");
	      TERM_FAO (" !XA-!XA!/",
			ldrisd[j].ldrisd$p_base,
			(unsigned __int64) ldrisd[j].ldrisd$p_base 
			+ ldrisd[j].ldrisd$i_len - 1);
	    }
	  ldrisd = ldrimg->ldrimg$l_dyn_seg;
	  if (ldrisd)
	    TERM_FAO ("   dynamic            !XA-!XA!/",
		      ldrisd->ldrisd$p_base,
		      (unsigned __int64) ldrisd->ldrisd$p_base 
		      + ldrisd->ldrisd$i_len - 1);
	}
    }

  if (has_threads)
    threads_init ();

  /* Wait for connection.  */
  sock_init ();

  /* Set primary exception vector.  */
  {
    unsigned int status;
    status = sys$setexv (0, excp_handler, PSL$C_USER, (__void_ptr32) &prevhnd);
    if (!(status & STS$M_SUCCESS))
      LIB$SIGNAL (status);
  }

  if (is_attached)
    {
      return excp_handler ((struct chf$signal_array *) progxfer[2],
			   (struct chf$mech_array *) progxfer[3]);
    }

  /* Change first instruction to set a breakpoint.  */
  {
    /*
      01 08 00 40 00 00 	[MII]       break.m 0x80001
      00 00 00 02 00 00 	            nop.i 0x0
      00 00 04 00       	            nop.i 0x0;;
    */
    static const unsigned char initbp[16] =
      { 0x01, 0x08, 0x00, 0x40, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x02, 0x00, 0x00,
	0x00, 0x00, 0x04, 0x00 };
    unsigned int entry_prot;
    unsigned int status;
    
    status = page_set_rw (entry_pc, 16, &entry_prot);

    if (!(status & STS$M_SUCCESS))
      {
	if ((status & STS$M_COND_ID) == (SS$_NOT_PROCESS_VA & STS$M_COND_ID))
	  {
	    /* Cannot write here.  This can happen when pthreads are
	       used.  */
	    entry_pc = 0;
	    term_puts ("gdbstub: cannot set breakpoint on entry\n");
	  }
	else
	  LIB$SIGNAL (status);
      }
    
    if (entry_pc != 0)
      {
	ots$move (entry_saved, 16, (void *)entry_pc);
	ots$move ((void *)entry_pc, 16, (void *)initbp);
	__fc (entry_pc);
	page_restore_rw (entry_pc, 16, entry_prot);
      }
  }

  /* If it wasn't possible to set a breakpoint on the entry point,
     accept gdb commands now.  Note that registers are not updated.  */
  if (entry_pc == 0)
    {
      while (one_command () == 0)
	;
    }

  /* We will see!  */
  return SS$_CONTINUE;
}

/* Declare the entry point of this relocatable module.  */

struct xfer_vector
{
  __int64 impure_start;
  __int64 impure_end;
  int (*entry) ();
};

#pragma __extern_model save
#pragma __extern_model strict_refdef "XFER_PSECT"
struct xfer_vector xfer_vector = {0, 0, stub_start};
#pragma __extern_model restore