be6d4f74c7
I wrote a little script to detect duplicate or commented-out #includes and ran it on gdb. This patch is the result. Tested by rebuilding. gdb/ChangeLog 2019-01-21 Tom Tromey <tom@tromey.com> * ui-out.c: Fix includes. * tui/tui-source.c: Fix includes. * target.c: Fix includes. * remote.c: Fix includes. * regcache.c: Fix includes. * python/py-block.c: Fix includes. * printcmd.c: Fix includes. * or1k-tdep.c: Fix includes. * mi/mi-main.c: Fix includes. * m32r-tdep.c: Fix includes. * csky-tdep.c: Fix includes. * compile/compile-cplus-types.c: Fix includes. * cli/cli-interp.c: Fix includes. gdb/gdbserver/ChangeLog 2019-01-21 Tom Tromey <tom@tromey.com> * tracepoint.c: Fix includes. * remote-utils.c: Fix includes. * linux-x86-low.c: Fix includes. gdb/stubs/ChangeLog 2019-01-21 Tom Tromey <tom@tromey.com> * ia64vms-stub.c: Fix includes.
2602 lines
65 KiB
C
2602 lines
65 KiB
C
/* GDB stub for Itanium OpenVMS
|
|
Copyright (C) 2012-2019 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 thead (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, ®s);
|
|
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 cheksum 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;
|
|
|
|
/* Completly 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
|