(doc (section "Introduction")
(para "The "
" package is a possible aid in debugging potential system problems
in a Racket-based production application. It is designed to be used by the
application program itself, perhaps when an error condition is detected, or
when invoked by a system administrator from a management console user
(para "Specifically, invoking the "
" procedure attempts to use the "
" (GDB) to attach temporarily to the process, and dump full native
code backtraces of all native threads to a file. This file can then be given
to programmers for debugging. This might be helpful, for example, if problems
with low-level system calls or native code libraries invoked via the FFI.")
(para "The "
" package is currently only for GNU/Linux and similar Unix-like
systems, with GDB installed."))
(doc (section "Interface")
(para "The main interface is the "
" procedure. Options are set via parameters, rather than as
arguments to "
", so that they can be type-checked during normal operation of the
program, since "
" might be applied only rarely and in already exceptional
(doc (defparam current-gdbdump-gdb-program filename string?
(para "String for the complete path to the GDB executable. The default is to try "
(racket (find-executable-path "gdb"))
", as evaluated at time the "
" package is loaded, and to falls back to "
". So, if installing GDB for use by "
" in a process that is already running, you will want to have it
accessible as "
", via symbolic link if not by file.")))
(make-parameter (cond ((find-executable-path "gdb") => path->string)
(cond ((string? v) v)
(else (raise-type-error 'current-gdbdump-gdb-program
(doc (defparam current-gdbdump-file path complete-path?
(para "Complete path to the file to which the dump should be written. By default, it is "
(racket (build-path (find-system-path 'temp-dir) "gdbdump"))
", for example, "
(make-parameter (build-path (find-system-path 'temp-dir) "gdbdump")
(let ((path (cleanse-path v)))
(if (complete-path? path)
(doc (defparam current-gdbdump-local-sleep seconds (>=/c 0)
(para "Number of seconds to "
" between spawning the subprocess that will invoke GDB, and
checking the results or killing the process. The default is "
(if (and (number? v)
(<= 0 v))
"number? >= 0"
(define %gdbdump:dev-null-path (string->path "/dev/null"))
(define (%gdbdump:write-command-line-arg str out)
(if (regexp-match? #rx"[^-_.:/a-z0-9]" str)
(begin (write-char #\" out)
(for ((c (in-string str)))
(let ((n (char->integer c)))
(if (<= 32 n 126)
(if (memv c '(#\" #\$ #\\))
(begin (write-char #\\ out)
(write-char c out))
(write-char c out))
"non-printable-ASCII char ~S in ~S"
(write-char #\" out))
(write-string str out)))
(define (%gdbdump:make-a-big-argument gdb-program this-program-or-false pid path)
(write-string "sleep 1 ; " out)
(%gdbdump:write-command-line-arg gdb-program out)
(write-string " -n -silent -batch -nw -e /dev/null " out)
(%gdbdump:write-command-line-arg this-program-or-false out))
(write-string " -p " out)
(%gdbdump:write-command-line-arg (number->string pid) out)
(write-string " -ex \"set history save off\" -ex \"set pagination off\" -ex \"set editing off\" -ex \"info inferiors\" -ex \"info threads\" -ex \"thread apply all backtrace full\" -ex detach -ex quit < /dev/null >> " out)
(%gdbdump:write-command-line-arg (path->string path) out)
(write-string " 2>&1" out))))
(doc (defproc (gdbdump)
(para "Attempts to use GDB to get native backtraces of all native threads of the native process hosting the current Racket evaluation, writing them to the file specified by "
(para "The GDB executable from "
" is used. GDB is invoked under a "
" process, after a brief sleep in the "
" process, to possibly give the Racket thread applying "
" a chance to "
". All three basic "
" ports of the "
" and GDB processes should be files, and not pass through the
Racket process. (Note: It is conceivable that GDB will run before the Racket
thread sleeps, such as if a long GC cycle is triggered immediately after
creating the subprocess.)")
(para "If the GDB process is still running after "
" seconds have elapsed, it is killed.")
(para "After attempting to use GDB, an event is logged to the current logger via "
" or "
(let ((path (current-gdbdump-file))
(pid (or (getpid)
"could not get PID")))
(this-program-or-false (let* ((exec-path (find-system-path 'exec-file))
(exec-path (if (complete-path? exec-path)
(write-string "PID: " out)
(write pid out)
(write-string "Command-Line: " out)
(write (current-command-line-arguments) out)
(write-string "Executable: " out)
(write this-program-or-false out)
(let-values (((sub stdout stdin stderr)
(parameterize ((subprocess-group-enabled #t))
(subprocess null-out null-in null-out "/bin/sh" "-c"
(let ((status (subprocess-status sub)))
(cond ((eq? 'running status)
(log-error "gdbdump: gdb process still running")
"gdbdump: could not kill gdb: "
(subprocess-kill sub #t)))
((equal? 0 status)
(log-info (format "gdbdump: wrote to ~S"
(log-error (format "gdbdump: gdb exit status ~S"
(#:planet 1:0 #:date "2012-06-25"
(item "Initial release. Tested lightly."))))