Mercurial > emacs
diff lisp/progmodes/gdb-mi.el @ 104151:22070e4cdf2a
* progmodes/gud.el (gud-stop-subjob, gud-menu-map): Respect GDB
non-stop settings.
* progmodes/gdb-mi.el (gdb-thread-number): Initialize with nil.
(gdb-current-context-command): Do not append --thread if
`gdb-thread-number' is nil.
(gdb-running-threads-count, gdb-stopped-threads-count): New
variables.
(gdb-non-stop, gdb-gud-control-all-threads, gdb-switch-reasons)
(gdb-stopped-hooks, gdb-switch-when-another-stopped): New
customization options.
(gdb-gud-context-command, gdb-gud-context-call): New wrappers for
GUD commands.
(gdb): `gud-def' definitions changed to use `gdb-gud-context-call'
(gdb-init-1): Activate non-stop mode if `gdb-non-stop' is enabled.
(gdb-setq-thread-number, gdb-update-gud-running): New functions to
set `gdb-thread-number' and update `gud-running' properly.
(gdb-running): Update threads list when new threads appear.
(gdb-stopped): Support non-stop operation and new thread switching
logic.
(gdb-jsonify-buffer, gdb-json-read-buffer, gdb-json-string)
(gdb-json-partial-output): New set of JSON routines.
(def-gdb-auto-update-trigger): New `signal-list' optional
argument.
(gdb-thread-list-handler-custom): Update `gud-running',
`gdb-stopped-threads-count' and `gdb-running-threads-count'.
(def-gdb-thread-buffer-gdb-command, gdb-interrupt-thread)
(gdb-continue-thread, gdb-step-thread): New commands for fine
thread execution control.
(gud-menu-map): New menu items to switch non-stop options.
(gdb-reset): Cleanup `gdb-thread-position' overlay arrow marker.
author | Dmitry Dzhus <dima@sphinx.net.ru> |
---|---|
date | Tue, 04 Aug 2009 15:07:23 +0000 |
parents | 925e1efc6761 |
children | 957779ca8cea |
line wrap: on
line diff
--- a/lisp/progmodes/gdb-mi.el Tue Aug 04 14:40:33 2009 +0000 +++ b/lisp/progmodes/gdb-mi.el Tue Aug 04 15:07:23 2009 +0000 @@ -116,16 +116,18 @@ "Address of previous memory page for program memory buffer.") (defvar gdb-frame-number "0") -(defvar gdb-thread-number "1" +(defvar gdb-thread-number nil "Main current thread. Invalidation triggers use this variable to query GDB for information on the specified thread by wrapping GDB/MI commands in `gdb-current-context-command'. -This variable may be updated implicitly by GDB via -`gdb-thread-list-handler-custom' or explicitly by -`gdb-select-thread'.") +This variable may be updated implicitly by GDB via `gdb-stopped' +or explicitly by `gdb-select-thread'. + +Only `gdb-setq-thread-number' should be used to change this +value.") ;; Used to show overlay arrow in source buffer. All set in ;; gdb-get-main-selected-frame. Disassembly buffer should not use @@ -141,14 +143,26 @@ "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 `json-partial-output'. Updated in +returned from -thread-info by `gdb-json-partial-output'. Updated in `gdb-thread-list-handler-custom'.") +(defvar gdb-running-threads-count nil + "Number of currently running threads. + +Nil means that no information is available. + +Updated in `gdb-thread-list-handler-custom'.") + +(defvar gdb-stopped-threads-count nil + "Number of currently stopped threads. + +See also `gdb-running-threads-count'.") + (defvar gdb-breakpoints-list nil "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 `json-partial-output' +as returned from \"-break-list\" by `gdb-json-partial-output' \(\"body\" field is used). Updated in `gdb-breakpoints-list-handler-custom'.") @@ -226,6 +240,85 @@ (const :tag "Unlimited" nil)) :version "22.1") +(defcustom gdb-non-stop t + "When in non-stop mode, stopped threads can be examined while +other threads continue to execute." + :type 'boolean + :group 'gdb + :version "23.2") + +;; TODO Some commands can't be called with --all (give a notice about +;; it in setting doc) +(defcustom gdb-gud-control-all-threads t + "When enabled, GUD execution commands affect all threads when +in non-stop mode. Otherwise, only currently selected thread is +affected." + :type 'boolean + :group 'gdb + :version "23.2") + +(defcustom gdb-switch-reasons t + "List of stop reasons which cause Emacs to switch to the thread +which caused the stop. When t, switch to stopped thread no matter +what the reason was. When nil, never switch to stopped thread +automatically. + +This setting is used in non-stop mode only. In all-stop mode, +Emacs always switches to the thread which caused the stop." + ;; exited, exited-normally and exited-signalled are not + ;; thread-specific stop reasons and therefore are not included in + ;; this list + :type '(choice + (const :tag "All reasons" t) + (set :tag "Selection of reasons..." + (const :tag "A breakpoint was reached." "breakpoint-hit") + (const :tag "A watchpoint was triggered." "watchpoint-trigger") + (const :tag "A read watchpoint was triggered." "read-watchpoint-trigger") + (const :tag "An access watchpoint was triggered." "access-watchpoint-trigger") + (const :tag "Function finished execution." "function-finished") + (const :tag "Location reached." "location-reached") + (const :tag "Watchpoint has gone out of scope" "watchpoint-scope") + (const :tag "End of stepping range reached." "end-stepping-range") + (const :tag "Signal received (like interruption)." "signal-received")) + (const :tag "None" nil)) + :group 'gdb + :version "23.2" + :link '(info-link "(gdb)GDB/MI Async Records")) + +(defcustom gdb-stopped-hooks nil + "This variable holds a list of functions to be called whenever +GDB stops. + +Each function takes one argument, a parsed MI response, which +contains fields of corresponding MI *stopped async record: + + ((stopped-threads . \"all\") + (thread-id . \"1\") + (frame (line . \"38\") + (fullname . \"/home/sphinx/projects/gsoc/server.c\") + (file . \"server.c\") + (args ((value . \"0x804b038\") + (name . \"arg\"))) + (func . \"hello\") + (addr . \"0x0804869e\")) + (reason . \"end-stepping-range\")) + +`gdb-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) + :group 'gdb + :version "23.2" + :link '(info-link "(gdb)GDB/MI Async Records")) + +(defcustom gdb-switch-when-another-stopped t + "When nil, Emacs won't switch to stopped thread if some other +stopped thread is already selected." + :type 'boolean + :group 'gdb + :version "23.2") + (defvar gdb-debug-log nil "List of commands sent to and replies received from GDB. Most recent commands are listed first. This list stores only the last @@ -329,6 +422,29 @@ ) "Font lock keywords used in `gdb-local-mode'.") +;; noall is used for commands which don't take --all, but only +;; --thread. +(defun gdb-gud-context-command (command &optional noall) + "When `gdb-non-stop' is t, add --thread option to COMMAND if +`gdb-gud-control-all-threads' is nil and --all option otherwise. +If NOALL is t, always add --thread option no matter what +`gdb-gud-control-all-threads' value is. + +When `gdb-non-stop' is nil, return COMMAND unchanged." + (if gdb-non-stop + (if (and gdb-gud-control-all-threads + (not noall)) + (concat command " --all ") + (gdb-current-context-command command)) + command)) + +;; TODO Document this. We use noarg when not in gud-def +(defmacro gdb-gud-context-call (cmd1 &optional cmd2 noall noarg) + `(gud-call + (concat + (gdb-gud-context-command ,cmd1 ,noall) + ,cmd2) ,(when (not noarg) 'arg))) + ;;;###autoload (defun gdb (command-line) "Run gdb on program FILE in buffer *gud-FILE*. @@ -404,27 +520,28 @@ (gud-def gud-pstar "print* %e" nil "Evaluate C dereferenced pointer expression at point.") - (gud-def gud-step "-exec-step %p" "\C-s" + (gud-def gud-step (gdb-gud-context-call "-exec-step" "%p" t) + "\C-s" "Step one source line with display.") - (gud-def gud-stepi "-exec-step-instruction %p" "\C-i" + (gud-def gud-stepi (gdb-gud-context-call "-exec-step-instruction" "%p" t) + "\C-i" "Step one instruction with display.") - (gud-def gud-next "-exec-next %p" "\C-n" + (gud-def gud-next (gdb-gud-context-call "-exec-next" "%p" t) + "\C-n" "Step one line (skip functions).") - (gud-def gud-nexti "nexti %p" nil + (gud-def gud-nexti (gdb-gud-context-call "-exec-next-instruction" "%p" t) + nil "Step one instruction (skip functions).") - (gud-def gud-cont "-exec-continue" "\C-r" + (gud-def gud-cont (gdb-gud-context-call "-exec-continue") + "\C-r" "Continue with display.") - (gud-def gud-finish "-exec-finish" "\C-f" + (gud-def gud-finish (gdb-gud-context-call "-exec-finish" nil t) + "\C-f" "Finish executing current function.") - (gud-def gud-run "-exec-run" nil "Runn the program.") - - (local-set-key "\C-i" 'gud-gdb-complete-command) - (setq gdb-first-prompt t) - (setq gud-running nil) - (gdb-update) - (run-hooks 'gdb-mode-hook)) - -(defun gdb-init-1 () + (gud-def gud-run "-exec-run" + nil + "Run the program.") + (gud-def gud-break (if (not (string-match "Disassembly" mode-name)) (gud-call "break %f:%l" arg) (save-excursion @@ -432,7 +549,7 @@ (forward-char 2) (gud-call "break *%a" arg))) "\C-b" "Set breakpoint at current line or address.") - ;; + (gud-def gud-remove (if (not (string-match "Disassembly" mode-name)) (gud-call "clear %f:%l" arg) (save-excursion @@ -440,7 +557,8 @@ (forward-char 2) (gud-call "clear *%a" arg))) "\C-d" "Remove breakpoint at current line or address.") - ;; + + ;; -exec-until doesn't support --all yet (gud-def gud-until (if (not (string-match "Disassembly" mode-name)) (gud-call "-exec-until %f:%l" arg) (save-excursion @@ -448,9 +566,11 @@ (forward-char 2) (gud-call "-exec-until *%a" arg))) "\C-u" "Continue to current line or address.") - ;; + ;; TODO Why arg here? (gud-def - gud-go (gud-call (if gdb-active-process "-exec-continue" "-exec-run") arg) + gud-go (gud-call (if gdb-active-process + (gdb-gud-context-command "-exec-continue") + "-exec-run") arg) nil "Start or continue execution.") ;; For debugging Emacs only. @@ -488,7 +608,14 @@ 'gdb-mouse-jump) (define-key gud-minor-mode-map [left-margin C-mouse-3] 'gdb-mouse-jump) - ;; + + (local-set-key "\C-i" 'gud-gdb-complete-command) + (setq gdb-first-prompt t) + (setq gud-running nil) + (gdb-update) + (run-hooks 'gdb-mode-hook)) + +(defun gdb-init-1 () ;; (re-)initialise (setq gdb-selected-frame nil gdb-frame-number nil @@ -507,13 +634,15 @@ gdb-debug-log nil gdb-source-window nil gdb-inferior-status nil - gdb-continuation nil) + gdb-continuation nil + gdb-buf-publisher '() + gdb-threads-list '() + gdb-breakpoints-list '()) ;; (setq gdb-buffer-type 'gdbmi) ;; (gdb-force-mode-line-update (propertize "initializing..." 'face font-lock-variable-name-face)) - (setq gdb-buf-publisher '()) (when gdb-use-separate-io-buffer (gdb-get-buffer-create 'gdb-inferior-io) (gdb-clear-inferior-io) @@ -526,6 +655,11 @@ (if (eq window-system 'w32) (gdb-input (list "-gdb-set new-console off" 'ignore))) (gdb-input (list "-gdb-set height 0" 'ignore)) + + (when gdb-non-stop + (gdb-input (list "-gdb-set non-stop 1" 'ignore)) + (gdb-input (list "-gdb-set target-async 1" 'ignore))) + ;; find source file and compilation directory here (gdb-input ; Needs GDB 6.2 onwards. @@ -944,11 +1078,14 @@ (assoc gdb-buffer-type gdb-buffer-rules)) (defun gdb-current-buffer-thread () - "Get thread of current buffer from `gdb-threads-list'." + "Get thread object of current buffer from `gdb-threads-list'. + +When current buffer is not bound to any thread, return main +thread." (cdr (assoc gdb-thread-number gdb-threads-list))) (defun gdb-current-buffer-frame () - "Get current stack frame for thread of current buffer." + "Get current stack frame object for thread of current buffer." (gdb-get-field (gdb-current-buffer-thread) 'frame)) (defun gdb-get-buffer (key &optional thread) @@ -1043,6 +1180,7 @@ (defun gdb-parent-mode () "Generic mode to derive all other GDB buffer modes from." + (kill-all-local-variables) (setq buffer-read-only t) (buffer-disable-undo) ;; Delete buffer from gdb-buf-publisher when it's killed @@ -1256,7 +1394,7 @@ (let ((inhibit-read-only t)) (remove-text-properties (point-min) (point-max) '(face)))) ;; mimic <RET> key to repeat previous command in GDB - (if (not (string-match "^\\s+$" string)) + (if (not (string= "" string)) (setq gdb-last-command string) (if gdb-last-command (setq string gdb-last-command))) (if gdb-enable-debug @@ -1285,8 +1423,11 @@ (defun gdb-current-context-command (command) "Add --thread option to gdb COMMAND. -Option value is taken from `gdb-thread-number'." - (concat command " --thread " gdb-thread-number)) +Option value is taken from `gdb-thread-number'. If +`gdb-thread-number' is nil, COMMAND is returned unchanged." + (if gdb-thread-number + (concat command " --thread " gdb-thread-number " ") + command)) (defun gdb-current-context-buffer-name (name) "Add thread information and asterisks to string NAME." @@ -1343,15 +1484,15 @@ (propertize "initializing..." 'face font-lock-variable-name-face)) (gdb-init-1) (setq gdb-first-prompt nil)) - ;; We may need to update gdb-thread-number and gdb-threads-list + ;; We may need to update gdb-threads-list so we can use (gdb-get-buffer-create 'gdb-threads-buffer) ;; gdb-break-list is maintained in breakpoints handler (gdb-get-buffer-create 'gdb-breakpoints-buffer) + (gdb-emit-signal gdb-buf-publisher 'update) + (gdb-get-main-selected-frame) - (gdb-emit-signal gdb-buf-publisher 'update) - (gdb-get-changed-registers) (when (and (boundp 'speedbar-frame) (frame-live-p speedbar-frame)) @@ -1359,6 +1500,28 @@ (setcar (nthcdr 5 var) nil)) (gdb-var-update))) +;; gdb-setq-thread-number and gdb-update-gud-running are decoupled +;; because we may need to update current gud-running value without +;; changing current thread (see gdb-running) +(defun gdb-setq-thread-number (number) + "Set `gdb-thread-number' to NUMBER and update `gud-running'." + (setq gdb-thread-number number) + (gdb-update-gud-running)) + +(defun gdb-update-gud-running () + "Set `gud-running' according to the state of current thread. + +Note that when `gdb-gud-control-all-threads' is t, `gud-running' +cannot be reliably used to determine whether or not execution +control buttons should be shown in menu or toolbar. Use +`gdb-running-threads-count' and `gdb-stopped-threads-count' +instead. + +For all-stop mode, thread information is unavailable while target is running" + (setq gud-running + (string= (gdb-get-field (gdb-current-buffer-thread) 'state) + "running"))) + ;; GUD displays the selected GDB frame. This might might not be the current ;; GDB frame (after up, down etc). If no GDB frame is visible but the last ;; visited breakpoint is, use that window. @@ -1385,7 +1548,7 @@ (gdb-error . "\\([0-9]*\\)\\^error,\\(.*?\\)\n") (gdb-console . "~\\(\".*?\"\\)\n") (gdb-internals . "&\\(\".*?\"\\)\n") - (gdb-stopped . "\\*stopped,?\\(.*?\n\\)") + (gdb-stopped . "\\*stopped,?\\(.*?\\)\n") (gdb-running . "\\*running,\\(.*?\n\\)") (gdb-thread-created . "=thread-created,\\(.*?\n\\)") (gdb-thread-exited . "=thread-exited,\\(.*?\n\\)"))) @@ -1446,15 +1609,20 @@ gdb-filter-output)) (defun gdb-gdb (output-field)) + +;; gdb-invalidate-threads is defined to accept 'update-threads signal (defun gdb-thread-created (output-field)) -(defun gdb-thread-exited (output-field)) +(defun gdb-thread-exited (output-field) + (gdb-emit-signal gdb-buf-publisher 'update-threads)) (defun gdb-running (output-field) (setq gdb-inferior-status "running") (gdb-force-mode-line-update (propertize gdb-inferior-status 'face font-lock-type-face)) + (when (not gdb-non-stop) + (setq gud-running t)) (setq gdb-active-process t) - (setq gud-running t)) + (gdb-emit-signal gdb-buf-publisher 'update-threads)) (defun gdb-starting (output-field) ;; CLI commands don't emit ^running at the moment so use gdb-running too. @@ -1464,17 +1632,18 @@ (gdb-force-mode-line-update (propertize gdb-inferior-status 'face font-lock-type-face)) (setq gdb-active-process t) - (setq gud-running t)) + (when (not gdb-non-stop) + (setq gud-running t))) ;; -break-insert -t didn't give a reason before gdb 6.9 -(defconst gdb-stopped-regexp - "\\(reason=\"\\(.*?\\)\"\\)?\\(\\(,exit-code=.*?\\)*\n\\|.*?,file=\".*?\".*?,fullname=\"\\(.*?\\)\".*?,line=\"\\(.*?\\)\".*?\n\\)") (defun gdb-stopped (output-field) - (setq gud-running nil) - (string-match gdb-stopped-regexp output-field) - (let ((reason (match-string 2 output-field)) - (file (match-string 5 output-field))) + "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 (gdb-get-field result 'reason)) + (thread-id (gdb-get-field result 'thread-id))) ;;; Don't set gud-last-frame here as it's currently done in gdb-frame-handler ;;; because synchronous GDB doesn't give these fields with CLI. @@ -1485,16 +1654,42 @@ ;;; (string-to-number ;;; (match-string 6 gud-marker-acc))))) - (setq gdb-inferior-status (if reason reason "unknown")) + (setq gdb-inferior-status (or reason "unknown")) (gdb-force-mode-line-update (propertize gdb-inferior-status 'face font-lock-warning-face)) (if (string-equal reason "exited-normally") - (setq gdb-active-process nil))) - + (setq gdb-active-process nil)) + + ;; Select new current thread. + + ;; Don't switch if we have no reasons selected + (when gdb-switch-reasons + ;; Switch from another stopped thread only if we have + ;; gdb-switch-when-another-stopped: + (when (or gdb-switch-when-another-stopped + (not (string= "stopped" + (gdb-get-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) + (member reason gdb-switch-reasons)) + (progn + (gdb-setq-thread-number thread-id) + (message (concat "Switched to thread " thread-id))) + (message (format "Thread %s stopped" thread-id))))) + + ;; Print "(gdb)" to GUD console (when gdb-first-done-or-error - (setq gdb-filter-output (concat gdb-filter-output gdb-prompt-name)) + (setq gdb-filter-output (concat gdb-filter-output gdb-prompt-name))) + + ;; In non-stop, we update information as soon as another thread gets + ;; stopped + (when (or gdb-first-done-or-error + gdb-non-stop) + ;; In all-stop this updates gud-running properly as well. (gdb-update) - (setq gdb-first-done-or-error nil))) + (setq gdb-first-done-or-error nil)) + (run-hook-with-args 'gdb-stopped-hook result))) ;; Remove the trimmings from log stream containing debugging messages ;; being produced by GDB's internals, use warning face and send to GUD @@ -1571,8 +1766,11 @@ (with-current-buffer (gdb-get-buffer-create 'gdb-partial-output-buffer) (erase-buffer))) -(defun json-partial-output (&optional fix-key fix-list) - "Parse gdb-partial-output-buffer with `json-read'. +(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=\" occurences from partial output. This is used to get rid of useless keys in lists @@ -1583,20 +1781,17 @@ 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. - -Note that GDB/MI output syntax is different from JSON both -cosmetically and (in some cases) structurally, so correct results -are not guaranteed." - (with-current-buffer (gdb-get-buffer-create 'gdb-partial-output-buffer) +incompatible with GDB/MI output syntax." + (save-excursion (goto-char (point-min)) (when fix-key (save-excursion (while (re-search-forward (concat "[\\[,]\\(" fix-key "=\\)") nil t) (replace-match "" nil nil nil 1)))) + ;; Emacs bug #3794 (when fix-list (save-excursion - ;; Find positions of brackets which enclose broken list + ;; 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) @@ -1611,17 +1806,37 @@ (insert "]")))))) (goto-char (point-min)) (insert "{") - ;; Wrap field names in double quotes and replace equal sign with - ;; semicolon. ;; TODO: This breaks badly with foo= inside constants (while (re-search-forward "\\([[:alpha:]-_]+\\)=" nil t) (replace-match "\"\\1\":" nil nil)) (goto-char (point-max)) - (insert "}") + (insert "}"))) + +(defun gdb-json-read-buffer (&optional fix-key fix-list) + "Prepare and parse GDB/MI output in current buffer with `json-read'. + +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'." + (with-temp-buffer + (insert string) + (gdb-json-read-buffer fix-key fix-list))) + +(defun gdb-json-partial-output (&optional fix-key fix-list) + "Prepare and parse gdb-partial-output-buffer with `json-read'. + +FIX-KEY and FIX-KEY work as in `gdb-jsonify-buffer'." + (with-current-buffer (gdb-get-buffer-create 'gdb-partial-output-buffer) + (gdb-json-read-buffer fix-key fix-list))) + (defun gdb-pad-string (string padding) (format (concat "%" (number-to-string padding) "s") string)) @@ -1634,29 +1849,35 @@ (setq values (append values (list (gdb-get-field struct field))))))) (defmacro def-gdb-auto-update-trigger (trigger-name gdb-command - handler-name) + handler-name + &optional signal-list) "Define a trigger TRIGGER-NAME which sends GDB-COMMAND and sets HANDLER-NAME as its handler. HANDLER-NAME is bound to current buffer with `gdb-bind-function-to-buffer'. +If SIGNAL-LIST is non-nil, GDB-COMMAND is sent only when the +defined trigger is called with an argument from SIGNAL-LIST. + Normally the trigger defined by this command must be called from the buffer where HANDLER-NAME must work. This should be done so that buffer-local thread number may be used in GDB-COMMAND (by calling `gdb-current-context-command'). -`gdb-bind-function-to-buffer' is used to achieve this, see how -it's done in `gdb-get-buffer-create'. +`gdb-bind-function-to-buffer' is used to achieve this, see +`gdb-get-buffer-create'. Triggers defined by this command are meant to be used as a trigger argument when describing buffer types with `gdb-set-buffer-rules'." `(defun ,trigger-name (&optional signal) - (if (not (gdb-pending-p - (cons (current-buffer) ',trigger-name))) - (progn - (gdb-input - (list ,gdb-command - (gdb-bind-function-to-buffer ',handler-name (current-buffer)))) - (gdb-add-pending (cons (current-buffer) ',trigger-name)))))) + (when + (or (not ,signal-list) + (memq signal ,signal-list)) + (when (not (gdb-pending-p + (cons (current-buffer) ',trigger-name))) + (gdb-input + (list ,gdb-command + (gdb-bind-function-to-buffer ',handler-name (current-buffer)))) + (gdb-add-pending (cons (current-buffer) ',trigger-name)))))) ;; Used by disassembly buffer only, the rest use ;; def-gdb-trigger-and-handler @@ -1665,9 +1886,9 @@ Handlers are normally called from the buffers they put output in. -Delete ((current-buffer) . TRIGGER) from `gdb-pending-triggers', -erase current buffer and evaluate CUSTOM-DEFUN. Then -`gdb-update-buffer-name' is called. +Delete ((current-buffer) . TRIGGER-NAME) from +`gdb-pending-triggers', erase current buffer and evaluate +CUSTOM-DEFUN. Then `gdb-update-buffer-name' is called. If NOPRESERVE is non-nil, window point is not restored after CUSTOM-DEFUN." `(defun ,handler-name () @@ -1684,18 +1905,19 @@ '(set-window-point window p))))) (defmacro def-gdb-trigger-and-handler (trigger-name gdb-command - handler-name custom-defun) + handler-name custom-defun + &optional signal-list) "Define trigger and handler. TRIGGER-NAME trigger is defined to send GDB-COMMAND. See -`def-gdb-auto-update-trigger'. +`def-gdb-auto-update-trigger'. SIGNAL-LIST determines when HANDLER-NAME handler uses customization of CUSTOM-DEFUN. See `def-gdb-auto-update-handler'." `(progn (def-gdb-auto-update-trigger ,trigger-name ,gdb-command - ,handler-name) + ,handler-name ,signal-list) (def-gdb-auto-update-handler ,handler-name ,trigger-name ,custom-defun))) @@ -1714,7 +1936,7 @@ (defun gdb-breakpoints-list-handler-custom () (let ((breakpoints-list (gdb-get-field - (json-partial-output "bkpt" "script") + (gdb-json-partial-output "bkpt" "script") 'BreakpointTable 'body))) (setq gdb-breakpoints-list nil) (insert "Num\tType\t\tDisp\tEnb\tHits\tAddr What\n") @@ -1730,7 +1952,7 @@ (let ((flag (gdb-get-field breakpoint 'enabled))) (if (string-equal flag "y") (propertize "y" 'face font-lock-warning-face) - (propertize "n" 'face font-lock-type-face))) "\t" + (propertize "n" 'face font-lock-comment-face))) "\t" (gdb-get-field breakpoint 'times) "\t" (gdb-get-field breakpoint 'addr))) (let ((at (gdb-get-field breakpoint 'at))) @@ -2026,7 +2248,8 @@ (def-gdb-trigger-and-handler gdb-invalidate-threads "-thread-info" - gdb-thread-list-handler gdb-thread-list-handler-custom) + gdb-thread-list-handler gdb-thread-list-handler-custom + '(update update-threads)) (gdb-set-buffer-rules 'gdb-threads-buffer @@ -2037,20 +2260,24 @@ (defvar gdb-threads-font-lock-keywords '(("in \\([^ ]+\\) (" (1 font-lock-function-name-face)) (" \\(stopped\\) in " (1 font-lock-warning-face)) + (" \\(running\\)" (1 font-lock-string-face)) ("\\(\\(\\sw\\|[_.]\\)+\\)=" (1 font-lock-variable-name-face))) "Font lock keywords used in `gdb-threads-mode'.") (defvar gdb-threads-mode-map (let ((map (make-sparse-keymap))) (define-key map "\r" 'gdb-select-thread) - (define-key map "s" 'gdb-display-stack-for-thread) - (define-key map "S" 'gdb-frame-stack-for-thread) + (define-key map "f" 'gdb-display-stack-for-thread) + (define-key map "F" 'gdb-frame-stack-for-thread) (define-key map "l" 'gdb-display-locals-for-thread) (define-key map "L" 'gdb-frame-locals-for-thread) (define-key map "r" 'gdb-display-registers-for-thread) (define-key map "R" 'gdb-frame-registers-for-thread) (define-key map "d" 'gdb-display-disassembly-for-thread) (define-key map "D" 'gdb-frame-disassembly-for-thread) + (define-key map "i" 'gdb-interrupt-thread) + (define-key map "c" 'gdb-continue-thread) + (define-key map "s" 'gdb-step-thread) map)) (defvar gdb-breakpoints-header @@ -2073,45 +2300,52 @@ 'gdb-invalidate-threads) (defun gdb-thread-list-handler-custom () - (let* ((res (json-partial-output)) - (threads-list (gdb-get-field res 'threads)) - (current-thread (gdb-get-field res 'current-thread-id))) + (let* ((res (gdb-json-partial-output)) + (threads-list (gdb-get-field res 'threads))) (setq gdb-threads-list nil) - (when (and current-thread - (not (string-equal current-thread gdb-thread-number))) - ;; Implicitly switch thread (in case previous one dies) - (message (concat "GDB switched to another thread: " current-thread)) - (setq gdb-thread-number current-thread)) + (setq gdb-running-threads-count 0) + (setq gdb-stopped-threads-count 0) (set-marker gdb-thread-position nil) - (dolist (thread threads-list) + + (dolist (thread (reverse threads-list)) + (let ((running (string-equal (gdb-get-field thread 'state) "running"))) (add-to-list 'gdb-threads-list (cons (gdb-get-field thread 'id) thread)) - (insert (apply 'format `("%s (%s) %s in %s " - ,@(gdb-get-many-fields thread 'id 'target-id 'state) - ,(gdb-get-field thread 'frame 'func)))) - ;; Arguments - (insert "(") - (let ((args (gdb-get-field thread 'frame 'args))) - (dolist (arg args) - (insert (apply 'format `("%s=%s" ,@(gdb-get-many-fields arg 'name 'value))))) - (when args (kill-backward-chars 1))) - (insert ")") - (gdb-insert-frame-location (gdb-get-field thread 'frame)) - (insert (format " at %s" (gdb-get-field thread 'frame 'addr))) + (if running + (incf gdb-running-threads-count) + (incf gdb-stopped-threads-count)) + + (insert (apply 'format `("%s (%s) %s" + ,@(gdb-get-many-fields thread 'id 'target-id 'state)))) + ;; Include frame information for stopped threads + (when (not running) + (insert (concat " in " (gdb-get-field thread 'frame 'func))) + (insert " (") + (let ((args (gdb-get-field thread 'frame 'args))) + (dolist (arg args) + (insert (apply 'format `("%s=%s," ,@(gdb-get-many-fields arg 'name 'value))))) + (when args (kill-backward-chars 1))) + (insert ")") + (gdb-insert-frame-location (gdb-get-field thread 'frame)) + (insert (format " at %s" (gdb-get-field thread 'frame 'addr)))) (add-text-properties (line-beginning-position) (line-end-position) `(gdb-thread ,thread)) + ;; We assume that gdb-thread-number is non-nil by this time (when (string-equal gdb-thread-number (gdb-get-field thread 'id)) - (set-marker gdb-thread-position (line-beginning-position))) - (newline)))) + (set-marker gdb-thread-position (line-beginning-position)))) + (newline)) + ;; We update gud-running here because we need to make sure that + ;; gdb-threads-list is up-to-date + (gdb-update-gud-running))) (defmacro def-gdb-thread-buffer-command (name custom-defun &optional doc) "Define a NAME command which will act upon thread on the current line. CUSTOM-DEFUN may use locally bound `thread' variable, which will -be the value of 'gdb-thread propery of the current line. If +be the value of 'gdb-thread property of the current line. If 'gdb-thread is nil, error is signaled." `(defun ,name () ,(when doc doc) @@ -2131,12 +2365,10 @@ ,doc)) (def-gdb-thread-buffer-command gdb-select-thread - (if (string-equal (gdb-get-field thread 'state) "running") - (error "Cannot select running thread") - (let ((new-id (gdb-get-field thread 'id))) - (setq gdb-thread-number new-id) - (gdb-input (list (concat "-thread-select " new-id) 'ignore)) - (gdb-update))) + (let ((new-id (gdb-get-field thread 'id))) + (gdb-setq-thread-number new-id) + (gdb-input (list (concat "-thread-select " new-id) 'ignore)) + (gdb-update)) "Select the thread at current line of threads buffer.") (def-gdb-thread-simple-buffer-command @@ -2183,6 +2415,34 @@ "Display a new frame with disassembly buffer for the thread at current line.") +(defmacro def-gdb-thread-buffer-gdb-command (name gdb-command &optional doc) + "Define a NAME which will execute send GDB-COMMAND with +`gdb-thread-number' locally bound to id of thread on the current +line." + `(def-gdb-thread-buffer-command ,name + (if gdb-non-stop + (let ((gdb-thread-number (gdb-get-field thread 'id))) + (gdb-input (list (gdb-current-context-command ,gdb-command) + 'ignore))) + (error "Available in non-stop mode only, customize gdb-non-stop.")) + ,doc)) + +;; Does this make sense in all-stop mode? +(def-gdb-thread-buffer-gdb-command + gdb-interrupt-thread + "-exec-interrupt" + "Interrupt thread at current line.") + +(def-gdb-thread-buffer-gdb-command + gdb-continue-thread + "-exec-continue" + "Continue thread at current line.") + +(def-gdb-thread-buffer-gdb-command + gdb-step-thread + "-exec-step" + "Step thread at current line.") + ;;; Memory view @@ -2255,7 +2515,7 @@ (error "Unknown format")))) (defun gdb-read-memory-custom () - (let* ((res (json-partial-output)) + (let* ((res (gdb-json-partial-output)) (err-msg (gdb-get-field res 'msg))) (if (not err-msg) (let ((memory (gdb-get-field res 'memory))) @@ -2635,6 +2895,7 @@ "Major mode for GDB disassembly information. \\{gdb-disassembly-mode-map}" + ;; TODO Rename overlay variable for disassembly mode (add-to-list 'overlay-arrow-variable-list 'gdb-overlay-arrow-position) (setq fringes-outside-margins t) (setq gdb-overlay-arrow-position (make-marker)) @@ -2646,7 +2907,7 @@ (defun gdb-disassembly-handler-custom () (let* ((pos 1) (address (gdb-get-field (gdb-current-buffer-frame) 'addr)) - (res (json-partial-output)) + (res (gdb-json-partial-output)) (instructions (gdb-get-field res 'asm_insns)) (last-instr (car (last instructions))) (column-padding (+ 2 (string-width @@ -2783,7 +3044,7 @@ (from (insert (format " of %s" from)))))) (defun gdb-stack-list-frames-custom () - (let* ((res (json-partial-output "frame")) + (let* ((res (gdb-json-partial-output "frame")) (stack (gdb-get-field res 'stack))) (dolist (frame stack) (insert (apply 'format `("%s in %s" ,@(gdb-get-many-fields frame 'level 'func)))) @@ -2904,7 +3165,7 @@ ;; Dont display values of arrays or structures. ;; These can be expanded using gud-watch. (defun gdb-locals-handler-custom () - (let ((locals-list (gdb-get-field (json-partial-output) 'locals))) + (let ((locals-list (gdb-get-field (gdb-json-partial-output) 'locals))) (dolist (local locals-list) (let ((name (gdb-get-field local 'name)) (value (gdb-get-field local 'value)) @@ -2981,7 +3242,7 @@ 'gdb-invalidate-registers) (defun gdb-registers-handler-custom () - (let ((register-values (gdb-get-field (json-partial-output) 'register-values)) + (let ((register-values (gdb-get-field (gdb-json-partial-output) 'register-values)) (register-names-list (reverse gdb-register-names))) (dolist (register register-values) (let* ((register-number (gdb-get-field register 'number)) @@ -3039,14 +3300,14 @@ (defun gdb-changed-registers-handler () (gdb-delete-pending 'gdb-get-changed-registers) (setq gdb-changed-registers nil) - (dolist (register-number (gdb-get-field (json-partial-output) 'changed-registers)) + (dolist (register-number (gdb-get-field (gdb-json-partial-output) 'changed-registers)) (push register-number gdb-changed-registers))) (defun gdb-register-names-handler () ;; Don't use gdb-pending-triggers because this handler is called ;; only once (in gdb-init-1) (setq gdb-register-names nil) - (dolist (register-name (gdb-get-field (json-partial-output) 'register-names)) + (dolist (register-name (gdb-get-field (gdb-json-partial-output) 'register-names)) (push register-name gdb-register-names)) (setq gdb-register-names (reverse gdb-register-names))) @@ -3078,7 +3339,7 @@ "Sets `gdb-pc-address', `gdb-selected-frame' and `gdb-selected-file' to show overlay arrow in source buffer." (gdb-delete-pending 'gdb-get-main-selected-frame) - (let ((frame (gdb-get-field (json-partial-output) 'frame))) + (let ((frame (gdb-get-field (gdb-json-partial-output) 'frame))) (when frame (setq gdb-frame-number (gdb-get-field frame 'level)) (setq gdb-selected-frame (gdb-get-field frame 'func)) @@ -3165,9 +3426,8 @@ (define-key menu [breakpoints] '("Breakpoints" . gdb-frame-breakpoints-buffer))) -(let ((menu (make-sparse-keymap "GDB-MI"))) - (define-key gud-menu-map [mi] - `(menu-item "GDB-MI" ,menu :visible (eq gud-minor-mode 'gdbmi))) +(let ((menu (make-sparse-keymap "GDB-MI")) + (submenu (make-sparse-keymap "GUD thread control mode"))) (define-key menu [gdb-customize] '(menu-item "Customize" (lambda () (interactive) (customize-group 'gdb)) :help "Customize Gdb Graphical Mode options.")) @@ -3177,7 +3437,37 @@ :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."))) + :help "Restore standard layout for debug session.")) + (define-key menu [sep1] + '(menu-item "--")) + (define-key submenu [all-threads] + '(menu-item "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 submenu [current-thread] + '(menu-item "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 [thread-control] + `("GUD thread control mode" . ,submenu)) + (define-key gud-menu-map [mi] + `(menu-item "GDB-MI" ,menu :visible (eq gud-minor-mode 'gdbmi))) + (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")) + (define-key menu [gdb-non-stop] + (menu-bar-make-toggle gdb-toggle-non-stop gdb-non-stop + "Non-stop mode" + "GDB non-stop mode %s" + "Allow examining stopped threads while others continue to execute"))) (defun gdb-frame-gdb-buffer () "Display GUD buffer in a new frame." @@ -3299,6 +3589,9 @@ (setq gdb-stack-position nil) (setq overlay-arrow-variable-list (delq 'gdb-stack-position overlay-arrow-variable-list)) + (setq gdb-thread-position nil) + (setq overlay-arrow-variable-list + (delq 'gdb-thread-position overlay-arrow-variable-list)) (if (boundp 'speedbar-frame) (speedbar-timer-fn)) (setq gud-running nil) (setq gdb-active-process nil)