/* GDB stub for Itanium OpenVMS Copyright (C) 2012-2024 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 . */ /* 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 through 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 the 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, ®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 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