/* NetHack 3.7 report.c $NHDT-Date: 1741406837 2025/03/07 20:07:17 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.16 $ */ /* Copyright (c) Kenneth Lorber, Kensington, Maryland, 2024 */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" /* NB: CRASHREPORT implies PANICTRACE */ # ifndef NO_SIGNAL #include # endif # ifndef LONG_MAX #include # endif #include "dlb.h" #include #include # ifdef PANICTRACE_LIBC #include # endif #ifdef CRASHREPORT # ifdef WIN32 # define HASH_PRAGMA_START # define HASH_PRAGMA_END # else # define HASH_PRAGMA_START \ _Pragma("GCC diagnostic push"); \ _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") # define HASH_PRAGMA_END _Pragma("GCC diagnostic pop"); # endif # ifdef MACOS #include # define HASH_CONTEXTPTR(CTXP) \ unsigned char tmp[CC_MD4_DIGEST_LENGTH]; \ CC_MD4_CTX CTXP ## _; \ CC_MD4_CTX *CTXP = &CTXP ## _ # define HASH_INIT(ctxp) !CC_MD4_Init(ctxp) # define HASH_UPDATE(ctx, ptr, len) !CC_MD4_Update(ctx, ptr, len) # define HASH_FINISH(ctxp) !CC_MD4_Final(tmp, ctxp) # define HASH_RESULT_SIZE(ctxp) CC_MD4_DIGEST_LENGTH # define HASH_RESULT(ctx, inp) *inp = (unsigned char *) ctx # define HASH_CLEANUP(ctxp) # define HASH_OFLAGS O_RDONLY # define HASH_BINFILE_DECL char *binfile = argv[0]; # if (NH_DEVEL_STATUS == NH_STATUS_BETA) # define HASH_BINFILE() \ if (!binfile || !*binfile) { \ /* If this triggers, investigate CFBundleGetMainBundle */ \ /* or CFBundleCopyExecutableURL. */ \ raw_print( \ "BETA warning: crashreport_init called without useful info"); \ goto skip; \ } # else /* BETA */ # define HASH_BINFILE() \ if (!binfile || !*binfile) { \ goto skip; \ } # endif /* BETA */ # endif /* MACOS */ # ifdef __linux__ #include "nhmd4.h" /* v0 is just to suppress compiler warnings about unreachable code */ # define HASH_CONTEXTPTR(CTXP) \ volatile int v0 = 0; \ unsigned char tmp[NHMD4_DIGEST_LENGTH]; \ NHMD4_CTX CTXP ## _; \ NHMD4_CTX *CTXP = &CTXP ## _ # define HASH_INIT(ctxp) (nhmd4_init(ctxp), v0) # define HASH_UPDATE(ctx, ptr, len) (nhmd4_update(ctx, ptr, len), v0) # define HASH_FINISH(ctxp) (nhmd4_final(ctxp, tmp), v0) # define HASH_RESULT_SIZE(ctxp) NHMD4_RESULTLEN # define HASH_RESULT(ctx, inp) *inp = tmp # define HASH_CLEANUP(ctxp) # define HASH_OFLAGS O_RDONLY # define HASH_BINFILE_DECL char binfile[PATH_MAX+1]; # define HASH_BINFILE() \ int len = readlink("/proc/self/exe", binfile, sizeof binfile - 1); \ if (len > 0) { \ binfile[len] = '\0'; \ } else { \ goto skip; \ } # endif // __linux__ # ifdef WIN32 /* WIN32 takes too much code and is dependent on OS includes we can't * pull in here, so we call out to code in sys/windows/windsys.c */ # define HASH_CONTEXTPTR(CTXP) # define HASH_INIT(ctxp) win32_cr_helper('i', ctxp, NULL, 0) # define HASH_UPDATE(ctxp, ptr, len) win32_cr_helper('u', ctxp, ptr, len) # define HASH_FINISH(ctxp) win32_cr_helper('f', ctxp, NULL, 0) # define HASH_CLEANUP(ctxp) win32_cr_helper('c', ctxp, NULL, 0) # define HASH_RESULT_SIZE(ctxp) win32_cr_helper('s', ctxp, NULL, 0) # define HASH_RESULT(ctxp, inp) win32_cr_helper('r', ctxp, inp, 0) # define HASH_OFLAGS _O_RDONLY | _O_BINARY # define HASH_BINFILE_DECL char *binfile; # define HASH_BINFILE() \ if (win32_cr_helper('b', NULL, &binfile, 0)) { \ goto skip; \ } # endif // WIN32 /* Binary ID - Use only as a hint to contact.html for recognizing our own binaries. This is easily spoofed! */ static char bid[40]; /* ARGSUSED */ void crashreport_init(int argc, char *argv[]) { static int once = 0; if (once++) /* NetHackW.exe calls us twice */ return; HASH_BINFILE_DECL; HASH_PRAGMA_START HASH_CONTEXTPTR(ctxp); if (HASH_INIT(ctxp)) goto skip; HASH_BINFILE(); /* Does "goto skip" on error. */ int fd = open(binfile, HASH_OFLAGS, 0); if (fd == -1) { # ifdef BETA raw_printf("open e=%s", strerror(errno)); # endif goto skip; } int segsize; unsigned char segment[4096]; while (0 < (segsize = read(fd, segment, sizeof segment))) { if (HASH_UPDATE(ctxp, segment, segsize)) goto skip; } if (segsize < 0) { close(fd); goto skip; } if (HASH_FINISH(ctxp)) goto skip; close(fd); static const char hexdigits[] = "0123456789abcdef"; char *p = bid; unsigned char *in; HASH_RESULT(ctxp, &in); uint8 cnt = (uint8) HASH_RESULT_SIZE(ctxp); /* Just in case, make sure not to overflow the bid buffer. Divide size by 2 because each octet in the hash uses two slots in bid[] when formatted as a pair of hexadecimal digits. */ if (cnt >= (uint8) sizeof bid / 2) cnt = (uint8) sizeof bid / 2 - 1; while (cnt) { /* sprintf(p, "%02x", *in++), p += 2; */ *p++ = hexdigits[(*in >> 4) & 0x0f]; *p++ = hexdigits[*in++ & 0x0f]; --cnt; } *p = '\0'; HASH_CLEANUP(ctxp); return; skip: Strcpy(bid, "unknown"); HASH_CLEANUP(ctxp); HASH_PRAGMA_END nhUse(argc); nhUse(argv); } #undef HASH_CONTEXTPTR #undef HASH_INIT #undef HASH_UPDATE #undef HASH_FINISH #undef HASH_CLEANUP #undef HASH_RESULT #undef HASH_RESULT_SIZE #undef HASH_PRAGMA_START #undef HASH_PRAGMA_END #undef HASH_BINFILE_DECL #undef HASH_BINFILE void crashreport_bidshow(void) { # if defined(WIN32) && !defined(WIN32CON) if (0 == win32_cr_helper('D', ctxp, bid, 0)) # endif { raw_print(bid); # ifdef WIN32notyet wait_synch(); # endif } } /* Build a URL with a query string and try to launch a new browser window * to report from panic() or impossible(). Requires libc support for * the stacktrace. Uses memory on the stack to avoid memory allocation * (on most platforms) (but libc can still do anything it wants). */ // No theoretial limit for URL length but reality is messy. // This should work on all modern platforms. # ifndef MAX_URL # define MAX_URL 8192 # endif # ifndef SWR_FRAMES # define SWR_FRAMES 20 # endif // mark holds the initial eos; if we can't get the value in // then we can remove the whole item if desired. For other // semantics, caller can handle mark. #define SWR_ADD(str) \ utmp = strlen(str); \ mark = uend; \ if (utmp >= urem) \ goto full; \ memcpy(uend, str, utmp); \ uend += utmp; urem -= utmp; \ *uend = '\0'; // NB: on overflow this rolls us back to mark, so if we don't // want to roll back to the last SWR_ADD, update mark before // calling this macro. #define SWR_ADD_URIcoded(str) \ if (swr_add_uricoded(str, &uend, &urem, mark)) \ goto full; /* On overflow, truncate to markp (but only if markp != NULL). */ boolean swr_add_uricoded( const char *in, char **out, int *remaining, char *markp) { while (*in) { if (isalnum(*in) || strchr("_-.~", *in)) { **out = *in; (*out)++; (*remaining)--; } else if (*in == ' ') { **out = '+'; (*out)++; (*remaining)--; } else { if (*remaining <= 3) { if (markp) *out = markp, *remaining = 0; **out = '\0'; return TRUE; } else { char chr[40]; /* [4] should suffice */ int x; Sprintf(chr, "%%%02X", *in); x = (int) strlen(chr); if (x <= *remaining) { Strcpy(*out, chr); *out += x; *remaining -= x; } } } in++; if (!*remaining) { if (markp) *out = markp, *remaining = 0; **out = '\0'; return TRUE; } **out = '\0'; } return FALSE; /* normal return */ } static char url[MAX_URL]; // XXX too bad this isn't allocated as needed static int urem = MAX_URL; // adjusted for gc.crash_urlmax below static char *uend = url; static int utmp; // used inside macros static char *mark; // holds previous terminator (generally) boolean submit_web_report(int cos, const char *msg, const char *why) { urem = (gc.crash_urlmax < 0 || gc.crash_urlmax > MAX_URL) ? MAX_URL : min(MAX_URL,gc.crash_urlmax); char temp[200]; char temp2[200]; int countpp = 0; /* pre and post traceback lines */ // URL loaded for creating reports to the NetHack DevTeam // CRASHREPORTURL=https://nethack.org/links/cr-37BETA.html if (!sysopt.crashreporturl) return FALSE; SWR_ADD(sysopt.crashreporturl); /* * Note: all snprintf() calls here changed to sprintf() to avoid * complaints from static analyzer. All but one were unnecessary * since they were formatting int or unsigned into a large buffer. */ /* cos - operation, v - version */ Sprintf(temp, "?cos=%d&v=1", cos); SWR_ADD(temp); /* msg==NULL for #bugreport */ if (msg) { SWR_ADD("&subject="); Sprintf(temp, "%.40s report for NetHack %.40s", msg, version_string(temp2, sizeof temp2 )); SWR_ADD_URIcoded(temp); } SWR_ADD("&gitver="); SWR_ADD_URIcoded(getversionstring(temp2, sizeof temp2)); if (gc.crash_name) { SWR_ADD("&name="); SWR_ADD_URIcoded(gc.crash_name); } if(gc.crash_email) { SWR_ADD("&email="); SWR_ADD_URIcoded(gc.crash_email); } // hardware: leave for user // software: leave for user // comments: leave for user SWR_ADD("&details="); if (why) { SWR_ADD_URIcoded(why); SWR_ADD_URIcoded("\n"); mark = uend; countpp++; } SWR_ADD_URIcoded("bid: "); SWR_ADD_URIcoded(bid); SWR_ADD_URIcoded("\n"); mark = uend; countpp++; int count = 0; if (cos == 1) { # ifdef WIN32 count = win32_cr_gettrace(SWR_FRAMES, uend, MAX_URL - (uend - url)); uend = eos(url); # else void *bt[SWR_FRAMES]; int x; char **info; count = backtrace(bt, SIZE(bt)); info = backtrace_symbols(bt, count); for (x = 0; x < count; x++) { copynchars(temp, info[x], (int) sizeof temp - 1 - 1); /* \n\0 */ /* try to remove up to 16 blank spaces by removing 8 twice */ (void) strsubst(temp, " ", ""); (void) strsubst(temp, " ", ""); (void) strncat(temp, "\n", sizeof temp - 1); # if 0 // __linux__ // not needed for MacOS // XXX is it actually needed for linux? TBD Sprintf(temp2, "[%02lu]\n", (unsigned long) x); uend--; // remove the \n we added above SWR_ADD_URIcoded(temp2); # endif // linux SWR_ADD_URIcoded(temp); mark = uend; } # endif // !WIN32 } # ifdef DUMPLOG_CORE // config.h turns this on, but make it easy to turn off if needed if (cos == 1) { int k; SWR_ADD_URIcoded("Latest messages:\n"); mark=uend; countpp++; for (k = 0; k < 5; k++) { const char *line = get_saved_pline(k); if (!line) break; SWR_ADD_URIcoded(line); SWR_ADD_URIcoded("\n"); countpp++; mark = uend; } } # endif /* DUMPLOG_CORE */ // detailrows: Guess since we can't know the // width of the window. SWR_ADD("&detailrows="); Sprintf(temp, "%d", min(count + countpp, 30)); SWR_ADD_URIcoded(temp); full: ; //printf("URL=%ld '%s'\n",strlen(url),url); # ifdef WIN32 int *rv = win32_cr_shellexecute(url); // XXX TESTING printf("ShellExecute returned: %p\n",rv); // >32 is ok # else /* !WIN32 */ const char *xargv[] = { CRASHREPORT, url, NULL }; int pid = fork(); extern char **environ; if (pid == 0) { char err[400]; # ifdef CRASHREPORT_EXEC_NOSTDERR int devnull; /* Keep the output clean - firefox spews useless errors on * my system. */ (void) close(2); devnull = open("/dev/null", O_WRONLY); # endif (void) execve(CRASHREPORT, (char * const *) xargv, environ); Sprintf(err, "Can't start " CRASHREPORT ": %.*s", (int) (sizeof err - sizeof "Can't start " CRASHREPORT ": "), strerror(errno)); raw_print(err); # ifdef CRASHREPORT_EXEC_NOSTDERR (void) close(devnull); # endif exit(1); } else { int status; errno = 0; (void) waitpid(pid, &status, 0); if (status) { /* XXX check could be more precise */ # ifdef BETA /* Not useful at the moment. XXX */ char err[100]; Sprintf(err, "pid=%d e=%d status=%0x", wpid, errno, status); raw_print(err); # endif return FALSE; } } /* free(info); -- Don't risk it. */ # endif /* !WIN32 */ return TRUE; } int dobugreport(void) { if (!submit_web_report(2, NULL, "#bugreport command")) { pline("Unable to send bug report. Please visit %s instead.", (sysopt.crashreporturl && *sysopt.crashreporturl) ? sysopt.crashreporturl : DEVTEAM_URL ); } return ECMD_OK; } #undef SWR_ADD #undef SWR_ADD_URIcoded #undef SWR_FRAMES #undef SWR_HDR #undef SWR_LINES #endif /* CRASHREPORT */ #ifdef PANICTRACE /*ARGSUSED*/ boolean NH_panictrace_libc(void) { # if 0 /* XXX how did this get left here? */ if (submit_web_report("Panic", why)) return TRUE; # endif # ifdef PANICTRACE_LIBC void *bt[20]; int count, x; char **info, buf[BUFSZ]; raw_print(" Generating more information you may report:\n"); count = backtrace(bt, SIZE(bt)); info = backtrace_symbols(bt, count); for (x = 0; x < count; x++) { copynchars(buf, info[x], (int) sizeof buf - 1); /* try to remove up to 16 blank spaces by removing 8 twice */ (void) strsubst(buf, " ", ""); (void) strsubst(buf, " ", ""); raw_printf("[%02lu] %s", (unsigned long) x, buf); } /* free(info); -- Don't risk it. */ return TRUE; # else return FALSE; # endif /* !PANICTRACE_LIBC */ } /* * fooPATH file system path for foo * fooVAR (possibly const) variable containing fooPATH */ # ifdef PANICTRACE_GDB # ifdef SYSCF # define GDBVAR sysopt.gdbpath # define GREPVAR sysopt.greppath # else /* SYSCF */ # define GDBVAR GDBPATH # define GREPVAR GREPPATH # endif /* SYSCF */ # endif /* PANICTRACE_GDB */ boolean NH_panictrace_gdb(void) { # ifdef PANICTRACE_GDB /* A (more) generic method to get a stack trace - invoke * gdb on ourself. */ const char *gdbpath = GDBVAR; const char *greppath = GREPVAR; char buf[BUFSZ]; FILE *gdb; if (gdbpath == NULL || gdbpath[0] == 0) return FALSE; if (greppath == NULL || greppath[0] == 0) return FALSE; Snprintf(buf, sizeof buf, "%s -n -q %s %d 2>&1 | %s '^#'", gdbpath, ARGV0, getpid(), greppath); gdb = popen(buf, "w"); if (gdb) { raw_print(" Generating more information you may report:\n"); (void) fprintf(gdb, "bt\nquit\ny"); (void) fflush(gdb); sleep(4); /* ugly */ (void) pclose(gdb); return TRUE; } else { return FALSE; } # else return FALSE; # endif /* !PANICTRACE_GDB */ } #ifdef DUMPLOG_CORE #define USED_if_dumplog #else #define USED_if_dumplog UNUSED #endif /* lineno==0 gives the most recent message (e.g. "Do you want to call panic..." if called from #panic) */ const char * get_saved_pline(int lineno USED_if_dumplog) { #ifdef DUMPLOG_CORE int p; int limit = DUMPLOG_MSG_COUNT; if (lineno >= DUMPLOG_MSG_COUNT) return NULL; p = (gs.saved_pline_index - 1) % DUMPLOG_MSG_COUNT; while (limit--) { if (gs.saved_plines[p]) { /* valid line */ if (lineno--) { p = (p - 1 + DUMPLOG_MSG_COUNT) % DUMPLOG_MSG_COUNT; } else { return gs.saved_plines[p]; } } } #endif /* DUMPLOG_CORE */ return NULL; } #undef USED_if_dumplog # ifndef NO_SIGNAL /* called as signal() handler, so sent at least one arg */ /*ARGUSED*/ void panictrace_handler(int sig_unused UNUSED) { #define SIG_MSG "\nSignal received.\n" int f2; # ifdef CURSES_GRAPHICS if (iflags.window_inited && WINDOWPORT(curses)) { extern void curses_uncurse_terminal(void); /* wincurs.h */ /* it is risky calling this during a program-terminating signal, but without it the subsequent backtrace is useless because that ends up being scrawled all over the screen; call is here rather than in NH_abort() because panic() calls both exit_nhwindows(), which makes this same call under curses, then NH_abort() and we don't want to call this twice */ curses_uncurse_terminal(); } # endif f2 = (int) write(2, SIG_MSG, sizeof SIG_MSG - 1); nhUse(f2); /* what could we do if write to fd#2 (stderr) fails */ NH_abort(NULL); /* ... and we're already in the process of quitting? */ } void panictrace_setsignals(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 /* NO_SIGNAL */ #endif /* PANICTRACE */ /* * FIXME: this should have a lot of '#undef's for onefile support. */ /*report.c*/