From 68526933a835f4a4984769083aebb47405af6086 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Fri, 21 Oct 2022 09:47:16 -0700 Subject: have gdbmacs start up Emacs rather than attach to it Additionally, either re-use or kill the GUD interaction buffer rather than calling `gdb-reset' ourselves: killing it is the documented way to reset. --- .emacs.d/init.el | 94 +++++++++++++++++++++++++++++++++++++------------------- bin/emacsclient | 82 +++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 141 insertions(+), 35 deletions(-) diff --git a/.emacs.d/init.el b/.emacs.d/init.el index e66dd3ad..2b955f63 100644 --- a/.emacs.d/init.el +++ b/.emacs.d/init.el @@ -2274,40 +2274,72 @@ Called by '~/src/dotfiles/bin/emacsclient --spw/update-environment'." (let ((slime-dispatching-connection connection)) (slime-eval cl-form)))))) -(defvar-local spw/gdbmacs-target-emacs nil) - -(defun spw/gdbmacs-attach (pid) - (interactive "nEmacs PID: ") - (when-let ((proc (and (boundp 'gud-comint-buffer) - (get-buffer-process gud-comint-buffer)))) - (if (string= gdb-inferior-status "signal-received") - ;; Avoid wiping out useful info. - (error "Possibly Emacs just crashed; not attaching for now") - (set-process-query-on-exit-flag proc nil) - (kill-buffer gud-comint-buffer))) - (require 'gdb-mi) - (let ((default-directory (expand-file-name "~/src/emacs/"))) - (gdb-reset) - (gdb (format "gdb -i=mi --pid=%d src/emacs" pid)) - (setq spw/gdbmacs-target-emacs pid) - (gud-basic-call "continue"))) - -(defun spw/remote-gdbmacs-attach () - (interactive) - (call-process "emacsclient" nil "*gdbmacs-emacsclient*" nil - "--socket-name=gdbmacs" "--spw/installed" - "-e" (prin1-to-string `(spw/gdbmacs-attach ,(emacs-pid))))) - -(defun spw/maybe-remote-gdbmacs-attach () - (when (and (eq (daemonp) t) - (file-in-directory-p invocation-directory "~/src/emacs/")) - (spw/remote-gdbmacs-attach))) -(add-hook 'after-init-hook #'spw/maybe-remote-gdbmacs-attach) +(defun spw/daemon-pid (&optional name) + ;; We don't use `server-eval-at' because perhaps we are trying to attach gdb + ;; to a wedged Emacs. + (let ((socket (file-name-concat server-socket-dir (or name "server")))) + (and (file-exists-p socket) + (and-let* ((output (car (process-lines "ss" "-Hplx" "src" socket)))) + (and (string-match "pid=\\([[:digit:]]+\\)" output) + (string-to-number (match-string 1 output))))))) + +(defvar-local spw/gdbmacs-target-pid nil) +(defvar-local spw/gdbmacs-target-name nil) + +(defun spw/gdbmacs-attach (&optional name) + (let (pid + (arg (if name (concat "--fg-daemon=" name) "--fg-daemon")) + (proc (and (boundp 'gud-comint-buffer) + (get-buffer-process gud-comint-buffer)))) + (when (and proc (string= gdb-inferior-status "signal-received")) + ;; Avoid wiping out useful info. + (error "Possibly Emacs just crashed; not attaching for now")) + (require 'gdb-mi) + (cl-flet ((run-or-continue () + (gdb-wait-for-pending + (lambda () + (with-current-buffer gud-comint-buffer + (setq spw/gdbmacs-target-pid pid + spw/gdbmacs-target-name name)) + (if pid + (gud-basic-call "continue") + (gud-basic-call "set cwd ~") + (gdb-wait-for-pending + (lambda () (gud-basic-call "run")))))))) + (gdb-wait-for-pending + (if (and proc + ;; Check it looks safe to re-use existing gdb process. + (string-prefix-p "exited" gdb-inferior-status) + (file-in-directory-p + (buffer-local-value 'default-directory gud-comint-buffer) + (expand-file-name "~/src/emacs/"))) + (lambda () + (gud-basic-call (if (setq pid (spw/daemon-pid name)) + (format "attach %d" pid) + (format "set args %s" arg))) + (run-or-continue)) + ;; Start up a new process. + (lambda () + (when (buffer-live-p gud-comint-buffer) + (when proc (set-process-query-on-exit-flag proc nil)) + (kill-buffer gud-comint-buffer)) + (gdb-wait-for-pending + (lambda () + (let ((default-directory (expand-file-name "~/src/emacs/"))) + (gdb (if (setq pid (spw/daemon-pid name)) + (format "gdb -i=mi --pid=%d src/emacs" pid) + (format "gdb -i=mi --args src/emacs %s" arg)))) + (run-or-continue))))))))) ;; C-c C-z to attempt to return control to the debugger. +;; +;; In the --fg-daemon case, AIUI we are here working around this: +;; . (defun spw/comint-stop-subjob (orig-fun) - (if spw/gdbmacs-target-emacs - (signal-process spw/gdbmacs-target-emacs 'SIGTSTP) + (if-let ((pid (or spw/gdbmacs-target-pid + (setq spw/gdbmacs-target-pid + (spw/daemon-pid spw/gdbmacs-target-name))))) + (signal-process pid 'SIGTSTP) (funcall orig-fun))) (advice-add 'comint-stop-subjob :around #'spw/comint-stop-subjob) diff --git a/bin/emacsclient b/bin/emacsclient index fe08427d..920c0102 100755 --- a/bin/emacsclient +++ b/bin/emacsclient @@ -24,8 +24,11 @@ min_arg=0 may_start=true want_update=false want_installed=false +want_eval=false +want_version=false devel_running=false installed_running=false +pass_to_gdbmacs=false socket_dir="/run/user/$(id -u)/emacs/" get_listener () { @@ -40,12 +43,34 @@ get_listener () { fi } +maybe_notify () { + echo >&2 "$1" + [ -n "$XDG_RUNTIME_DIR" ] \ + && notify-send --urgency=low --expire-time=10000 "$1" +} + +die_wedged () { + maybe_notify "in-tree Emacs appears to be wedged" + # Only exit non-zero if we were expected to start the daemon. + ! $may_start; exit +} + +egrep_negate () { + echo -n "^([^${1:0:1}]" + for (( i=1; i < ${#1}; i++ )); do + echo -n "|(${1:0:$i}([^${1:$i:1}]|$))" + done + echo -n "|($1.+))" +} + for arg do shift case "$arg" in '--spw/installed') want_installed=true ;; '--spw/no-start') may_start=false ;; '--spw/update-environment') want_update=true ;; + '-V'|'--version') want_version=true ;;& + '-e'|'--eval') want_eval=true ;;& '-s'|'--socket-name') min_arg=2; daemon_name="$1" ;;& '-s'?*) min_arg=1; daemon_name="${arg:2}" ;;& '--socket-name='?*) min_arg=1; daemon_name="${arg:14}" ;;& @@ -81,10 +106,11 @@ if [ -z "$daemon_name" ] && $devel_running && $want_installed; then # Detach gdb so that Emacs can handle the SIGTERM. # We also have to quit gdb so that `gdb-inferior-status' is reset from - # "signal-received"; see `spw/gdbmacs-attach' command. - [ -n "$(get_listener ${socket_dir}gdbmacs)" ] \ - && emacsclient -sgdbmacs --eval '(gud-basic-call "detach")' \ - --eval '(gud-basic-call "quit")' + # "signal-received"; see `spw/gdbmacs-attach' function. + [ -n "${gdbmacs:=$(get_listener ${socket_dir}gdbmacs)}" ] \ + && "$installed_emacsclient" -sgdbmacs \ + --eval '(gud-basic-call "detach")' \ + --eval '(gud-basic-call "quit")' wait $inotifywait devel_running=false @@ -106,6 +132,54 @@ if ! $want_installed && ! $installed_running \ | xargs pwdx | grep -q "$HOME/src/emacs" ); then emacs="$devel_emacs" emacsclient="$devel_emacsclient" + + # ---- Two special cases for primary session that's always under gdb + if [ -z "$daemon_name" ] && ! $want_version; then + # If devel Emacs is stopped or gdbmacs can't start it, ask gdbmacs to + # handle the request: it's probs. C-i e or editing a file via EDITOR. + + [ -n "${gdbmacs:=$(get_listener ${socket_dir}gdbmacs)}" ] \ + && status=$("$installed_emacsclient" \ + -sgdbmacs \ + --eval \ + '(and (boundp '"'"'gud-comint-buffer) + (get-buffer-process gud-comint-buffer) + (string= "signal-received" + gdb-inferior-status))') + + if [ "$status" = t ]; then + if $want_eval || $want_update; then + die_wedged + else + pass_to_gdbmacs=true + fi + elif [ -z "$listener" ]; then + # inotifywait(1) in Debian "bullseye" doesn't have --include. + inotifywait -qq -e create "${socket_dir}" \ + --exclude "$(egrep_negate ${socket_dir}server)" & + inotifywait=$! + + if "$installed_emacsclient" -a '' -sgdbmacs \ + --eval '(spw/gdbmacs-attach)'; then + wait $inotifywait + listener=true + elif $want_eval || $want_update; then + kill $inotifywait + die_wedged + else + kill $inotifywait + pass_to_gdbmacs=true + fi + fi + + if $pass_to_gdbmacs; then + emacs="$installed_emacs" + emacsclient="$installed_emacsclient" + listener="$gdbmacs" + set -- "$@" -sgdbmacs + fi + fi + # ---- End two special cases for primary session that's always under gdb else emacs="$installed_emacs" emacsclient="$installed_emacsclient" -- cgit v1.2.3