--- a/src/runtime/print.c
+++ b/src/runtime/print.c
@@ -24,6 +24,175 @@
 #include "sbcl.h"
 #include "print.h"
 #include "runtime.h"
+#include <stdarg.h>
+#include "thread.h"              /* genesis/primitive-objects.h needs this */
+#include <errno.h>
+
+/* FSHOW and odxprint provide debugging output for low-level information
+ * (signal handling, exceptions, safepoints) which is hard to debug by
+ * other means.
+ *
+ * If enabled at all, environment variables control whether calls of the
+ * form odxprint(name, ...) are enabled at run-time, e.g. using
+ * SBCL_DYNDEBUG="fshow fshow_signal safepoints".
+ *
+ * In the case of FSHOW and FSHOW_SIGNAL, old-style code from runtime.h
+ * can also be used to enable or disable these more aggressively.
+ */
+
+struct dyndebug_config dyndebug_config = {
+    QSHOW == 2, QSHOW_SIGNALS == 2
+};
+
+void
+dyndebug_init()
+{
+#define DYNDEBUG_NFLAGS (sizeof(struct dyndebug_config) / sizeof(int))
+#define dyndebug_init1(lowercase, uppercase)                    \
+    do {                                                        \
+        int *ptr = &dyndebug_config.dyndebug_##lowercase;       \
+        ptrs[n] = ptr;                                          \
+        names[n] = #lowercase;                                  \
+        char *val = getenv("SBCL_DYNDEBUG__" uppercase);        \
+        *ptr = val && strlen(val);                              \
+        n++;                                                    \
+    } while (0)
+    int n = 0;
+    char *names[DYNDEBUG_NFLAGS];
+    int *ptrs[DYNDEBUG_NFLAGS];
+
+    dyndebug_init1(fshow,          "FSHOW");
+    dyndebug_init1(fshow_signal,   "FSHOW_SIGNAL");
+    dyndebug_init1(gencgc_verbose, "GENCGC_VERBOSE");
+    dyndebug_init1(safepoints,     "SAFEPOINTS");
+    dyndebug_init1(seh,            "SEH");
+    dyndebug_init1(misc,           "MISC");
+    dyndebug_init1(pagefaults,     "PAGEFAULTS");
+
+    if (n != DYNDEBUG_NFLAGS)
+        fprintf(stderr, "Bug in dyndebug_init\n");
+
+    gencgc_verbose = dyndebug_config.dyndebug_gencgc_verbose;
+
+    char *featurelist = getenv("SBCL_DYNDEBUG");
+    if (featurelist) {
+        int err = 0;
+        featurelist = strdup(featurelist);
+        char *ptr = featurelist;
+        for (;;) {
+            char *token = strtok(ptr, " ");
+            if (!token) break;
+            unsigned i;
+            if (!strcmp(token, "all"))
+                for (i = 0; i < DYNDEBUG_NFLAGS; i++)
+                    *ptrs[i] = 1;
+            else {
+                for (i = 0; i < DYNDEBUG_NFLAGS; i++)
+                    if (!strcmp(token, names[i])) {
+                        *ptrs[i] = 1;
+                        break;
+                    }
+                if (i == DYNDEBUG_NFLAGS) {
+                    fprintf(stderr, "No such dyndebug flag: `%s'\n", token);
+                    err = 1;
+                }
+            }
+            ptr = 0;
+        }
+        free(featurelist);
+        if (err) {
+            fprintf(stderr, "Valid flags are:\n");
+            fprintf(stderr, "  all  ;enables all of the following:\n");
+            unsigned i;
+            for (i = 0; i < DYNDEBUG_NFLAGS; i++)
+                fprintf(stderr, "  %s\n", names[i]);
+        }
+    }
+
+#undef dyndebug_init1
+#undef DYNDEBUG_NFLAGS
+}
+
+/* Temporarily, odxprint merely performs the equivalent of a traditional
+ * FSHOW call, i.e. it merely formats to stderr.  Ultimately, it should
+ * be restored to its full win32 branch functionality, where output to a
+ * file or to the debugger can be selected at runtime. */
+
+void
+odxprint_fun(const char *fmt, ...)
+{
+    va_list args;
+    va_start(args, fmt);
+    vodxprint_fun(fmt, args);
+    va_end(args);
+}
+
+void
+vodxprint_fun(const char *fmt, va_list args)
+{
+#ifdef LISP_FEATURE_WIN32
+    DWORD lastError = GetLastError();
+#else
+    int original_errno = errno;
+#endif
+
+    QSHOW_BLOCK;
+
+    char buf[1024];
+
+#ifdef LISP_FEATURE_SB_THREAD
+    struct thread *arch_os_get_current_thread(void);
+    struct thread *self = arch_os_get_current_thread();
+    void *pth = self ? (void *) self->os_thread : 0;
+    snprintf(buf, sizeof(buf), "[%p/%p] ", self, pth);
+#endif
+
+    int n = strlen(buf);
+    vsnprintf(buf + n, sizeof(buf) - n - 1, fmt, args);
+    /* buf is now zero-terminated (even in case of overflow).
+     * Our caller took care of the newline (if any) through `fmt'. */
+
+    /* A sufficiently POSIXy implementation of stdio will provide
+     * per-FILE locking, as defined in the spec for flockfile.  At least
+     * glibc complies with this.  Hence we do not need to perform
+     * locking ourselves here.  (Should it turn out, of course, that
+     * other libraries opt for speed rather than safety, we need to
+     * revisit this decision.) */
+    fputs(buf, stderr);
+
+#ifdef LISP_FEATURE_WIN32
+    /* stdio's stderr is line-bufferred, i.e. \n ought to flush it.
+     * Unfortunately, MinGW does not behave the way I would expect it
+     * to.  Let's be safe: */
+    fflush(stderr);
+#endif
+
+    QSHOW_UNBLOCK;
+
+#ifdef LISP_FEATURE_WIN32
+    SetLastError(lastError);
+#else
+    errno = original_errno;
+#endif
+}
+
+/* Translate the rather awkward syntax
+ *   FSHOW((stderr, "xyz"))
+ * into the new and cleaner
+ *   odxprint("xyz").
+ * If we were willing to clean up all existing call sites, we could remove
+ * this wrapper function.  (This is a function, because I don't know how to
+ * strip the extra parens in a macro.) */
+void
+fshow_fun(void __attribute__((__unused__)) *ignored,
+          const char *fmt,
+          ...)
+{
+    va_list args;
+    va_start(args, fmt);
+    vodxprint_fun(fmt, args);
+    va_end(args);
+}
 
 /* This file can be skipped if we're not supporting LDB. */
 #if defined(LISP_FEATURE_SB_LDB)
@@ -35,7 +204,6 @@
 #include "gencgc-alloc-region.h" /* genesis/thread.h needs this */
 #endif
 #include "genesis/static-symbols.h"
-#include "thread.h"              /* genesis/primitive-objects.h needs this */
 #include "genesis/primitive-objects.h"
 #include "genesis/static-symbols.h"
 #include "genesis/tagnames.h"