#include <ssdef.h>
#include <stsdef.h>
+int debuggable = 0; /* 1 if we can debug or show a call trace */
+
void FDECL(vms_exit, (int));
void NDECL(vms_abort);
-extern int FDECL(vms_define, (const char *,const char *,int));
-
-extern void lib$signal( /*_ unsigned long,... _*/ );
+/* first arg should be unsigned long but <lib$routines.h> has unsigned int */
+extern void VDECL(lib$signal, (unsigned,...));
+/* terminate, converting Unix-style exit code into VMS status code */
void
vms_exit(status)
int status;
{
+ /* convert non-zero to failure, zero to success */
exit(status ? (SS$_ABORT | STS$M_INHIB_MSG) : SS$_NORMAL);
+ /* NOT REACHED */
}
+/* put the user into the debugger; used for abort() when in wizard mode */
void
vms_abort()
{
- lib$signal(SS$_DEBUG);
-}
+ if (debuggable)
+ lib$signal(SS$_DEBUG);
-#ifdef PANICTRACE
-void
-vms_traceback(how)
-int how; /* 1: exit after traceback; 2: stay in debugger */
-{
- /* signal handler expects first byte to hold length of the rest */
- char dbgcmd[1+255];
-
- dbgcmd[0] = dbgcmd[1] = '\0';
- if (how == 2) {
- /* limit output to 18 stack frames to avoid longer output causing
- nethack's panic prolog from scrolling off conventional sized
- screen; perhaps we should adapt to termcap LI here... */
- (void)strcpy(dbgcmd, "#set Module/Calls; show Calls 18");
- } else if (how == 1) {
- /*
- * Suppress most of debugger's initial feedback to avoid scaring users.
- */
- /* start up with output going to /dev/null instead of stdout */
- (void)vms_define("DBG$OUTPUT", "_NL:", 0);
- /* bypass any debugger initialization file the user might have */
- (void)vms_define("DBG$INIT", "_NL:", 0);
- /* force tty interface by suppressing DECwindows/Motif interface */
- (void)vms_define("DBG$DECW$DISPLAY", " ", 0);
- /* once started, send output to log file on stdout */
- (void)strcpy(dbgcmd, "#set Log SYS$OUTPUT:; set output Log,noTerminal");
- /* FIXME: the trailing exit command here is actually being ignored,
- leaving us at the DBG> prompt contrary to our intent... */
- (void)strcat(dbgcmd, "; set Module/Calls; show Calls 18; exit");
- }
-
- if (dbgcmd[1]) {
- /* plug in command's length; debugger's signal handler expects ASCIC
- counted string rather than C-style ASCIZ 0-terminated string */
- dbgcmd[0] = (char)strlen(&dbgcmd[1]);
- /*
- * This won't work if we've been linked /noTraceback, and
- * we have to link /noTraceback if nethack.exe is going
- * to be installed with privileges, so this is of dubious
- * value for a SECURE multi-user playground installation.
- *
- * TODO: What's worse, we need to add a condition handler
- * to trap the resulting "improperly handled condition"
- * and the annoying and/or frightening (and in this case,
- * useless) register dump given when the debugger can't be
- * activated for a noTraceback executable.
- */
- (void)lib$signal(SS$_DEBUG, 1, dbgcmd);
- }
-
- vms_exit(2); /* don't return to caller */
+ /* we'll get here if the debugger isn't available, or if the user
+ uses GO to resume execution instead of EXIT to quit */
+ vms_exit(2); /* don't return to caller (2==arbitrary non-zero) */
/* NOT REACHED */
}
-#endif
+ /*
+ * Caveat: the VERYOLD_VMS configuration hasn't been tested in many years.
+ */
#ifdef VERYOLD_VMS
#include "oldcrtl.c"
#endif
/* NetHack 3.5 vmsunix.c $Date$ $Revision$ */
-/* SCCS Id: @(#)vmsunix.c 3.5 2006/12/09 */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/* NetHack may be freely redistributed. See license for details. */
#endif
#include <ctype.h>
+extern int debuggable; /* defined in vmsmisc.c */
+
+extern void VDECL(lib$signal, (unsigned,...));
extern unsigned long sys$setprv();
extern unsigned long lib$getdvi(), lib$getjpi(), lib$spawn(), lib$attach();
extern unsigned long smg$init_term_table_by_type(), smg$del_term_table();
#define vms_ok(sts) ((sts) & 1) /* odd => success */
+/* this could be static; it's only used within this file;
+ it won't be used at all if C_LIB$INTIALIZE gets commented out below,
+ so make it global so that compiler won't complain that it's not used */
+int FDECL(vmsexeini, (const void *,const void *,const unsigned char *));
+
static int FDECL(veryold, (int));
static char *NDECL(verify_term);
#if defined(SHELL) || defined(SUSPEND)
}
#endif /* SELECTSAVED */
+#ifdef PANICTRACE
+/* nethack has detected an internal error; try to give a trace of call stack */
+void
+vms_traceback(how)
+int how; /* 1: exit after traceback; 2: stay in debugger */
+{
+ /* assumes that a static initializer applies to the first union
+ field and that no padding will be placed between len and str */
+ union dbgcmd {
+ struct ascic {
+ unsigned char len; /* 8-bit length prefix */
+ char str[79]; /* could be up to 255, but we don't need that much */
+ } cmd_fields;
+ char cmd[1+79];
+ };
+#define DBGCMD(arg) { (unsigned char)(sizeof arg - sizeof ""), arg }
+ static union dbgcmd dbg[3] = {
+ /* prologue for less verbose feedback (when combined with
+ $ define/User_mode dbg$output _NL: ) */
+ DBGCMD("set Log SYS$OUTPUT: ; set Output Log,noTerminal,noVerify"),
+ /* enable modules with calls present on stack, then show those calls;
+ limit traceback to 18 stack frames to avoid scrolling off screen
+ (could check termcap LI and maybe give more, but we're operating
+ in a last-gasp environment so apply the KISS principle...) */
+ DBGCMD("set Module/Calls ; show Calls 18"),
+ /* epilogue; "exit" ends the sequence it's part of, but it doesn't
+ seem able to cause program termination end when used separately;
+ instead of relying on it, we'll redirect debugger input to come
+ from the null device so that it'll get an end-of-input condition
+ when it tries to get a command from the user */
+ DBGCMD("exit"),
+ };
+#undef DBGCMD
+
+ /*
+ * If we've been linked /noTraceback then we can't provide any
+ * trace of the call stack. Linking that way is required if
+ * nethack.exe is going to be installed with privileges, so the
+ * SECURE configuration usually won't have any trace feedback.
+ */
+ if (!debuggable) {
+ ; /* debugger not available to catch lib$signal(SS$_DEBUG) */
+ } else if (how == 2) {
+ /* omit prologue and epilogue (dbg[0] and dbg[2]) */
+ (void)lib$signal(SS$_DEBUG, 1, dbg[1].cmd);
+ } else if (how == 1) {
+ /*
+ * Suppress most of debugger's initial feedback to avoid scaring
+ * users (and scrolling panic message off the screen). Also control
+ * debugging environment to try to prevent unexpected complications.
+ */
+ /* start up with output going to /dev/null instead of stdout;
+ once started, output is sent to log file that's actually stdout */
+ (void)vms_define("DBG$OUTPUT", "_NL:", 0);
+ /* take input from null device so debugger will see end-on-input
+ and quit if/when it tries to get a command from the user */
+ (void)vms_define("DBG$INPUT", "_NL:", 0);
+ /* bypass any debugger initialization file the user might have */
+ (void)vms_define("DBG$INIT", "_NL:", 0);
+ /* force tty interface by suppressing DECwindows/Motif interface */
+ (void)vms_define("DBG$DECW$DISPLAY", " ", 0);
+ /* raise an exception for the debugger to catch */
+ (void)lib$signal(SS$_DEBUG, 3, dbg[0].cmd, dbg[1].cmd, dbg[2].cmd);
+ }
+
+ vms_exit(2); /* don't return to caller (2==arbitrary non-zero) */
+ /* NOT REACHED */
+}
+#endif /* PANICTRACE */
+
+ /*
+ * Play Hunt the Wumpus to see whether the debugger lurks nearby.
+ * It all takes place before nethack even starts, and sets up
+ * `debuggable' to control possible use of lib$signal(SS$_DEBUG).
+ */
+typedef unsigned FDECL((*condition_handler), (unsigned *,unsigned *));
+extern condition_handler FDECL(lib$establish, (condition_handler));
+extern unsigned FDECL(lib$sig_to_ret, (unsigned *,unsigned *));
+
+/* SYS$IMGSTA() is not documented: if called at image startup, it controls
+ access to the debugger; fortunately, the linker knows now to find it
+ without needing to link against sys.stb (VAX) or use LINK/System (Alpha).
+ We won't be calling it, but we indirectly check whether it has already
+ been called by checking if nethack.exe has it as a transfer address. */
+extern unsigned FDECL(sys$imgsta, ());
+
+/*
+ * These structures are in header files contained in sys$lib_c.tlb,
+ * but that isn't available on sufficiently old versions of VMS.
+ * Construct our own: partly stubs, with simpler field names and
+ * without ugly unions. Contents derived from Bliss32 definitions
+ * in lib.req and/or Macro32 definitions in lib.mlb.
+ */
+struct ihd { /* (vax) image header, $IHDDEF */
+ unsigned short size, activoff;
+ unsigned char otherstuff[512-4];
+};
+struct eihd { /* extended image header, $EIHDDEF */
+ unsigned long majorid, minorid, size, isdoff, activoff;
+ unsigned char otherstuff[512-20];
+};
+struct iha { /* (vax) image header activation block, $IHADEF */
+ unsigned long trnadr1, trnadr2, trnadr3;
+ unsigned long fill_, inishr;
+};
+struct eiha { /* extended image header activation block, $EIHADEF */
+ unsigned long size, spare;
+ unsigned long trnadr1[2], trnadr2[2], trnadr3[2], trnadr4[2], inishr[2];
+};
+
+ /*
+ * We're going to use lib$initialize, not because we need or
+ * want to be called before main(), but because one of the
+ * arguments passed to a lib$initialize callback is a pointer
+ * to the image header (somewhat complex data structure which
+ * includes the memory location(s) of where to start executing)
+ * of the program being initialized. It comes in two flavors,
+ * one used by VAX and the other by Alpha and IA64.
+ *
+ * An image can have up to three transfer addresses; one of them
+ * decides whether to run under debugger control (RUN/Debug, or
+ * LINK/Debug + plain RUN), another handles lib$initialize calls
+ * if that's used, and the last is to start the program itself
+ * (a jacket built around main() for code compiled with DEC C).
+ * They aren't always all present; some might be zero/null.
+ * A shareable image (pre-linked library) usually won't have any,
+ * but can have a separate initializer (not of interest here).
+ *
+ * The transfer targets don't have fixed slots but do occur in a
+ * particular order:
+ * link link lib$initialize lib$initialize
+ * sharable /noTrace /Trace + /noTrace + /Traceback
+ * 1: (none) main debugger init-handler debugger
+ * 2: main main init-handler
+ * 3: main
+ *
+ * We check whether the first transfer address is SYS$IMGSTA().
+ * If it is, the debugger should be available to catch SS$_DEBUG
+ * exception even when we don't start up under debugger control.
+ * One extra complication: if we *do* start up under debugger
+ * control, the first address in the in-memory copy of the image
+ * header will be changed from sys$imgsta() to a value in system
+ * space. [I don't know how to reference that one symbolically,
+ * so I'm going to treat any address in system space as meaning
+ * that the debugger is available. pr]
+ */
+
+/* called via lib$initialize during image activation: before main() and
+ with magic arguments; C run-time library won't be initialized yet */
+/*ARGSUSED*/
+int
+vmsexeini(inirtn_unused, clirtn_unused, imghdr)
+const void *inirtn_unused, *clirtn_unused;
+const unsigned char *imghdr;
+{
+ const struct ihd *vax_hdr;
+ const struct eihd *axp_hdr;
+ const struct iha *vax_xfr;
+ const struct eiha *axp_xfr;
+ unsigned long trnadr1;
+
+ (void)lib$establish(lib$sig_to_ret); /* set up condition handler */
+ /*
+ * Check the first of three transfer addresses to see whether
+ * it is SYS$IMGSTA(). Note that they come from a file,
+ * where they reside as longword or quadword integers rather
+ * than function pointers. (Basically just a C type issue;
+ * casting back and forth between integer and pointer doesn't
+ * change any bits for the architectures VMS runs on.)
+ */
+ debuggable = 0;
+ /* start with a guess rather than bothering to figure out architecture */
+ vax_hdr = (struct ihd *)imghdr;
+ if (vax_hdr->size >= 512) {
+ /* this is a VAX-specific header; addresses are longwords */
+ vax_xfr = (struct iha *)(imghdr + vax_hdr->activoff);
+ trnadr1 = vax_xfr->trnadr1;
+ } else {
+ /* the guess above was wrong; imghdr's first word is not
+ the size field, it's a version number component */
+ axp_hdr = (struct eihd *)imghdr;
+ /* this is an Alpha or IA64 header; addresses are quadwords
+ but we ignore the upper half which will be all 0's or 0xF's
+ (we hope; if not, assume it still won't matter for this test) */
+ axp_xfr = (struct eiha *)(imghdr + axp_hdr->activoff);
+ trnadr1 = axp_xfr->trnadr1[0];
+ }
+ if ((unsigned (*)())trnadr1 == sys$imgsta ||
+ /* check whether first transfer address points to system space
+ [we want (trnadr1 >= 0x80000000UL) but really old compilers
+ don't support the UL suffix, so do a signed compare instead] */
+ (long)trnadr1 < 0L) debuggable = 1;
+ return 1; /* success (return value here doesn't actually matter) */
+}
+
+/*
+ * Setting up lib$initialize transfer block is trivial with Macro32,
+ * but we don't want to introduce use of assembler code. Doing it
+ * with C requires jiggery-pokery here and again when linking, and
+ * may not work with some compiler versions. The lib$initialize
+ * transfer block is an open-ended array of 32-bit routine addresses
+ * in a psect named "lib$initialize" with particular attributes (one
+ * being "concatenate" so that multiple instances of lib$initialize
+ * are appended rather than overwriting each other).
+ *
+ * VAX C made global variables become named program sections, to be
+ * compatable with Fortran COMMON blocks, simplifying mixed-language
+ * programs. GNU C for VAX/VMS did the same, to be compatable with
+ * VAX C. By default, DEC C makes global variables be global symbols
+ * instead, with its /Extern_Model=Relaxed_Ref_Def mode, but can be
+ * told to be VAX C compatable by using /Extern_Model=Common_Block.
+ *
+ * We don't want to force that for the whole program; occasional use
+ * of /Extern_Model=Strict_Ref_Def to find mistakes is too useful.
+ * Also, using symbols instead of psects is more robust when linking
+ * with an object library if the module defining the symbol contains
+ * only data. With a psect, any declaration is enough to become a
+ * definition and the linker won't bother hunting through a library
+ * to find another one unless explicitly told to do so. Bad news
+ * if that other one happens to include the intended initial value
+ * and someone bypasses `make' to link interactively but neglects
+ * to give the linker enough explicit directions. Linking like that
+ * would work, but the program wouldn't.
+ *
+ * So, we switch modes for this hack only. Besides, psect attributes
+ * for lib$initialize are different from the ones used for ordinary
+ * variables, so we'd need to resort to some linker magic anyway.
+ * (With assembly language, in addtion to having full control of the
+ * psect attributes in the source code, Macro32 would include enough
+ * information in its object file such that linker wouldn't need any
+ * extra instructions from us to make this work.) [If anyone links
+ * manually now and neglects the esoteric details, vmsexeini() won't
+ * get called and `debuggable' will stay 0, so lib$signal(SS$_DEBUG)
+ * will be avoided even when its use is viable. But the program will
+ * still work correctly.]
+ */
+#define C_LIB$INITIALIZE /* comment out if this won't compile... */
+ /* (then `debuggable' will always stay 0) */
+#ifdef C_LIB$INITIALIZE
+# ifdef __DECC
+# pragma extern_model save /* push current mode */
+# pragma extern_model common_block /* set new mode */
+# endif
+/* values are 32-bit function addresses; pointers might be 64 so avoid them */
+extern const unsigned long lib$initialize[1]; /* size is actually variable */
+const unsigned long lib$initialize[] = { (unsigned long)(void *)vmsexeini };
+# ifdef __DECC
+# pragma extern_model restore /* pop previous mode */
+# endif
+/* We also need to link against a linker options file containing:
+sys$library:starlet.olb/Include=(lib$initialize)
+psect_attr=lib$initialize, Con,Usr,noPic,Rel,Gbl,noShr,noExe,Rd,noWrt,Long
+ */
+#endif /* C_LIB$INITIALIZE */
+ /* End of debugger hackery. */
/*vmsunix.c*/