From d80fcaada491585107d0e0ea6b99baadccaa559e Mon Sep 17 00:00:00 2001 From: keni Date: Fri, 15 Jan 2010 19:54:37 +0000 Subject: [PATCH] PANICTRACE (stacktrace on panic or signal) + bits On crash signal or panic(), use a configurable method to get a stacktrace the user can easily report to us. Currently only for Unix/Linux and only ifdef BETA. Hopefully ports can add additional methods. Bits: - linux hints file had PREFIX definition in the wrong place - sample sysconf file used wrong delimiter for WIZARDS - fix grammar error in support message when using sysconf.wizards - options.c comment typo - capitalize "Crash test" output from #panic command --- include/decl.h | 5 +- include/extern.h | 3 + include/global.h | 15 +++- include/sys.h | 9 ++- src/cmd.c | 2 +- src/decl.c | 4 + src/end.c | 181 +++++++++++++++++++++++++++++++++++++++++-- src/files.c | 18 +++++ src/options.c | 3 +- src/pager.c | 3 +- src/sys.c | 18 ++++- sys/unix/hints/linux | 10 ++- sys/unix/sysconf | 13 +++- sys/unix/unixmain.c | 12 +++ 14 files changed, 277 insertions(+), 19 deletions(-) diff --git a/include/decl.h b/include/decl.h index a1a1c2cb9..021fa51f3 100644 --- a/include/decl.h +++ b/include/decl.h @@ -1,5 +1,4 @@ /* NetHack 3.5 decl.h $Date$ $Revision$ */ -/* SCCS Id: @(#)decl.h 3.5 2008/07/20 */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -390,6 +389,10 @@ struct autopickup_exception { }; #endif /* AUTOPICKUP_EXCEPTIONS */ +#ifdef PANICTRACE +E char *ARGV0; +#endif + #undef E #endif /* DECL_H */ diff --git a/include/extern.h b/include/extern.h index 5d5e29413..a8aff13ae 100644 --- a/include/extern.h +++ b/include/extern.h @@ -643,6 +643,9 @@ E void FDECL(dealloc_killer, (struct kinfo*)); E void FDECL(save_killers, (int,int)); E void FDECL(restore_killers, (int)); E char *FDECL(build_english_list, (char *)); +#if defined(PANICTRACE) && !defined(NO_SIGNAL) +E void FDECL(panictrace_setsignals, (boolean)); +#endif /* ### engrave.c ### */ diff --git a/include/global.h b/include/global.h index 3ffb8df71..dc9a87d1d 100644 --- a/include/global.h +++ b/include/global.h @@ -1,5 +1,4 @@ /* NetHack 3.5 global.h $Date$ $Revision$ */ -/* SCCS Id: @(#)global.h 3.5 2007/01/12 */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -366,4 +365,18 @@ struct savefile_info { #define MAXMONNO 120 /* extinct monst after this number created */ #define MHPMAX 500 /* maximum monster hp */ +#ifdef BETA +/* see end.c */ +# ifndef PANICTRACE +# define PANICTRACE +# endif +#endif +/* The following are meaningless if PANICTRACE is not defined: */ +#if defined(__linux__) && defined(__GLIBC__) && (__GLIBC__ >= 2) +# define PANICTRACE_GLIBC +#endif +#ifdef UNIX +# define PANICTRACE_GDB +#endif + #endif /* GLOBAL_H */ diff --git a/include/sys.h b/include/sys.h index 78a7cd4c8..8002538ca 100644 --- a/include/sys.h +++ b/include/sys.h @@ -1,5 +1,4 @@ /* NetHack 3.5 sys.h $Date$ $Revision$ */ -/* SCCS Id: @(#)sys.h 3.5 2008/01/30 */ /* Copyright (c) Kenneth Lorber, Kensington, Maryland, 2008. */ /* NetHack may be freely redistributed. See license for details. */ @@ -21,6 +20,14 @@ struct sysopt { int pers_is_uid; int entrymax; int pointsmin; +#ifdef PANICTRACE + /* panic options */ + char *gdbpath; + int panictrace_gdb; +# ifdef PANICTRACE_GLIBC + int panictrace_glibc; +# endif +#endif }; E struct sysopt sysopt; diff --git a/src/cmd.c b/src/cmd.c index 878ab5565..6ce225935 100644 --- a/src/cmd.c +++ b/src/cmd.c @@ -682,7 +682,7 @@ STATIC_PTR int wiz_panic(VOID_ARGS) { if (yn("Do you want to call panic() and end your game?") == 'y') - panic("crash test."); + panic("Crash test."); return 0; } diff --git a/src/decl.c b/src/decl.c index 021e4c958..7009b5da1 100644 --- a/src/decl.c +++ b/src/decl.c @@ -326,6 +326,10 @@ NEARDATA struct savefile_info sfrestinfo, sfsaveinfo = { #endif }; +#ifdef PANICTRACE +char *ARGV0; +#endif + /* dummy routine used to force linkage */ void decl_init() diff --git a/src/end.c b/src/end.c index 0071dc99e..7b7ef6f73 100644 --- a/src/end.c +++ b/src/end.c @@ -1,5 +1,4 @@ /* NetHack 3.5 end.c $Date$ $Revision$ */ -/* SCCS Id: @(#)end.c 3.5 2008/02/08 */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -61,20 +60,190 @@ extern void FDECL(nethack_exit,(int)); #define done_stopprint program_state.stopprint +#ifndef PANICTRACE +# define NH_abort NH_abort_ +#endif + #ifdef AMIGA -# define NH_abort() Abort(0) +# define NH_abort_() Abort(0) #else # ifdef SYSV -# define NH_abort() (void) abort() +# define NH_abort_() (void) abort() # else # ifdef WIN32 -# define NH_abort() win32_abort() +# define NH_abort_() win32_abort() # else -# define NH_abort() abort() +# define NH_abort_() abort() # endif # endif #endif +#ifdef PANICTRACE +# include +# ifdef PANICTRACE_GLIBC +# include +# endif + +/* What do we try and in what order? Tradeoffs: + * glibc: +no external programs required + * -requires newish glibc + * -requires -rdynamic + * gdb: +gives more detailed information + * +works on more OS versions + * -requires -g, which may preclude -O on some compilers + */ +# ifdef SYSCF +# define SYSOPT_PANICTRACE_GDB sysopt.panictrace_gdb +# ifdef PANICTRACE_GLIBC +# define SYSOPT_PANICTRACE_GLIBC sysopt.panictrace_glibc +# else +# define SYSOPT_PANICTRACE_GLIBC 0 +# endif +# else +# define SYSOPT_PANICTRACE_GDB (nh_getenv("NETHACK_USE_GDB")==0?0:2) +# ifdef PANICTRACE_GLIBC +# define SYSOPT_PANICTRACE_GLIBC 1 +# else +# define SYSOPT_PANICTRACE_GLIBC 0 +# endif +# endif + +static void NDECL(NH_abort); +#ifndef NO_SIGNAL +static void FDECL(panictrace_handler, (int)); +#endif +static boolean NDECL(NH_panictrace_glibc); +static boolean NDECL(NH_panictrace_gdb); + +#ifndef NO_SIGNAL +/*ARGSUSED*/ +void +panictrace_handler(sig_unused) /* called as signal() handler, so sent at least one arg */ +int sig_unused; +{ +# define SIG_MSG "\nSignal received.\n" + (void) write(2, SIG_MSG, sizeof(SIG_MSG)-1); + NH_abort(); +} + +void +panictrace_setsignals(set) +boolean set; +{ +# define SETSIGNAL(sig) (void) signal(sig, set?(SIG_RET_TYPE)panictrace_handler:SIG_DFL); +# ifdef SIGILL + SETSIGNAL(SIGILL); +# endif +# ifdef SIGTRAP + SETSIGNAL(SIGTRAP); +# endif +# ifdef SIGIOT + SETSIGNAL(SIGIOT); +# endif +# ifdef SIGBUS + SETSIGNAL(SIGBUS); +# endif +# ifdef SIGFPE + SETSIGNAL(SIGFPE); +# endif +# ifdef SIGSEGV + SETSIGNAL(SIGSEGV); +# endif +# ifdef SIGSTKFLT + SETSIGNAL(SIGSTKFLT); +# endif +# ifdef SIGSYS + SETSIGNAL(SIGSYS); +# endif +# ifdef SIGEMT + SETSIGNAL(SIGEMT); +# endif +# undef SETSIGNAL +} +#endif + +static void +NH_abort(){ + int gdb_prio = SYSOPT_PANICTRACE_GDB; + int glibc_prio = SYSOPT_PANICTRACE_GLIBC; + static boolean aborting = FALSE; + + if(aborting) return; + aborting = TRUE; + + if(gdb_prio == glibc_prio && gdb_prio > 0) gdb_prio++; + + if(gdb_prio > glibc_prio){ + NH_panictrace_gdb() || (glibc_prio && NH_panictrace_glibc()); + } else { + NH_panictrace_glibc() || (gdb_prio && NH_panictrace_gdb()); + } + + panictrace_setsignals(FALSE); + NH_abort_(); +} + +static boolean +NH_panictrace_glibc(){ +# ifdef PANICTRACE_GLIBC + void *bt[20]; + size_t count; + char **info; + int x; + + raw_print("Generating more information you may report:\n"); + count = backtrace(bt, SIZE(bt)); + info = backtrace_symbols(bt, count); + for(x=0; x&1 | /bin/grep '^#'", + GDBPATH, ARGV0, getpid()); + FILE *gdb = popen(buf, "w"); + if(gdb){ + raw_print("Generating more information you may report:\n"); + fprintf(gdb, "bt\nquit\ny"); + fflush(gdb); + sleep(4); /* ugly */ + pclose(gdb); + return TRUE; + } else { + return FALSE; + } +# else + return FALSE; +# endif +} +#endif + /* * The order of these needs to match the macros in hack.h. */ @@ -295,7 +464,7 @@ int how; } #if defined(WIN32) && !defined(SYSCF) -#define NOTIFY_NETHACK_BUGS +# define NOTIFY_NETHACK_BUGS #endif /*VARARGS1*/ diff --git a/src/files.c b/src/files.c index 13fe43ff5..efce344b2 100644 --- a/src/files.c +++ b/src/files.c @@ -2119,6 +2119,24 @@ int src; return 0; } sysopt.pointsmin = temp; + } else if ( (src==SET_IN_SYS) && match_varname(buf, "PANICTRACE_GLIBC", 16)) { + int temp = atoi(bufp); + if(temp < 1 || temp > 2){ + raw_printf("Illegal value in PANICTRACE_GLIBC (not 0,1,2)."); + return 0; + } + sysopt.panictrace_glibc = temp; + } else if ( (src==SET_IN_SYS) && match_varname(buf, "PANICTRACE_GDB", 14)) { + int temp = atoi(bufp); + if(temp < 1 || temp > 2){ + raw_printf("Illegal value in PANICTRACE_GDB (not 0,1,2)."); + return 0; + } + sysopt.panictrace_gdb = temp; + } else if ( (src==SET_IN_SYS) && match_varname(buf, "GDBPATH", 7)) { + if(sysopt.gdbpath) free(sysopt.gdbpath); + sysopt.gdbpath = (char*)alloc(strlen(bufp)+1); + Strcpy(sysopt.gdbpath, bufp); #endif } else if (match_varname(buf, "BOULDER", 3)) { (void) get_uchars(fp, buf, bufp, &iflags.bouldersym, TRUE, diff --git a/src/options.c b/src/options.c index 677715ce0..c6cbcf175 100644 --- a/src/options.c +++ b/src/options.c @@ -1,5 +1,4 @@ /* NetHack 3.5 options.c $Date$ $Revision$ */ -/* SCCS Id: @(#)options.c 3.5 2008/08/22 */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -532,7 +531,7 @@ boolean val_allowed; /* most environment variables will eventually be printed in an error * message if they don't work, and most error message paths go through * BUFSZ buffers, which could be overflowed by a maliciously long - * environment variable. if a variable can legitimately be long, or + * environment variable. If a variable can legitimately be long, or * if it's put in a smaller buffer, the responsible code will have to * bounds-check itself. */ diff --git a/src/pager.c b/src/pager.c index bb969d98b..22cc5accc 100644 --- a/src/pager.c +++ b/src/pager.c @@ -1,5 +1,4 @@ /* NetHack 3.5 pager.c $Date$ $Revision$ */ -/* SCCS Id: @(#)pager.c 3.5 2009/01/30 */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -897,7 +896,7 @@ docontact() putstr(cwin, 0, ""); } else if(sysopt.wizards){ char *tmp = build_english_list(sysopt.wizards); - Sprintf(buf, "To contact local support, %s", tmp); + Sprintf(buf, "To contact local support, contact %s.", tmp); free(tmp); putstr(cwin, 0, buf); putstr(cwin, 0, ""); diff --git a/src/sys.c b/src/sys.c index 1fee333a3..b7b43f880 100644 --- a/src/sys.c +++ b/src/sys.c @@ -24,11 +24,27 @@ sys_early_init(){ sysopt.pointsmin = POINTSMIN; sysopt.pers_is_uid = PERS_IS_UID; - /* sanity checks */ + /* sanity checks */ if(PERSMAX<1) sysopt.persmax = 1; if(ENTRYMAX<10) sysopt.entrymax = 10; if(POINTSMIN<1) sysopt.pointsmin = 1; if(PERS_IS_UID != 0 && PERS_IS_UID != 1) panic("config error: PERS_IS_UID must be either 0 or 1"); + +#ifdef PANICTRACE + /* panic options */ + sysopt.gdbpath = NULL; +# ifdef BETA + sysopt.panictrace_gdb = 1; +# ifdef PANICTRACE_GLIBC + sysopt.panictrace_glibc = 2; +# endif +# else + sysopt.panictrace_gdb = 0; +# ifdef PANICTRACE_GLIBC + sysopt.panictrace_glibc = 0; +# endif +# endif +#endif } diff --git a/sys/unix/hints/linux b/sys/unix/hints/linux index 37f137bd7..9ff45ff00 100644 --- a/sys/unix/hints/linux +++ b/sys/unix/hints/linux @@ -9,15 +9,19 @@ # for Ubuntu dapper. -HACKDIR=$(PREFIX)/games/lib/$(GAME)dir -SHELLDIR = $(PREFIX)/games #PREFIX=/usr PREFIX=$(wildcard ~)/nh/install +HACKDIR=$(PREFIX)/games/lib/$(GAME)dir +SHELLDIR = $(PREFIX)/games -CFLAGS=-O -I../include -DNOTPARMDECL $(CFLAGS1) -DDLB +CFLAGS=-g -O -I../include -DNOTPARMDECL $(CFLAGS1) -DDLB CFLAGS1=-DCOMPRESS=\"/bin/gzip\" -DCOMPRESS_EXTENSION=\".gz\" +CFLAGS+=-DSYSCF -DSYSCF_FILE=\"$(HACKDIR)/sysconf\" -DSECURE +CFLAGS+=-DHACKDIR=\"$(HACKDIR)\" LINK=$(CC) +# Only needed for GLIBC stack trace: +LFLAGS=-rdynamic WINSRC = $(WINTTYSRC) WINOBJ = $(WINTTYOBJ) diff --git a/sys/unix/sysconf b/sys/unix/sysconf index 644339098..4090e223f 100644 --- a/sys/unix/sysconf +++ b/sys/unix/sysconf @@ -7,7 +7,7 @@ # Which users can use WIZARD (debugging) mode (the -D flag). # A value of * allows anyone to enter debugging mode. -WIZARDS=root,games +WIZARDS=root games # Users allowed to use the ! (shell escape) command or to suspend the game. # Uses the same syntax as the WIZARDS option above. @@ -35,3 +35,14 @@ MAXPLAYERS=10 # Determine identity of "person" in the score file with name (0) or # numeric (1) user id. #PERS_IS_UID=1 + +# Try to get more info in case of a program bug or crash. Using GDB can +# get more information and works on more systems but requires gdb be available; +# using GLIBC only works if NetHack is linked with glibc. Both require +# certain compilation options. See src/end.c and sys/unix/hints/* for +# more information. +GDBPATH=/usr/bin/gdb +# Values are priorities: 0 - do not use this method, 1 - low priority, +# 2 - high priority. Non-zero priority methods are tried in order. +PANICTRACE_GDB=1 +PANICTRACE_GLIBC=2 diff --git a/sys/unix/unixmain.c b/sys/unix/unixmain.c index 02b8ac45d..7e3342a44 100644 --- a/sys/unix/unixmain.c +++ b/sys/unix/unixmain.c @@ -139,6 +139,12 @@ char *argv[]; initoptions_init(); read_config_file(SYSCF_FILE, SET_IN_SYS); initoptions_finish(); +#endif +#ifdef PANICTRACE + ARGV0 = argv[0]; /* save for possible stack trace */ +# ifndef NO_SIGNAL + panictrace_setsignals(TRUE); +# endif #endif prscore(argc, argv); exit(EXIT_SUCCESS); @@ -164,6 +170,12 @@ char *argv[]; read_config_file(SYSCF_FILE, SET_IN_SYS); #endif initoptions_finish(); +#ifdef PANICTRACE + ARGV0 = argv[0]; /* save for possible stack trace */ +# ifndef NO_SIGNAL + panictrace_setsignals(TRUE); +# endif +#endif exact_username = whoami(); /*