diff options
Diffstat (limited to 'lisp/progmodes/gdb-mi.el')
-rw-r--r-- | lisp/progmodes/gdb-mi.el | 1094 |
1 files changed, 702 insertions, 392 deletions
diff --git a/lisp/progmodes/gdb-mi.el b/lisp/progmodes/gdb-mi.el index e785acd2840..6e9b6830a01 100644 --- a/lisp/progmodes/gdb-mi.el +++ b/lisp/progmodes/gdb-mi.el @@ -8,7 +8,7 @@ ;; This file is part of GNU Emacs. -;; Homepage: http://www.emacswiki.org/emacs/GDB-MI +;; Homepage: https://www.emacswiki.org/emacs/GDB-MI ;; GNU Emacs is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by @@ -89,9 +89,9 @@ ;;; Code: (require 'gud) -(require 'json) -(require 'bindat) (require 'cl-lib) +(require 'cl-seq) +(eval-when-compile (require 'pcase)) (declare-function speedbar-change-initial-expansion-list "speedbar" (new-default)) @@ -105,13 +105,24 @@ (defvar speedbar-initial-expansion-list-name) (defvar speedbar-frame) -(defvar gdb-memory-address "main") -(defvar gdb-memory-last-address nil +(defvar-local gdb-memory-address-expression "main" + "This expression is passed to gdb. +Possible value: main, $rsp, x+3.") +(defvar-local gdb-memory-address nil + "Address of memory display.") +(defvar-local gdb-memory-last-address nil "Last successfully accessed memory address.") (defvar gdb-memory-next-page nil "Address of next memory page for program memory buffer.") (defvar gdb-memory-prev-page nil "Address of previous memory page for program memory buffer.") +(defvar-local gdb--memory-display-warning nil + "Display warning on memory header if t. + +When error occurs when retrieving memory, gdb-mi displays the +last successful page. In that case the expression might not +match the memory displayed. We want to let the user be aware of +that, so display a warning exclamation mark in the header line.") (defvar gdb-thread-number nil "Main current thread. @@ -154,7 +165,7 @@ May be manually changed by user with `gdb-select-frame'.") "Associative list of threads provided by \"-thread-info\" MI command. Keys are thread numbers (in strings) and values are structures as -returned from -thread-info by `gdb-json-partial-output'. Updated in +returned from -thread-info by `gdb-mi--partial-output'. Updated in `gdb-thread-list-handler-custom'.") (defvar gdb-running-threads-count nil @@ -173,7 +184,7 @@ See also `gdb-running-threads-count'.") "Associative list of breakpoints provided by \"-break-list\" MI command. Keys are breakpoint numbers (in string) and values are structures -as returned from \"-break-list\" by `gdb-json-partial-output' +as returned from \"-break-list\" by `gdb-mi--partial-output' \(\"body\" field is used). Updated in `gdb-breakpoints-list-handler-custom'.") @@ -211,7 +222,9 @@ Only used for files that Emacs can't find.") (defvar gdb-source-file-list nil "List of source files for the current executable.") (defvar gdb-first-done-or-error t) -(defvar gdb-source-window nil) +(defvar gdb-source-window-list nil + "List of windows used for displaying source files. +Sorted in most-recently-visited-first order.") (defvar gdb-inferior-status nil) (defvar gdb-continuation nil) (defvar gdb-supports-non-stop nil) @@ -242,6 +255,27 @@ Possible values are these symbols: disposition of output generated by commands that gdb mode sends to gdb on its own behalf.") +(defvar gdb--window-configuration-before nil + "Stores the window configuration before starting GDB.") + +(defcustom gdb-restore-window-configuration-after-quit nil + "If non-nil, restore window configuration as of before GDB started. + +Possible values are: + t -- Always restore. + nil -- Don't restore. + `if-gdb-show-main' -- Restore only if variable `gdb-show-main' + is non-nil + `if-gdb-many-windows' -- Restore only if variable `gdb-many-windows' + is non-nil." + :type '(choice + (const :tag "Always restore" t) + (const :tag "Don't restore" nil) + (const :tag "Depends on `gdb-show-main'" 'if-gdb-show-main) + (const :tag "Depends on `gdb-many-windows'" 'if-gdb-many-windows)) + :group 'gdb + :version "28.1") + (defcustom gdb-discard-unordered-replies t "Non-nil means discard any out-of-order GDB replies. This protects against lost GDB replies, assuming that GDB always @@ -480,8 +514,6 @@ contains fields of corresponding MI *stopped async record: Note that \"reason\" is only present in non-stop debugging mode. -`bindat-get-field' may be used to access the fields of response. - Each function is called after the new current thread was selected and GDB buffers were updated in `gdb-stopped'." :type '(repeat function) @@ -592,6 +624,41 @@ Also display the main routine in the disassembly buffer if present." :group 'gdb :version "22.1") +(defcustom gdb-window-configuration-directory user-emacs-directory + "Directory where GDB window configuration files are stored. +If nil, use `default-directory'." + :type 'string + :group 'gdb + :version "28.1") + +(defcustom gdb-default-window-configuration-file nil + "If non-nil, load this window configuration (layout) on startup. +This should be the full name of the window configuration file. +If this is not an absolute path, GDB treats it as a relative path +and looks under `gdb-window-configuration-directory'. + +Note that this variable only takes effect when variable +`gdb-many-windows' is t." + :type '(choice (const :tag "None" nil) + string) + :group 'gdb + :version "28.1") + +(defcustom gdb-display-source-buffer-action '(nil . ((inhibit-same-window . t))) + "`display-buffer' action used when GDB displays a source buffer." + :type 'sexp + :group 'gdb + :version "28.1") + +(defcustom gdb-max-source-window-count 1 + "Maximum number of source windows to use. +Until there are such number of source windows on screen, GDB +tries to open a new window when visiting a new source file; after +that GDB starts to reuse existing source windows." + :type 'number + :group 'gdb + :version "28.1") + (defvar gdbmi-debug-mode nil "When non-nil, print the messages sent/received from GDB/MI in *Messages*.") @@ -669,8 +736,10 @@ NOARG must be t when this macro is used outside `gud-def'." (unless (zerop (length string)) (remove-function (process-filter proc) #'gdb--check-interpreter) (unless (memq (aref string 0) '(?^ ?~ ?@ ?& ?* ?=)) - ;; Apparently we're not running with -i=mi. - (let ((msg "Error: you did not specify -i=mi on GDB's command line!")) + ;; Apparently we're not running with -i=mi (or we're, for + ;; instance, debugging something inside a Docker instance with + ;; Emacs on the outside). + (let ((msg "Error: Either -i=mi wasn't specified on the GDB command line, or the extra socket couldn't be established. Consider using `M-x gud-gdb' instead.")) (message msg) (setq string (concat (propertize msg 'font-lock-face 'error) "\n" string))) @@ -750,6 +819,12 @@ detailed description of this mode. (gdb-restore-windows) (error "Multiple debugging requires restarting in text command mode")) + + ;; Save window configuration before starting gdb so we can restore + ;; it after gdb quits. Save it regardless of the value of + ;; `gdb-restore-window-configuration-after-quit'. + (setq gdb--window-configuration-before (window-state-get)) + ;; (gud-common-init command-line nil 'gud-gdbmi-marker-filter) @@ -925,7 +1000,7 @@ detailed description of this mode. gdb-first-done-or-error t gdb-buffer-fringe-width (car (window-fringes)) gdb-debug-log nil - gdb-source-window nil + gdb-source-window-list nil gdb-inferior-status nil gdb-continuation nil gdb-buf-publisher '() @@ -1035,7 +1110,10 @@ no input, and GDB is waiting for input." (declare-function tooltip-show "tooltip" (text &optional use-echo-area)) -(defconst gdb--string-regexp "\"\\(?:[^\\\"]\\|\\\\.\\)*\"") +(defconst gdb--string-regexp (rx "\"" + (* (or (seq "\\" nonl) + (not (any "\"\\")))) + "\"")) (defun gdb-tooltip-print (expr) (with-current-buffer (gdb-get-buffer 'gdb-partial-output-buffer) @@ -1045,11 +1123,11 @@ no input, and GDB is waiting for input." "\\)") nil t) (tooltip-show - (concat expr " = " (read (match-string 1))) + (concat expr " = " (gdb-mi--c-string-from-string (match-string 1))) (or gud-tooltip-echo-area (not (display-graphic-p))))) ((re-search-forward "msg=\\(\".+\"\\)$" nil t) - (tooltip-show (read (match-string 1)) + (tooltip-show (gdb-mi--c-string-from-string (match-string 1)) (or gud-tooltip-echo-area (not (display-graphic-p)))))))) @@ -1062,7 +1140,7 @@ no input, and GDB is waiting for input." (if (search-forward "expands to: " nil t) (unless (looking-at "\\S-+.*(.*).*") (gdb-input (concat "-data-evaluate-expression \"" expr "\"") - `(lambda () (gdb-tooltip-print ,expr))))))) + (lambda () (gdb-tooltip-print expr))))))) (defun gdb-init-buffer () (set (make-local-variable 'gud-minor-mode) 'gdbmi) @@ -1182,23 +1260,26 @@ With arg, enter name of variable to be watched in the minibuffer." (tooltip-identifier-from-point (point))))))) (set-text-properties 0 (length expr) nil expr) (gdb-input (concat "-var-create - * " expr "") - `(lambda () (gdb-var-create-handler ,expr)))))) + (lambda () (gdb-var-create-handler expr)))))) (message "gud-watch is a no-op in this mode.")))) +(defsubst gdb-mi--field (value field) + (cdr (assq field value))) + (defun gdb-var-create-handler (expr) - (let* ((result (gdb-json-partial-output))) - (if (not (bindat-get-field result 'msg)) + (let* ((result (gdb-mi--partial-output))) + (if (not (gdb-mi--field result 'msg)) (let ((var - (list (bindat-get-field result 'name) + (list (gdb-mi--field result 'name) (if (and (string-equal gdb-current-language "c") gdb-use-colon-colon-notation gdb-selected-frame) (setq expr (concat gdb-selected-frame "::" expr)) expr) - (bindat-get-field result 'numchild) - (bindat-get-field result 'type) - (bindat-get-field result 'value) + (gdb-mi--field result 'numchild) + (gdb-mi--field result 'type) + (gdb-mi--field result 'value) nil - (bindat-get-field result 'has_more) + (gdb-mi--field result 'has_more) gdb-frame-address))) (push var gdb-var-list) (speedbar 1) @@ -1219,41 +1300,31 @@ With arg, enter name of variable to be watched in the minibuffer." (raise-frame speedbar-frame)) (speedbar-timer-fn)) -(defun gdb-var-evaluate-expression-handler (varnum changed) - (goto-char (point-min)) - (re-search-forward (concat ".*value=\\(" gdb--string-regexp "\\)") - nil t) - (let ((var (assoc varnum gdb-var-list))) - (when var - (if changed (setcar (nthcdr 5 var) 'changed)) - (setcar (nthcdr 4 var) (read (match-string 1))))) - (gdb-speedbar-update)) - ; Uses "-var-list-children --all-values". Needs GDB 6.1 onwards. (defun gdb-var-list-children (varnum) (gdb-input (concat "-var-update " varnum) 'ignore) (gdb-input (concat "-var-list-children --all-values " varnum) - `(lambda () (gdb-var-list-children-handler ,varnum)))) + (lambda () (gdb-var-list-children-handler varnum)))) (defun gdb-var-list-children-handler (varnum) (let* ((var-list nil) - (output (bindat-get-field (gdb-json-partial-output "child"))) - (children (bindat-get-field output 'children))) + (output (gdb-mi--partial-output 'child)) + (children (gdb-mi--field output 'children))) (catch 'child-already-watched (dolist (var gdb-var-list) (if (string-equal varnum (car var)) (progn ;; With dynamic varobjs numchild may have increased. - (setcar (nthcdr 2 var) (bindat-get-field output 'numchild)) + (setcar (nthcdr 2 var) (gdb-mi--field output 'numchild)) (push var var-list) (dolist (child children) - (let ((varchild (list (bindat-get-field child 'name) - (bindat-get-field child 'exp) - (bindat-get-field child 'numchild) - (bindat-get-field child 'type) - (bindat-get-field child 'value) + (let ((varchild (list (gdb-mi--field child 'name) + (gdb-mi--field child 'exp) + (gdb-mi--field child 'numchild) + (gdb-mi--field child 'type) + (gdb-mi--field child 'value) nil - (bindat-get-field child 'has_more)))) + (gdb-mi--field child 'has_more)))) (if (assoc (car varchild) gdb-var-list) (throw 'child-already-watched nil)) (push varchild var-list)))) @@ -1296,7 +1367,7 @@ With arg, enter name of variable to be watched in the minibuffer." (varnum (car var)) (value (read-string "New value: "))) (gdb-input (concat "-var-assign " varnum " " value) - `(lambda () (gdb-edit-value-handler ,value))))) + (lambda () (gdb-edit-value-handler value))))) (defconst gdb-error-regexp "\\^error,msg=\\(\".+\"\\)") @@ -1312,17 +1383,17 @@ With arg, enter name of variable to be watched in the minibuffer." 'gdb-var-update)) (defun gdb-var-update-handler () - (let ((changelist (bindat-get-field (gdb-json-partial-output) 'changelist))) + (let ((changelist (gdb-mi--field (gdb-mi--partial-output) 'changelist))) (dolist (var gdb-var-list) (setcar (nthcdr 5 var) nil)) (let ((temp-var-list gdb-var-list)) (dolist (change changelist) - (let* ((varnum (bindat-get-field change 'name)) + (let* ((varnum (gdb-mi--field change 'name)) (var (assoc varnum gdb-var-list)) - (new-num (bindat-get-field change 'new_num_children))) + (new-num (gdb-mi--field change 'new_num_children))) (when var - (let ((scope (bindat-get-field change 'in_scope)) - (has-more (bindat-get-field change 'has_more))) + (let ((scope (gdb-mi--field change 'in_scope)) + (has-more (gdb-mi--field change 'has_more))) (cond ((string-equal scope "false") (if gdb-delete-out-of-scope (gdb-var-delete-1 var varnum) @@ -1334,12 +1405,12 @@ With arg, enter name of variable to be watched in the minibuffer." (not new-num) (string-equal (nth 2 var) "0")) (setcar (nthcdr 4 var) - (bindat-get-field change 'value)) + (gdb-mi--field change 'value)) (setcar (nthcdr 5 var) 'changed))) ((string-equal scope "invalid") (gdb-var-delete-1 var varnum))))) (let ((var-list nil) var1 - (children (bindat-get-field change 'new_children))) + (children (gdb-mi--field change 'new_children))) (when new-num (setq var1 (pop temp-var-list)) (while var1 @@ -1355,13 +1426,13 @@ With arg, enter name of variable to be watched in the minibuffer." (push (pop temp-var-list) var-list)) (dolist (child children) (let ((varchild - (list (bindat-get-field child 'name) - (bindat-get-field child 'exp) - (bindat-get-field child 'numchild) - (bindat-get-field child 'type) - (bindat-get-field child 'value) + (list (gdb-mi--field child 'name) + (gdb-mi--field child 'exp) + (gdb-mi--field child 'numchild) + (gdb-mi--field child 'type) + (gdb-mi--field child 'value) 'changed - (bindat-get-field child 'has_more)))) + (gdb-mi--field child 'has_more)))) (push varchild var-list)))) ;; Remove deleted children from list. ((< new previous) @@ -1442,7 +1513,7 @@ thread." (defun gdb-current-buffer-frame () "Get current stack frame object for thread of current buffer." - (bindat-get-field (gdb-current-buffer-thread) 'frame)) + (gdb-mi--field (gdb-current-buffer-thread) 'frame)) (defun gdb-buffer-type (buffer) "Get value of `gdb-buffer-type' for BUFFER." @@ -1504,9 +1575,9 @@ this trigger is subscribed to `gdb-buf-publisher' and called with (defun gdb-bind-function-to-buffer (expr buffer) "Return a function which will evaluate EXPR in BUFFER." - `(lambda (&rest args) - (with-current-buffer ,buffer - (apply ',expr args)))) + (lambda (&rest args) + (with-current-buffer buffer + (apply expr args)))) ;; Used to display windows with thread-bound buffers (defmacro def-gdb-preempt-display-buffer (name buffer &optional doc @@ -1667,25 +1738,25 @@ this trigger is subscribed to `gdb-buf-publisher' and called with "Interrupt the program being debugged." (interactive) (interrupt-process - (get-buffer-process gud-comint-buffer) comint-ptyp)) + (get-buffer-process (gdb-get-buffer-create 'gdb-inferior-io)) comint-ptyp)) (defun gdb-io-quit () "Send quit signal to the program being debugged." (interactive) (quit-process - (get-buffer-process gud-comint-buffer) comint-ptyp)) + (get-buffer-process (gdb-get-buffer-create 'gdb-inferior-io)) comint-ptyp)) (defun gdb-io-stop () "Stop the program being debugged." (interactive) (stop-process - (get-buffer-process gud-comint-buffer) comint-ptyp)) + (get-buffer-process (gdb-get-buffer-create 'gdb-inferior-io)) comint-ptyp)) (defun gdb-io-eof () "Send end-of-file to the program being debugged." (interactive) (process-send-eof - (get-buffer-process gud-comint-buffer))) + (get-buffer-process (gdb-get-buffer-create 'gdb-inferior-io)))) (defun gdb-clear-inferior-io () (with-current-buffer (gdb-get-buffer-create 'gdb-inferior-io) @@ -1788,7 +1859,8 @@ static char *magick[] = { "\\|def\\(i\\(ne?\\)?\\)?\\|doc\\(u\\(m\\(e\\(nt?\\)?\\)?\\)?\\)?\\|" gdb-python-guile-commands-regexp "\\|while-stepping\\|stepp\\(i\\(ng?\\)?\\)?\\|ws\\|actions" - "\\)\\([[:blank:]]+\\([^[:blank:]]*\\)\\)?$") + "\\|expl\\(o\\(re?\\)?\\)?" + "\\)\\([[:blank:]]+\\([^[:blank:]]*\\)\\)*$") "Regexp matching GDB commands that enter a recursive reading loop. As long as GDB is in the recursive reading loop, it does not expect commands to be prefixed by \"-interpreter-exec console\".") @@ -1976,7 +2048,7 @@ For all-stop mode, thread information is unavailable while target is running." (let ((old-value gud-running)) (setq gud-running - (string= (bindat-get-field (gdb-current-buffer-thread) 'state) + (string= (gdb-mi--field (gdb-current-buffer-thread) 'state) "running")) ;; Set frame number to "0" when _current_ threads stops. (when (and (gdb-current-buffer-thread) @@ -2007,17 +2079,36 @@ is running." ;; GDB frame (after up, down etc). If no GDB frame is visible but the last ;; visited breakpoint is, use that window. (defun gdb-display-source-buffer (buffer) - (let* ((last-window (if gud-last-last-frame - (get-buffer-window - (gud-find-file (car gud-last-last-frame))))) - (source-window (or last-window - (if (and gdb-source-window - (window-live-p gdb-source-window)) - gdb-source-window)))) - (when source-window - (setq gdb-source-window source-window) - (set-window-buffer source-window buffer)) - source-window)) + "Find a window to display BUFFER. +Always find a window to display buffer, and return it." + ;; This function doesn't take care of setting up source window(s) at startup, + ;; that's handled by `gdb-setup-windows' (if `gdb-many-windows' is non-nil). + ;; If `buffer' is already shown in a window, use that window. + (or (get-buffer-window buffer) + (progn + ;; First, update the window list. + (setq gdb-source-window-list + (cl-remove-duplicates + (cl-remove-if-not + (lambda (win) + (and (window-live-p win) + (eq (window-frame win) + (selected-frame)))) + gdb-source-window-list))) + ;; Should we create a new window or reuse one? + (if (> gdb-max-source-window-count + (length gdb-source-window-list)) + ;; Create a new window, push it to window list and return it. + (car (push (display-buffer buffer gdb-display-source-buffer-action) + gdb-source-window-list)) + ;; Reuse a window, we use the oldest window and put that to + ;; the front of the window list. + (let ((last-win (car (last gdb-source-window-list))) + (rest (butlast gdb-source-window-list))) + (set-window-buffer last-win buffer) + (setq gdb-source-window-list + (cons last-win rest)) + last-win))))) (defun gdbmi-start-with (str offset match) @@ -2214,7 +2305,8 @@ a GDB/MI reply message." ;; Suppress "No registers." GDB 6.8 and earlier ;; duplicates MI error message on internal stream. ;; Don't print to GUD buffer. - (if (not (string-equal (read c-string) "No registers.\n")) + (if (not (string-equal (gdb-mi--c-string-from-string c-string) + "No registers.\n")) (gdb-internals c-string))) @@ -2336,7 +2428,7 @@ the end of the current result or async record is reached." is-complete))) -; The following grammar rules are not yet implemented by this GDBMI-BNF parser. +; The following grammar rules are not parsed directly by this GDBMI-BNF parser. ; The handling of those rules is currently done by the handlers registered ; in gdbmi-bnf-result-state-configs ; @@ -2358,19 +2450,17 @@ the end of the current result or async record is reached." ; list ==> ; "[]" | "[" value ( "," value )* "]" | "[" result ( "," result )* "]" -(defcustom gdb-mi-decode-strings nil +;; FIXME: This is fragile: it relies on the assumption that all the +;; non-ASCII strings output by GDB, including names of the source +;; files, values of string variables in the inferior, etc., are all +;; encoded in the same encoding. + +(defcustom gdb-mi-decode-strings t "When non-nil, decode octal escapes in GDB output into non-ASCII text. If the value is a coding-system, use that coding-system to decode the bytes reconstructed from octal escapes. Any other non-nil value -means to decode using the coding-system set for the GDB process. - -Warning: setting this non-nil might mangle strings reported by GDB -that have literal substrings which match the \\nnn octal escape -patterns, where nnn is an octal number between 200 and 377. So -we only recommend to set this variable non-nil if the program you -are debugging really reports non-ASCII text, or some of its source -file names include non-ASCII characters." +means to decode using the coding-system set for the GDB process." :type '(choice (const :tag "Don't decode" nil) (const :tag "Decode using default coding-system" t) @@ -2378,47 +2468,9 @@ file names include non-ASCII characters." :group 'gdb :version "25.1") -;; The idea of the following function was suggested -;; by Kenichi Handa <handa@gnu.org>. -;; -;; FIXME: This is fragile: it relies on the assumption that all the -;; non-ASCII strings output by GDB, including names of the source -;; files, values of string variables in the inferior, etc., are all -;; encoded in the same encoding. It also assumes that the \nnn -;; sequences are not split between chunks of output of the GDB process -;; due to buffering, and arrive together. Finally, if some string -;; included literal \nnn strings (as opposed to non-ASCII characters -;; converted by GDB/MI to octal escapes), this decoding will mangle -;; those strings. When/if GDB acquires the ability to not -;; escape-protect non-ASCII characters in its MI output, this kludge -;; should be removed. -(defun gdb-mi-decode (string) - "Decode octal escapes in MI output STRING into multibyte text." - (let ((coding - (if (coding-system-p gdb-mi-decode-strings) - gdb-mi-decode-strings - (with-current-buffer - (gdb-get-buffer-create 'gdb-partial-output-buffer) - buffer-file-coding-system)))) - (with-temp-buffer - (set-buffer-multibyte nil) - (prin1 string (current-buffer)) - (goto-char (point-min)) - ;; prin1 quotes the octal escapes as well, which interferes with - ;; their interpretation by 'read' below. Remove the extra - ;; backslashes to countermand that. - (while (re-search-forward "\\\\\\(\\\\[2-3][0-7][0-7]\\)" nil t) - (replace-match "\\1" nil nil)) - (goto-char (point-min)) - (decode-coding-string (read (current-buffer)) coding)))) - (defun gud-gdbmi-marker-filter (string) "Filter GDB/MI output." - ;; If required, decode non-ASCII text encoded with octal escapes. - (or (null gdb-mi-decode-strings) - (setq string (gdb-mi-decode string))) - ;; Record transactions if logging is enabled. (when gdb-enable-debug (push (cons 'recv string) gdb-debug-log) @@ -2446,7 +2498,13 @@ file names include non-ASCII characters." gdb-filter-output) -(defun gdb-gdb (_output-field)) +(defun gdb-gdb (_output-field) + ;; This is needed because the "explore" command is not ended by the + ;; likes of "end" or "quit", but instead by a RET at the appropriate + ;; place, and we know we have exited "explore" when we get the + ;; "(gdb)" prompt. + (and (> gdb-control-level 0) + (setq gdb-control-level (1- gdb-control-level)))) (defun gdb-shell (output-field) (setq gdb-filter-output @@ -2459,7 +2517,7 @@ file names include non-ASCII characters." (defun gdb-thread-exited (_token output-field) "Handle =thread-exited async record. Unset `gdb-thread-number' if current thread exited and update threads list." - (let* ((thread-id (bindat-get-field (gdb-json-string output-field) 'id))) + (let* ((thread-id (gdb-mi--field (gdb-mi--from-string output-field) 'id))) (if (string= gdb-thread-number thread-id) (gdb-setq-thread-number nil)) ;; When we continue current thread and it quickly exits, @@ -2473,8 +2531,8 @@ Unset `gdb-thread-number' if current thread exited and update threads list." "Handler for =thread-selected MI output record. Sets `gdb-thread-number' to new id." - (let* ((result (gdb-json-string output-field)) - (thread-id (bindat-get-field result 'id))) + (let* ((result (gdb-mi--from-string output-field)) + (thread-id (gdb-mi--field result 'id))) (gdb-setq-thread-number thread-id) ;; Typing `thread N' in GUD buffer makes GDB emit `^done' followed ;; by `=thread-selected' notification. `^done' causes `gdb-update' @@ -2489,7 +2547,7 @@ Sets `gdb-thread-number' to new id." (defun gdb-running (_token output-field) (let* ((thread-id - (bindat-get-field (gdb-json-string output-field) 'thread-id))) + (gdb-mi--field (gdb-mi--from-string output-field) 'thread-id))) ;; We reset gdb-frame-number to nil if current thread has gone ;; running. This can't be done in gdb-thread-list-handler-custom ;; because we need correct gdb-frame-number by the time @@ -2518,11 +2576,11 @@ Sets `gdb-thread-number' to new id." "Given the contents of *stopped MI async record, select new current thread and update GDB buffers." ;; Reason is available with target-async only - (let* ((result (gdb-json-string output-field)) - (reason (bindat-get-field result 'reason)) - (thread-id (bindat-get-field result 'thread-id)) - (retval (bindat-get-field result 'return-value)) - (varnum (bindat-get-field result 'gdb-result-var))) + (let* ((result (gdb-mi--from-string output-field)) + (reason (gdb-mi--field result 'reason)) + (thread-id (gdb-mi--field result 'thread-id)) + (retval (gdb-mi--field result 'return-value)) + (varnum (gdb-mi--field result 'gdb-result-var))) ;; -data-list-register-names needs to be issued for any stopped ;; thread @@ -2565,7 +2623,7 @@ current thread and update GDB buffers." ;; gdb-switch-when-another-stopped: (when (or gdb-switch-when-another-stopped (not (string= "stopped" - (bindat-get-field (gdb-current-buffer-thread) 'state)))) + (gdb-mi--field (gdb-current-buffer-thread) 'state)))) ;; Switch if current reason has been selected or we have no ;; reasons (if (or (eq gdb-switch-reasons t) @@ -2598,7 +2656,7 @@ current thread and update GDB buffers." (if (string= output-field "\"\\n\"") "" (let ((error-message - (read output-field))) + (gdb-mi--c-string-from-string output-field))) (put-text-property 0 (length error-message) 'face font-lock-warning-face @@ -2609,7 +2667,8 @@ current thread and update GDB buffers." ;; (frontend MI commands should not print to this stream) (defun gdb-console (output-field) (setq gdb-filter-output - (gdb-concat-output gdb-filter-output (read output-field)))) + (gdb-concat-output gdb-filter-output + (gdb-mi--c-string-from-string output-field)))) (defun gdb-done (token-number output-field is-complete) (gdb-done-or-error token-number 'done output-field is-complete)) @@ -2626,7 +2685,8 @@ current thread and update GDB buffers." ;; MI error - send to minibuffer (when (eq type 'error) ;; Skip "msg=" from `output-field' - (message "%s" (read (substring output-field 4))) + (message "%s" (gdb-mi--c-string-from-string + (substring output-field 4))) ;; Don't send to the console twice. (If it is a console error ;; it is also in the console stream.) (setq output-field nil))) @@ -2674,83 +2734,154 @@ current thread and update GDB buffers." (with-current-buffer (gdb-get-buffer-create 'gdb-partial-output-buffer) (erase-buffer))) -(defun gdb-jsonify-buffer (&optional fix-key fix-list) - "Prepare GDB/MI output in current buffer for parsing with `json-read'. - -Field names are wrapped in double quotes and equal signs are -replaced with semicolons. - -If FIX-KEY is non-nil, strip all \"FIX-KEY=\" occurrences from -partial output. This is used to get rid of useless keys in lists -in MI messages, e.g.: [key=.., key=..]. -stack-list-frames and --break-info are examples of MI commands which issue such -responses. - -If FIX-LIST is non-nil, \"FIX-LIST={..}\" is replaced with -\"FIX-LIST=[..]\" prior to parsing. This is used to fix broken --break-info output when it contains breakpoint script field -incompatible with GDB/MI output syntax. +;; Parse GDB/MI result records: this process converts +;; list [...] -> list +;; tuple {...} -> list +;; result KEY=VALUE -> (KEY . VALUE) where KEY is a symbol +;; c-string "..." -> string + +(defun gdb-mi--parse-tuple-or-list (end-char) + "Parse a tuple or list, either returned as a Lisp list. +END-CHAR is the ending delimiter; will stop at end-of-buffer otherwise." + (let ((items nil)) + (while (not (or (eobp) + (eq (following-char) end-char))) + (let ((item (gdb-mi--parse-result-or-value))) + (push item items) + (when (eq (following-char) ?,) + (forward-char)))) + (when (eq (following-char) end-char) + (forward-char)) + (nreverse items))) + +(defun gdb-mi--parse-c-string () + "Parse a c-string." + (let ((start (point)) + (pieces nil) + (octals-used nil)) + (while (and (re-search-forward (rx (or ?\\ ?\"))) + (not (eq (preceding-char) ?\"))) + (push (buffer-substring start (1- (point))) pieces) + (cond + ((looking-at (rx (any "0-7") (? (any "0-7") (? (any "0-7"))))) + (push (unibyte-string (string-to-number (match-string 0) 8)) pieces) + (setq octals-used t) + (goto-char (match-end 0))) + ((looking-at (rx (any "ntrvfab\"\\"))) + (push (cdr (assq (following-char) + '((?n . "\n") + (?t . "\t") + (?r . "\r") + (?v . "\v") + (?f . "\f") + (?a . "\a") + (?b . "\b") + (?\" . "\"") + (?\\ . "\\")))) + pieces) + (forward-char)) + (t + (warn "Unrecognised escape char: %c" (following-char)))) + (setq start (point))) + (push (buffer-substring start (1- (point))) pieces) + (let ((s (apply #'concat (nreverse pieces)))) + (if (and octals-used gdb-mi-decode-strings) + (let ((coding + (if (coding-system-p gdb-mi-decode-strings) + gdb-mi-decode-strings + (buffer-local-value + 'buffer-file-coding-system + ;; FIXME: This is somewhat expensive. + (gdb-get-buffer-create 'gdb-partial-output-buffer))))) + (decode-coding-string s coding)) + s)))) + +;; FIXME: Ideally this function should not be needed. +(defun gdb-mi--c-string-from-string (string) + "Parse a c-string from (the beginning of) STRING." + (with-temp-buffer + (insert string) + (goto-char (1+ (point-min))) ; Skip leading double quote. + (gdb-mi--parse-c-string))) -If `default-directory' is remote, full file names are adapted accordingly." - (save-excursion +(defun gdb-mi--parse-value () + "Parse a value." + (cond + ((eq (following-char) ?\{) + (forward-char) + (gdb-mi--parse-tuple-or-list ?\})) + ((eq (following-char) ?\[) + (forward-char) + (gdb-mi--parse-tuple-or-list ?\])) + ((eq (following-char) ?\") + (forward-char) + (gdb-mi--parse-c-string)) + (t (error "Bad start of result or value: %c" (following-char))))) + +(defun gdb-mi--parse-result-or-value () + "Parse a result (key=value) or value." + (if (looking-at (rx (group (+ (any "a-zA-Z" ?_ ?-))) "=")) + (progn + (goto-char (match-end 0)) + (let* ((variable (intern (match-string 1))) + (value (gdb-mi--parse-value))) + (cons variable value))) + (gdb-mi--parse-value))) + +(defun gdb-mi--parse-results () + "Parse zero or more result productions as a list." + (gdb-mi--parse-tuple-or-list nil)) + +(defun gdb-mi--fix-key (key value) + "Convert any result (key-value pair) in VALUE whose key is KEY to its value." + (cond + ((atom value) value) + ((symbolp (car value)) + (if (eq (car value) key) + (cdr value) + (cons (car value) (gdb-mi--fix-key key (cdr value))))) + (t (mapcar (lambda (x) (gdb-mi--fix-key key x)) value)))) + +(defun gdb-mi--extend-fullname (remote value) + "Prepend REMOTE to any result string with `fullname' as the key in VALUE." + (cond + ((atom value) value) + ((symbolp (car value)) + (if (and (eq (car value) 'fullname) + (stringp (cdr value))) + (cons 'fullname (concat remote (cdr value))) + (cons (car value) (gdb-mi--extend-fullname remote (cdr value))))) + (t (mapcar (lambda (x) (gdb-mi--extend-fullname remote x)) value)))) + +(defun gdb-mi--read-buffer (fix-key) + "Parse the current buffer as a list of result productions. +If FIX-KEY is a non-nil symbol, convert all FIX-KEY=VALUE results into VALUE. +This is used to get rid of useless keys in lists in MI messages; +eg, [key=.., key=..]. -stack-list-frames and -break-info are +examples of MI commands which issue such responses." + (goto-char (point-min)) + (let ((results (gdb-mi--parse-results))) (let ((remote (file-remote-p default-directory))) (when remote - (goto-char (point-min)) - (while (re-search-forward "[\\[,]fullname=\"\\(.+\\)\"" nil t) - (replace-match (concat remote "\\1") nil nil nil 1)))) - (goto-char (point-min)) + (setq results (gdb-mi--extend-fullname remote results)))) (when fix-key - (save-excursion - (while (re-search-forward (concat "[\\[,]\\(" fix-key "=\\)") nil t) - (replace-match "" nil nil nil 1)))) - (when fix-list - (save-excursion - ;; Find positions of braces which enclose broken list - (while (re-search-forward (concat fix-list "={\"") nil t) - (let ((p1 (goto-char (- (point) 2))) - (p2 (progn (forward-sexp) - (1- (point))))) - ;; Replace braces with brackets - (save-excursion - (goto-char p1) - (delete-char 1) - (insert "[") - (goto-char p2) - (delete-char 1) - (insert "]")))))) - (goto-char (point-min)) - (insert "{") - (let ((re (concat "\\([[:alnum:]_-]+\\)="))) - (while (re-search-forward re nil t) - (replace-match "\"\\1\":" nil nil) - (if (eq (char-after) ?\") (forward-sexp) (forward-char)))) - (goto-char (point-max)) - (insert "}"))) + (setq results (gdb-mi--fix-key fix-key results))) + results)) -(defun gdb-json-read-buffer (&optional fix-key fix-list) - "Prepare and parse GDB/MI output in current buffer with `json-read'. +(defun gdb-mi--from-string (string &optional fix-key) + "Prepare and parse STRING containing GDB/MI output. -FIX-KEY and FIX-LIST work as in `gdb-jsonify-buffer'." - (gdb-jsonify-buffer fix-key fix-list) - (save-excursion - (goto-char (point-min)) - (let ((json-array-type 'list)) - (json-read)))) - -(defun gdb-json-string (string &optional fix-key fix-list) - "Prepare and parse STRING containing GDB/MI output with `json-read'. - -FIX-KEY and FIX-LIST work as in `gdb-jsonify-buffer'." +FIX-KEY works as in `gdb-mi--read-buffer'." (with-temp-buffer (insert string) - (gdb-json-read-buffer fix-key fix-list))) + (gdb-mi--read-buffer fix-key))) -(defun gdb-json-partial-output (&optional fix-key fix-list) - "Prepare and parse gdb-partial-output-buffer with `json-read'. +(defun gdb-mi--partial-output (&optional fix-key) + "Prepare and parse gdb-partial-output-buffer. -FIX-KEY and FIX-KEY work as in `gdb-jsonify-buffer'." +FIX-KEY works as in `gdb-mi--read-buffer'." (with-current-buffer (gdb-get-buffer-create 'gdb-partial-output-buffer) - (gdb-json-read-buffer fix-key fix-list))) + (gdb-mi--read-buffer fix-key))) (defun gdb-line-posns (line) "Return a pair of LINE beginning and end positions." @@ -2831,14 +2962,6 @@ calling `gdb-table-string'." (gdb-table-row-properties table)) "\n"))) -;; bindat-get-field goes deep, gdb-get-many-fields goes wide -(defun gdb-get-many-fields (struct &rest fields) - "Return a list of FIELDS values from STRUCT." - (let ((values)) - (dolist (field fields) - (push (bindat-get-field struct field) values)) - (nreverse values))) - (defmacro def-gdb-auto-update-trigger (trigger-name gdb-command handler-name &optional signal-list) @@ -2926,26 +3049,27 @@ See `def-gdb-auto-update-handler'." 'gdb-invalidate-breakpoints) (defun gdb-breakpoints-list-handler-custom () - (let ((breakpoints-list (bindat-get-field - (gdb-json-partial-output "bkpt" "script") - 'BreakpointTable 'body)) + (let ((breakpoints-list (gdb-mi--field + (gdb-mi--field (gdb-mi--partial-output 'bkpt) + 'BreakpointTable) + 'body)) (table (make-gdb-table))) (setq gdb-breakpoints-list nil) (gdb-table-add-row table '("Num" "Type" "Disp" "Enb" "Addr" "Hits" "What")) (dolist (breakpoint breakpoints-list) (add-to-list 'gdb-breakpoints-list - (cons (bindat-get-field breakpoint 'number) + (cons (gdb-mi--field breakpoint 'number) breakpoint)) - (let ((at (bindat-get-field breakpoint 'at)) - (pending (bindat-get-field breakpoint 'pending)) - (func (bindat-get-field breakpoint 'func)) - (type (bindat-get-field breakpoint 'type))) + (let ((at (gdb-mi--field breakpoint 'at)) + (pending (gdb-mi--field breakpoint 'pending)) + (func (gdb-mi--field breakpoint 'func)) + (type (gdb-mi--field breakpoint 'type))) (gdb-table-add-row table (list - (bindat-get-field breakpoint 'number) + (gdb-mi--field breakpoint 'number) (or type "") - (or (bindat-get-field breakpoint 'disp) "") - (let ((flag (bindat-get-field breakpoint 'enabled))) + (or (gdb-mi--field breakpoint 'disp) "") + (let ((flag (gdb-mi--field breakpoint 'enabled))) (if (string-equal flag "y") (eval-when-compile (propertize "y" 'font-lock-face @@ -2953,10 +3077,10 @@ See `def-gdb-auto-update-handler'." (eval-when-compile (propertize "n" 'font-lock-face font-lock-comment-face)))) - (bindat-get-field breakpoint 'addr) - (or (bindat-get-field breakpoint 'times) "") + (gdb-mi--field breakpoint 'addr) + (or (gdb-mi--field breakpoint 'times) "") (if (and type (string-match ".*watchpoint" type)) - (bindat-get-field breakpoint 'what) + (gdb-mi--field breakpoint 'what) (or pending at (concat "in " (propertize (or func "unknown") @@ -2981,11 +3105,11 @@ See `def-gdb-auto-update-handler'." (dolist (breakpoint gdb-breakpoints-list) (let* ((breakpoint (cdr breakpoint)) ; gdb-breakpoints-list is ; an associative list - (line (bindat-get-field breakpoint 'line))) + (line (gdb-mi--field breakpoint 'line))) (when line - (let ((file (bindat-get-field breakpoint 'fullname)) - (flag (bindat-get-field breakpoint 'enabled)) - (bptno (bindat-get-field breakpoint 'number))) + (let ((file (gdb-mi--field breakpoint 'fullname)) + (flag (gdb-mi--field breakpoint 'enabled)) + (bptno (gdb-mi--field breakpoint 'number))) (unless (and file (file-exists-p file)) (setq file (cdr (assoc bptno gdb-location-alist)))) (if (or (null file) @@ -2993,11 +3117,11 @@ See `def-gdb-auto-update-handler'." ;; If the full filename is not recorded in the ;; breakpoint structure or in `gdb-location-alist', use ;; -file-list-exec-source-file to extract it. - (when (setq file (bindat-get-field breakpoint 'file)) + (when (setq file (gdb-mi--field breakpoint 'file)) (gdb-input (concat "list " file ":1") 'ignore) (gdb-input "-file-list-exec-source-file" - `(lambda () (gdb-get-location - ,bptno ,line ,flag)))) + (lambda () (gdb-get-location + bptno line flag)))) (with-current-buffer (find-file-noselect file 'nowarn) (gdb-init-buffer) ;; Only want one breakpoint icon at each location. @@ -3249,7 +3373,7 @@ corresponding to the mode line clicked." 'gdb-invalidate-threads) (defun gdb-thread-list-handler-custom () - (let ((threads-list (bindat-get-field (gdb-json-partial-output) 'threads)) + (let ((threads-list (gdb-mi--field (gdb-mi--partial-output) 'threads)) (table (make-gdb-table)) (marked-line nil)) (setq gdb-threads-list nil) @@ -3258,9 +3382,9 @@ corresponding to the mode line clicked." (set-marker gdb-thread-position nil) (dolist (thread (reverse threads-list)) - (let ((running (equal (bindat-get-field thread 'state) "running"))) + (let ((running (equal (gdb-mi--field thread 'state) "running"))) (add-to-list 'gdb-threads-list - (cons (bindat-get-field thread 'id) + (cons (gdb-mi--field thread 'id) thread)) (cl-incf (if running gdb-running-threads-count @@ -3269,37 +3393,41 @@ corresponding to the mode line clicked." (gdb-table-add-row table (list - (bindat-get-field thread 'id) + (gdb-mi--field thread 'id) (concat (if gdb-thread-buffer-verbose-names - (concat (bindat-get-field thread 'target-id) " ") "") - (bindat-get-field thread 'state) + (concat (gdb-mi--field thread 'target-id) " ") "") + (gdb-mi--field thread 'state) ;; Include frame information for stopped threads (if (not running) (concat - " in " (bindat-get-field thread 'frame 'func) + " in " (gdb-mi--field (gdb-mi--field thread 'frame) 'func) (if gdb-thread-buffer-arguments (concat " (" - (let ((args (bindat-get-field thread 'frame 'args))) + (let ((args (gdb-mi--field (gdb-mi--field thread 'frame) + 'args))) (mapconcat (lambda (arg) - (apply #'format "%s=%s" - (gdb-get-many-fields arg 'name 'value))) + (format "%s=%s" + (gdb-mi--field arg 'name) + (gdb-mi--field arg 'value))) args ",")) ")") "") (if gdb-thread-buffer-locations - (gdb-frame-location (bindat-get-field thread 'frame)) "") + (gdb-frame-location (gdb-mi--field thread 'frame)) "") (if gdb-thread-buffer-addresses - (concat " at " (bindat-get-field thread 'frame 'addr)) "")) + (concat " at " (gdb-mi--field (gdb-mi--field thread 'frame) + 'addr)) + "")) ""))) (list 'gdb-thread thread 'mouse-face 'highlight 'help-echo "mouse-2, RET: select thread"))) (when (string-equal gdb-thread-number - (bindat-get-field thread 'id)) + (gdb-mi--field thread 'id)) (setq marked-line (length gdb-threads-list)))) (insert (gdb-table-string table " ")) (when marked-line @@ -3331,11 +3459,11 @@ If `gdb-thread' is nil, error is signaled." "Define a NAME which will call BUFFER-COMMAND with id of thread on the current line." `(def-gdb-thread-buffer-command ,name - (,buffer-command (bindat-get-field thread 'id)) + (,buffer-command (gdb-mi--field thread 'id)) ,doc)) (def-gdb-thread-buffer-command gdb-select-thread - (let ((new-id (bindat-get-field thread 'id))) + (let ((new-id (gdb-mi--field thread 'id))) (gdb-setq-thread-number new-id) (gdb-input (concat "-thread-select " new-id) 'ignore) (gdb-update)) @@ -3387,7 +3515,7 @@ on the current line." line." `(def-gdb-thread-buffer-command ,name (if gdb-non-stop - (let ((gdb-thread-number (bindat-get-field thread 'id)) + (let ((gdb-thread-number (gdb-mi--field thread 'id)) (gdb-gud-control-all-threads nil)) (call-interactively #',gud-command)) (error "Available in non-stop mode only, customize `gdb-non-stop-setting'")) @@ -3450,7 +3578,7 @@ line." (def-gdb-trigger-and-handler gdb-invalidate-memory (format "-data-read-memory %s %s %d %d %d" - gdb-memory-address + (gdb-mi-quote gdb-memory-address-expression) gdb-memory-format gdb-memory-unit gdb-memory-rows @@ -3486,27 +3614,35 @@ in `gdb-memory-format'." (error "Unknown format")))) (defun gdb-read-memory-custom () - (let* ((res (gdb-json-partial-output)) - (err-msg (bindat-get-field res 'msg))) + (let* ((res (gdb-mi--partial-output)) + (err-msg (gdb-mi--field res 'msg))) (if (not err-msg) - (let ((memory (bindat-get-field res 'memory))) - (setq gdb-memory-address (bindat-get-field res 'addr)) - (setq gdb-memory-next-page (bindat-get-field res 'next-page)) - (setq gdb-memory-prev-page (bindat-get-field res 'prev-page)) + (let ((memory (gdb-mi--field res 'memory))) + (when gdb-memory-last-address + ;; Nil means last retrieve emits error or just started the session. + (setq gdb--memory-display-warning nil)) + (setq gdb-memory-address (gdb-mi--field res 'addr)) + (setq gdb-memory-next-page (gdb-mi--field res 'next-page)) + (setq gdb-memory-prev-page (gdb-mi--field res 'prev-page)) (setq gdb-memory-last-address gdb-memory-address) (dolist (row memory) - (insert (concat (bindat-get-field row 'addr) ":")) - (dolist (column (bindat-get-field row 'data)) + (insert (concat (gdb-mi--field row 'addr) ":")) + (dolist (column (gdb-mi--field row 'data)) (insert (gdb-pad-string column (+ 2 (gdb-memory-column-width gdb-memory-unit gdb-memory-format))))) (newline))) ;; Show last page instead of empty buffer when out of bounds - (progn - (let ((gdb-memory-address gdb-memory-last-address)) + (when gdb-memory-last-address + (let ((gdb-memory-address-expression gdb-memory-last-address)) + ;; If we don't set `gdb-memory-last-address' to nil, + ;; `gdb-invalidate-memory' eventually calls + ;; `gdb-read-memory-custom', making an infinite loop. + (setq gdb-memory-last-address nil + gdb--memory-display-warning t) (gdb-invalidate-memory 'update) - (error err-msg)))))) + (user-error "Error when retrieving memory: %s Displaying last successful page" err-msg)))))) (defvar gdb-memory-mode-map (let ((map (make-sparse-keymap))) @@ -3540,7 +3676,7 @@ in `gdb-memory-format'." "Set the start memory address." (interactive) (let ((arg (read-from-minibuffer "Memory address: "))) - (setq gdb-memory-address arg)) + (setq gdb-memory-address-expression arg)) (gdb-invalidate-memory 'update)) (defmacro def-gdb-set-positive-number (name variable echo-string &optional doc) @@ -3723,7 +3859,19 @@ DOC is an optional documentation string." (defvar gdb-memory-header '(:eval (concat - "Start address[" + "Start address " + ;; If `gdb-memory-address-expression' is nil, `propertize' would error. + (propertize (or gdb-memory-address-expression "N/A") + 'face font-lock-warning-face + 'help-echo "mouse-1: set start address" + 'mouse-face 'mode-line-highlight + 'local-map (gdb-make-header-line-mouse-map + 'mouse-1 + #'gdb-memory-set-address-event)) + (if gdb--memory-display-warning + (propertize " !" 'face '(:inherit error :weight bold)) + "") + " [" (propertize "-" 'face font-lock-warning-face 'help-echo "mouse-1: decrement address" @@ -3740,13 +3888,9 @@ DOC is an optional documentation string." 'mouse-1 #'gdb-memory-show-next-page)) "]: " - (propertize gdb-memory-address - 'face font-lock-warning-face - 'help-echo "mouse-1: set start address" - 'mouse-face 'mode-line-highlight - 'local-map (gdb-make-header-line-mouse-map - 'mouse-1 - #'gdb-memory-set-address-event)) + ;; If `gdb-memory-address' is nil, `propertize' would error. + (propertize (or gdb-memory-address "N/A") + 'face font-lock-warning-face) " Rows: " (propertize (number-to-string gdb-memory-rows) 'face font-lock-warning-face @@ -3822,8 +3966,8 @@ DOC is an optional documentation string." (def-gdb-auto-update-trigger gdb-invalidate-disassembly (let* ((frame (gdb-current-buffer-frame)) - (file (bindat-get-field frame 'fullname)) - (line (bindat-get-field frame 'line))) + (file (gdb-mi--field frame 'fullname)) + (line (gdb-mi--field frame 'line))) (if file (format "-data-disassemble -f %s -l %s -n -1 -- 0" file line) ;; If we're unable to get a file name / line for $PC, simply @@ -3879,22 +4023,22 @@ DOC is an optional documentation string." 'gdb-invalidate-disassembly) (defun gdb-disassembly-handler-custom () - (let* ((instructions (bindat-get-field (gdb-json-partial-output) 'asm_insns)) - (address (bindat-get-field (gdb-current-buffer-frame) 'addr)) + (let* ((instructions (gdb-mi--field (gdb-mi--partial-output) 'asm_insns)) + (address (gdb-mi--field (gdb-current-buffer-frame) 'addr)) (table (make-gdb-table)) (marked-line nil)) (dolist (instr instructions) (gdb-table-add-row table (list - (bindat-get-field instr 'address) + (gdb-mi--field instr 'address) (let - ((func-name (bindat-get-field instr 'func-name)) - (offset (bindat-get-field instr 'offset))) + ((func-name (gdb-mi--field instr 'func-name)) + (offset (gdb-mi--field instr 'offset))) (if func-name (format "<%s+%s>:" func-name offset) "")) - (bindat-get-field instr 'inst))) - (when (string-equal (bindat-get-field instr 'address) + (gdb-mi--field instr 'inst))) + (when (string-equal (gdb-mi--field instr 'address) address) (progn (setq marked-line (length (gdb-table-rows table))) @@ -3913,15 +4057,15 @@ DOC is an optional documentation string." (setq mode-name (gdb-current-context-mode-name (concat "Disassembly: " - (bindat-get-field (gdb-current-buffer-frame) 'func)))))) + (gdb-mi--field (gdb-current-buffer-frame) 'func)))))) (defun gdb-disassembly-place-breakpoints () (gdb-remove-breakpoint-icons (point-min) (point-max)) (dolist (breakpoint gdb-breakpoints-list) (let* ((breakpoint (cdr breakpoint)) - (bptno (bindat-get-field breakpoint 'number)) - (flag (bindat-get-field breakpoint 'enabled)) - (address (bindat-get-field breakpoint 'addr))) + (bptno (gdb-mi--field breakpoint 'number)) + (flag (gdb-mi--field breakpoint 'enabled)) + (address (gdb-mi--field breakpoint 'addr))) (save-excursion (goto-char (point-min)) (if (re-search-forward (concat "^" address) nil t) @@ -3951,10 +4095,10 @@ DOC is an optional documentation string." (let ((breakpoint (get-text-property (point) 'gdb-breakpoint))) (if breakpoint (gud-basic-call - (concat (if (equal "y" (bindat-get-field breakpoint 'enabled)) + (concat (if (equal "y" (gdb-mi--field breakpoint 'enabled)) "-break-disable " "-break-enable ") - (bindat-get-field breakpoint 'number))) + (gdb-mi--field breakpoint 'number))) (error "Not recognized as break/watchpoint line"))))) (defun gdb-delete-breakpoint () @@ -3965,7 +4109,7 @@ DOC is an optional documentation string." (let ((breakpoint (get-text-property (point) 'gdb-breakpoint))) (if breakpoint (gud-basic-call (concat "-break-delete " - (bindat-get-field breakpoint 'number))) + (gdb-mi--field breakpoint 'number))) (error "Not recognized as break/watchpoint line"))))) (defun gdb-goto-breakpoint (&optional event) @@ -3979,16 +4123,14 @@ DOC is an optional documentation string." (beginning-of-line) (let ((breakpoint (get-text-property (point) 'gdb-breakpoint))) (if breakpoint - (let ((bptno (bindat-get-field breakpoint 'number)) - (file (bindat-get-field breakpoint 'fullname)) - (line (bindat-get-field breakpoint 'line))) + (let ((bptno (gdb-mi--field breakpoint 'number)) + (file (gdb-mi--field breakpoint 'fullname)) + (line (gdb-mi--field breakpoint 'line))) (save-selected-window (let* ((buffer (find-file-noselect (if (file-exists-p file) file (cdr (assoc bptno gdb-location-alist))))) - (window (or (gdb-display-source-buffer buffer) - (display-buffer buffer)))) - (setq gdb-source-window window) + (window (gdb-display-source-buffer buffer))) (with-current-buffer buffer (goto-char (point-min)) (forward-line (1- (string-to-number line))) @@ -4014,28 +4156,28 @@ DOC is an optional documentation string." FRAME must have either \"file\" and \"line\" members or \"from\" member." - (let ((file (bindat-get-field frame 'file)) - (line (bindat-get-field frame 'line)) - (from (bindat-get-field frame 'from))) + (let ((file (gdb-mi--field frame 'file)) + (line (gdb-mi--field frame 'line)) + (from (gdb-mi--field frame 'from))) (let ((res (or (and file line (concat file ":" line)) from))) (if res (concat " of " res) "")))) (defun gdb-stack-list-frames-custom () - (let ((stack (bindat-get-field (gdb-json-partial-output "frame") 'stack)) + (let ((stack (gdb-mi--field (gdb-mi--partial-output 'frame) 'stack)) (table (make-gdb-table))) (set-marker gdb-stack-position nil) (dolist (frame stack) (gdb-table-add-row table (list - (bindat-get-field frame 'level) + (gdb-mi--field frame 'level) "in" (concat - (bindat-get-field frame 'func) + (gdb-mi--field frame 'func) (if gdb-stack-buffer-locations (gdb-frame-location frame) "") (if gdb-stack-buffer-addresses - (concat " at " (bindat-get-field frame 'addr)) ""))) + (concat " at " (gdb-mi--field frame 'addr)) ""))) `(mouse-face highlight help-echo "mouse-2, RET: Select frame" gdb-frame ,frame))) @@ -4095,7 +4237,7 @@ member." (let ((frame (get-text-property (point) 'gdb-frame))) (if frame (if (gdb-buffer-shows-main-thread-p) - (let ((new-level (bindat-get-field frame 'level))) + (let ((new-level (gdb-mi--field frame 'level))) (setq gdb-frame-number new-level) (gdb-input (concat "-stack-select-frame " new-level) 'ignore) @@ -4141,7 +4283,7 @@ member." (save-excursion (if event (posn-set-point (event-end event))) (beginning-of-line) - (let* ((var (bindat-get-field + (let* ((var (gdb-mi--field (get-text-property (point) 'gdb-local-variable) 'name)) (value (read-string (format "New value (%s): " var)))) (gud-basic-call @@ -4150,12 +4292,12 @@ member." ;; Don't display values of arrays or structures. ;; These can be expanded using gud-watch. (defun gdb-locals-handler-custom () - (let ((locals-list (bindat-get-field (gdb-json-partial-output) 'locals)) + (let ((locals-list (gdb-mi--field (gdb-mi--partial-output) 'locals)) (table (make-gdb-table))) (dolist (local locals-list) - (let ((name (bindat-get-field local 'name)) - (value (bindat-get-field local 'value)) - (type (bindat-get-field local 'type))) + (let ((name (gdb-mi--field local 'name)) + (value (gdb-mi--field local 'value)) + (type (gdb-mi--field local 'type))) (when (not value) (setq value "<complex data type>")) (if (or (not value) @@ -4181,7 +4323,7 @@ member." (setq mode-name (gdb-current-context-mode-name (concat "Locals: " - (bindat-get-field (gdb-current-buffer-frame) 'func)))))) + (gdb-mi--field (gdb-current-buffer-frame) 'func)))))) (defvar gdb-locals-header (list @@ -4247,11 +4389,11 @@ member." (defun gdb-registers-handler-custom () (when gdb-register-names (let ((register-values - (bindat-get-field (gdb-json-partial-output) 'register-values)) + (gdb-mi--field (gdb-mi--partial-output) 'register-values)) (table (make-gdb-table))) (dolist (register register-values) - (let* ((register-number (bindat-get-field register 'number)) - (value (bindat-get-field register 'value)) + (let* ((register-number (gdb-mi--field register 'number)) + (value (gdb-mi--field register 'value)) (register-name (nth (string-to-number register-number) gdb-register-names))) (gdb-table-add-row @@ -4275,8 +4417,7 @@ member." (save-excursion (if event (posn-set-point (event-end event))) (beginning-of-line) - (let* ((var (bindat-get-field - (get-text-property (point) 'gdb-register-name))) + (let* ((var (get-text-property (point) 'gdb-register-name)) (value (read-string (format "New value (%s): " var)))) (gud-basic-call (concat "-gdb-set variable $" var " = " value))))) @@ -4338,7 +4479,7 @@ member." (defun gdb-changed-registers-handler () (setq gdb-changed-registers nil) (dolist (register-number - (bindat-get-field (gdb-json-partial-output) 'changed-registers)) + (gdb-mi--field (gdb-mi--partial-output) 'changed-registers)) (push register-number gdb-changed-registers))) (defun gdb-register-names-handler () @@ -4346,7 +4487,7 @@ member." ;; only once (in gdb-init-1) (setq gdb-register-names nil) (dolist (register-name - (bindat-get-field (gdb-json-partial-output) 'register-names)) + (gdb-mi--field (gdb-mi--partial-output) 'register-names)) (push register-name gdb-register-names)) (setq gdb-register-names (reverse gdb-register-names))) @@ -4357,7 +4498,8 @@ If buffers already exist for any of these files, `gud-minor-mode' is set in them." (goto-char (point-min)) (while (re-search-forward gdb-source-file-regexp nil t) - (push (read (match-string 1)) gdb-source-file-list)) + (push (gdb-mi--c-string-from-string (match-string 1)) + gdb-source-file-list)) (dolist (buffer (buffer-list)) (with-current-buffer buffer (when (member buffer-file-name gdb-source-file-list) @@ -4373,13 +4515,13 @@ Called from `gdb-update'." (defun gdb-frame-handler () "Set `gdb-selected-frame' and `gdb-selected-file' to show overlay arrow in source buffer." - (let ((frame (bindat-get-field (gdb-json-partial-output) 'frame))) + (let ((frame (gdb-mi--field (gdb-mi--partial-output) 'frame))) (when frame - (setq gdb-selected-frame (bindat-get-field frame 'func)) - (setq gdb-selected-file (bindat-get-field frame 'fullname)) - (setq gdb-frame-number (bindat-get-field frame 'level)) - (setq gdb-frame-address (bindat-get-field frame 'addr)) - (let ((line (bindat-get-field frame 'line))) + (setq gdb-selected-frame (gdb-mi--field frame 'func)) + (setq gdb-selected-file (gdb-mi--field frame 'fullname)) + (setq gdb-frame-number (gdb-mi--field frame 'level)) + (setq gdb-frame-address (gdb-mi--field frame 'addr)) + (let ((line (gdb-mi--field frame 'line))) (setq gdb-selected-line (and line (string-to-number line))) (when (and gdb-selected-file gdb-selected-line) (setq gud-last-frame (cons gdb-selected-file gdb-selected-line)) @@ -4404,7 +4546,7 @@ overlay arrow in source buffer." (goto-char (point-min)) (setq gdb-prompt-name nil) (re-search-forward gdb-prompt-name-regexp nil t) - (setq gdb-prompt-name (read (match-string 1))) + (setq gdb-prompt-name (gdb-mi--c-string-from-string (match-string 1))) ;; Insert first prompt. (setq gdb-filter-output (concat gdb-filter-output gdb-prompt-name))) @@ -4441,17 +4583,17 @@ SPLIT-HORIZONTAL and show BUF in the new window." (let* ((buf-type (gdb-buffer-type buf)) (existing-window (get-window-with-predicate - #'(lambda (w) - (and (eq buf-type - (gdb-buffer-type (window-buffer w))) - (not (window-dedicated-p w))))))) + (lambda (w) + (and (eq buf-type + (gdb-buffer-type (window-buffer w))) + (not (window-dedicated-p w))))))) (if existing-window (set-window-buffer existing-window buf) (let ((dedicated-window (get-window-with-predicate - #'(lambda (w) - (eq buf-type - (gdb-buffer-type (window-buffer w))))))) + (lambda (w) + (eq buf-type + (gdb-buffer-type (window-buffer w))))))) (if dedicated-window (set-window-buffer (split-window dedicated-window nil split-horizontal) buf) @@ -4464,6 +4606,26 @@ SPLIT-HORIZONTAL and show BUF in the new window." (define-key gud-menu-map [displays] `(menu-item "GDB-Windows" ,menu :visible (eq gud-minor-mode 'gdbmi))) + (define-key menu [gdb-restore-windows] + '(menu-item "Restore Initial Layout" gdb-restore-windows + :help "Restore the initial GDB window layout.")) + ;; Window layout vs window configuration: We use "window layout" in + ;; GDB UI. Internally we refer to "window configuration" because + ;; that's the data structure used to store window layouts. Though + ;; bare in mind that there is a small difference between what we + ;; store and what normal window configuration functions + ;; output. Because GDB buffers (source, local, breakpoint, etc) are + ;; different between each debugging sessions, simply save/load + ;; window configurations doesn't + ;; work. `gdb-save-window-configuration' and + ;; `gdb-load-window-configuration' do some tricks to store and + ;; recreate each buffer in the layout. + (define-key menu [load-layout] '("Load Layout" "Load GDB window configuration (layout) from a file" . gdb-load-window-configuration)) + (define-key menu [save-layout] '("Save Layout" "Save current GDB window configuration (layout) to a file" . gdb-save-window-configuration)) + (define-key menu [restore-layout-after-quit] + '(menu-item "Restore Layout After Quit" gdb-toggle-restore-window-configuration + :button (:toggle . gdb-restore-window-configuration-after-quit) + :help "Toggle between always restore the window configuration (layout) after GDB quits and never restore.\n You can also change this setting in Customize to conditionally restore.")) (define-key menu [gdb] '("Gdb" . gdb-display-gdb-buffer)) (define-key menu [threads] '("Threads" . gdb-display-threads-buffer)) (define-key menu [memory] '("Memory" . gdb-display-memory-buffer)) @@ -4496,44 +4658,41 @@ SPLIT-HORIZONTAL and show BUF in the new window." (let ((menu (make-sparse-keymap "GDB-MI"))) (define-key menu [gdb-customize] - '(menu-item "Customize" (lambda () (interactive) (customize-group 'gdb)) + `(menu-item "Customize" ,(lambda () (interactive) (customize-group 'gdb)) :help "Customize Gdb Graphical Mode options.")) (define-key menu [gdb-many-windows] '(menu-item "Display Other Windows" gdb-many-windows :help "Toggle display of locals, stack and breakpoint information" :button (:toggle . gdb-many-windows))) - (define-key menu [gdb-restore-windows] - '(menu-item "Restore Window Layout" gdb-restore-windows - :help "Restore standard layout for debug session.")) (define-key menu [sep1] '(menu-item "--")) (define-key menu [all-threads] - '(menu-item "GUD controls all threads" - (lambda () - (interactive) - (setq gdb-gud-control-all-threads t)) + `(menu-item "GUD controls all threads" + ,(lambda () + (interactive) + (setq gdb-gud-control-all-threads t)) :help "GUD start/stop commands apply to all threads" :button (:radio . gdb-gud-control-all-threads))) (define-key menu [current-thread] - '(menu-item "GUD controls current thread" - (lambda () - (interactive) - (setq gdb-gud-control-all-threads nil)) + `(menu-item "GUD controls current thread" + ,(lambda () + (interactive) + (setq gdb-gud-control-all-threads nil)) :help "GUD start/stop commands apply to current thread only" :button (:radio . (not gdb-gud-control-all-threads)))) (define-key menu [sep2] '(menu-item "--")) (define-key menu [gdb-customize-reasons] - '(menu-item "Customize switching..." - (lambda () - (interactive) - (customize-option 'gdb-switch-reasons)))) + `(menu-item "Customize switching..." + ,(lambda () + (interactive) + (customize-option 'gdb-switch-reasons)))) (define-key menu [gdb-switch-when-another-stopped] - (menu-bar-make-toggle gdb-toggle-switch-when-another-stopped - gdb-switch-when-another-stopped - "Automatically switch to stopped thread" - "GDB thread switching %s" - "Switch to stopped thread")) + (menu-bar-make-toggle-command + gdb-toggle-switch-when-another-stopped + gdb-switch-when-another-stopped + "Automatically switch to stopped thread" + "GDB thread switching %s" "Switch to stopped thread")) (define-key gud-menu-map [mi] `(menu-item "GDB-MI" ,menu :visible (eq gud-minor-mode 'gdbmi)))) @@ -4579,41 +4738,173 @@ window is dedicated." (set-window-buffer window (get-buffer name)) (set-window-dedicated-p window t)) +(defun gdb-toggle-restore-window-configuration () + "Toggle whether to restore window configuration when GDB quits." + (interactive) + (setq gdb-restore-window-configuration-after-quit + (not gdb-restore-window-configuration-after-quit))) + +(defun gdb-get-source-buffer () + "Return a buffer displaying source file or nil if we can't find one. +The source file is the file that contains the source location +where GDB stops. There could be multiple source files during a +debugging session, we get the most recently showed one. If +program hasn't started running yet, the source file is the \"main +file\" where the GDB session starts (see `gdb-main-file')." + (if gud-last-last-frame + (gud-find-file (car gud-last-last-frame)) + (when gdb-main-file + (gud-find-file gdb-main-file)))) + (defun gdb-setup-windows () - "Layout the window pattern for option `gdb-many-windows'." - (gdb-get-buffer-create 'gdb-locals-buffer) - (gdb-get-buffer-create 'gdb-stack-buffer) - (gdb-get-buffer-create 'gdb-breakpoints-buffer) - (set-window-dedicated-p (selected-window) nil) - (switch-to-buffer gud-comint-buffer) - (delete-other-windows) - (let ((win0 (selected-window)) - (win1 (split-window nil ( / ( * (window-height) 3) 4))) - (win2 (split-window nil ( / (window-height) 3))) - (win3 (split-window-right))) - (gdb-set-window-buffer (gdb-locals-buffer-name) nil win3) - (select-window win2) - (set-window-buffer - win2 - (if gud-last-last-frame - (gud-find-file (car gud-last-last-frame)) - (if gdb-main-file - (gud-find-file gdb-main-file) - ;; Put buffer list in window if we - ;; can't find a source file. - (list-buffers-noselect)))) - (setq gdb-source-window (selected-window)) - (let ((win4 (split-window-right))) - (gdb-set-window-buffer - (gdb-get-buffer-create 'gdb-inferior-io) nil win4)) - (select-window win1) - (gdb-set-window-buffer (gdb-stack-buffer-name)) - (let ((win5 (split-window-right))) - (gdb-set-window-buffer (if gdb-show-threads-by-default - (gdb-threads-buffer-name) - (gdb-breakpoints-buffer-name)) - nil win5)) - (select-window win0))) + "Lay out the window pattern for option `gdb-many-windows'." + (if gdb-default-window-configuration-file + (gdb-load-window-configuration + (if (file-name-absolute-p gdb-default-window-configuration-file) + gdb-default-window-configuration-file + (expand-file-name gdb-default-window-configuration-file + gdb-window-configuration-directory))) + ;; Create default layout as before. + (gdb-get-buffer-create 'gdb-locals-buffer) + (gdb-get-buffer-create 'gdb-stack-buffer) + (gdb-get-buffer-create 'gdb-breakpoints-buffer) + (set-window-dedicated-p (selected-window) nil) + (switch-to-buffer gud-comint-buffer) + (delete-other-windows) + (let ((win0 (selected-window)) + (win1 (split-window nil ( / ( * (window-height) 3) 4))) + (win2 (split-window nil ( / (window-height) 3))) + (win3 (split-window-right))) + (gdb-set-window-buffer (gdb-locals-buffer-name) nil win3) + (select-window win2) + (set-window-buffer win2 (or (gdb-get-source-buffer) + (list-buffers-noselect))) + (setq gdb-source-window-list (list (selected-window))) + (let ((win4 (split-window-right))) + (gdb-set-window-buffer + (gdb-get-buffer-create 'gdb-inferior-io) nil win4)) + (select-window win1) + (gdb-set-window-buffer (gdb-stack-buffer-name)) + (let ((win5 (split-window-right))) + (gdb-set-window-buffer (if gdb-show-threads-by-default + (gdb-threads-buffer-name) + (gdb-breakpoints-buffer-name)) + nil win5)) + (select-window win0)))) + +(defun gdb-buffer-p (buffer) + "Return t if BUFFER is GDB-related." + (with-current-buffer buffer + (eq gud-minor-mode 'gdbmi))) + +(defun gdb-function-buffer-p (buffer) + "Return t if BUFFER is a GDB function buffer. + +Function buffers are locals buffer, registers buffer, etc, but +not including main command buffer (the one where you type GDB +commands) or source buffers (that display program source code)." + (with-current-buffer buffer + (derived-mode-p 'gdb-parent-mode 'gdb-inferior-io-mode))) + +(defun gdb--buffer-type (buffer) + "Return the type of BUFFER if it is a function buffer. +Buffer type is like `gdb-registers-type', `gdb-stack-buffer'. +These symbols are used by `gdb-get-buffer-create'. + +Return nil if BUFFER is not a GDB function buffer." + (with-current-buffer buffer + (cl-loop for rule in gdb-buffer-rules + for mode-name = (gdb-rules-buffer-mode rule) + for type = (car rule) + if (eq mode-name major-mode) + return type + finally return nil))) + +(defun gdb-save-window-configuration (file) + "Save current window configuration (layout) to FILE. +You can later restore this configuration from that file by +`gdb-load-window-configuration'." + (interactive (list (read-file-name + "Save window configuration to file: " + (or gdb-window-configuration-directory + default-directory)))) + ;; We replace the buffer in each window with a placeholder, store + ;; the buffer type (register, breakpoint, etc) in window parameters, + ;; and write the window configuration to the file. + (save-window-excursion + (let ((placeholder (get-buffer-create " *gdb-placeholder*")) + (window-persistent-parameters + (cons '(gdb-buffer-type . writable) window-persistent-parameters))) + (unwind-protect + (dolist (win (window-list nil 'no-minibuffer)) + (select-window win) + (when (gdb-buffer-p (current-buffer)) + (set-window-parameter + nil 'gdb-buffer-type + (cond ((gdb-function-buffer-p (current-buffer)) + ;; 1) If a user arranged the window + ;; configuration herself and saves it, windows + ;; are probably not dedicated. 2) We use the + ;; same dedication flag as in + ;; `gdb-display-buffer'. + (set-window-dedicated-p nil t) + ;; We save this gdb-buffer-type symbol so + ;; we can later pass it to `gdb-get-buffer-create'; + ;; one example: `gdb-registers-buffer'. + (or (gdb--buffer-type (current-buffer)) + (error "Unrecognized gdb buffer mode: %s" major-mode))) + ;; Command buffer. + ((derived-mode-p 'gud-mode) 'command) + ;; Consider everything else as source buffer. + (t 'source))) + (with-window-non-dedicated nil + (set-window-buffer nil placeholder) + (set-window-prev-buffers (selected-window) nil) + (set-window-next-buffers (selected-window) nil)))) + ;; Save the window configuration to FILE. + (let ((window-config (window-state-get nil t))) + (with-temp-buffer + (prin1 window-config (current-buffer)) + (write-file file t))) + (kill-buffer placeholder))))) + +(defun gdb-load-window-configuration (file) + "Restore window configuration (layout) from FILE. +FILE should be a window configuration file saved by +`gdb-save-window-configuration'." + (interactive (list (read-file-name + "Restore window configuration from file: " + (or gdb-window-configuration-directory + default-directory)))) + ;; Basically, we restore window configuration and go through each + ;; window and restore the function buffers. + (let* ((placeholder (get-buffer-create " *gdb-placeholder*"))) + (unwind-protect ; Don't leak buffer. + (let ((window-config (with-temp-buffer + (insert-file-contents file) + ;; We need to go to point-min because + ;; `read' reads from point + (goto-char (point-min)) + (read (current-buffer)))) + (source-buffer (or (gdb-get-source-buffer) + ;; Do the same thing as in + ;; `gdb-setup-windows' if no source + ;; buffer is found. + (list-buffers-noselect))) + buffer-type) + (window-state-put window-config (frame-root-window)) + (dolist (window (window-list nil 'no-minibuffer)) + (with-selected-window window + (setq buffer-type (window-parameter nil 'gdb-buffer-type)) + (pcase buffer-type + ('source (when source-buffer + (set-window-buffer nil source-buffer) + (push (selected-window) gdb-source-window-list))) + ('command (switch-to-buffer gud-comint-buffer)) + (_ (let ((buffer (gdb-get-buffer-create buffer-type))) + (with-window-non-dedicated nil + (set-window-buffer nil buffer)))))))) + (kill-buffer placeholder)))) (define-minor-mode gdb-many-windows "If nil just pop up the GUD buffer unless `gdb-show-main' is t. @@ -4631,7 +4922,12 @@ of the debugged program. Non-nil means display the layout shown for (defun gdb-restore-windows () "Restore the basic arrangement of windows used by gdb. -This arrangement depends on the value of option `gdb-many-windows'." +This arrangement depends on the values of variable +`gdb-many-windows' and `gdb-default-window-configuration-file'." + ;; This function is used when the user messed up window + ;; configuration and wants to "reset to default". The function that + ;; sets up window configuration on start up is + ;; `gdb-get-source-file'. (interactive) (switch-to-buffer gud-comint-buffer) ;Select the right window and frame. (delete-other-windows) @@ -4644,7 +4940,7 @@ This arrangement depends on the value of option `gdb-many-windows'." (if gud-last-last-frame (gud-find-file (car gud-last-last-frame)) (gud-find-file gdb-main-file))) - (setq gdb-source-window win))))) + (setq gdb-source-window-list (list win)))))) ;; Called from `gud-sentinel' in gud.el: (defun gdb-reset () @@ -4678,14 +4974,28 @@ Kills the gdb buffers, and resets variables and the source buffers." (if (boundp 'speedbar-frame) (speedbar-timer-fn)) (setq gud-running nil) (setq gdb-active-process nil) - (remove-hook 'after-save-hook 'gdb-create-define-alist t)) + (remove-hook 'after-save-hook 'gdb-create-define-alist t) + ;; Recover window configuration. + (when (or (eq gdb-restore-window-configuration-after-quit t) + (and (eq gdb-restore-window-configuration-after-quit + 'if-gdb-show-main) + gdb-show-main) + (and (eq gdb-restore-window-configuration-after-quit + 'if-gdb-many-windows) + gdb-many-windows)) + (when gdb--window-configuration-before + (window-state-put gdb--window-configuration-before) + ;; This way we don't accidentally restore an outdated window + ;; configuration. + (setq gdb--window-configuration-before nil)))) (defun gdb-get-source-file () "Find the source file where the program starts and display it with related buffers, if required." + ;; This function is called only once on startup. (goto-char (point-min)) (if (re-search-forward gdb-source-file-regexp nil t) - (setq gdb-main-file (read (match-string 1)))) + (setq gdb-main-file (gdb-mi--c-string-from-string (match-string 1)))) (if gdb-many-windows (gdb-setup-windows) (gdb-get-buffer-create 'gdb-breakpoints-buffer) |