├── .flake8 ├── .gitignore ├── .pre-commit-config.yaml ├── Makefile ├── NOTES ├── README.md ├── gdb-gui.py.in ├── gui ├── __init__.py ├── adapt.py ├── bpcache.py ├── commands.py ├── display.py ├── dprintf.py ├── events.py ├── framecache.py ├── gdbutil.py ├── icons │ ├── README │ ├── arrow-down.svg │ ├── arrow-right.svg │ ├── arrow-up.svg │ ├── breakpoint-disabled-marker.png │ ├── breakpoint-marker.png │ ├── corner-right-down.svg │ ├── corner-right-up.svg │ ├── face-raspberry-symbolic.svg │ ├── fast-forward.svg │ ├── line-pointer.png │ ├── ok.png │ ├── ok.xcf │ └── pause.svg ├── invoker.py ├── logwindow.py ├── logwindow.xml ├── notify.py ├── params.py ├── source.py ├── sourcewindow.xml ├── stack.py ├── stackwindow.xml ├── startup.py ├── storage.py ├── toplevel.py └── updatewindow.py └── pyproject.toml /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | # Disable some formatted-related warnings that conflict with black's way of 3 | # formatting code. 4 | # 5 | # E203: Whitespace before ':' 6 | # E501: line too long 7 | # E701: Multiple statements on one line (colon) 8 | ignore = E203,E501,E701 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.pyc 3 | *.o 4 | *.so 5 | SourceMe.py 6 | gdb-gui.py 7 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | repos: 4 | - repo: https://github.com/psf/black-pre-commit-mirror 5 | rev: 24.3.0 6 | hooks: 7 | - id: black 8 | files: 'gdb/.*' 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # This is passed to pkg-config to determine which python to use. It 2 | # has to match your gdb. 3 | all: gdb-gui.py 4 | @: 5 | 6 | gdb-gui.py: gdb-gui.py.in 7 | sed -e "s,HERE,`pwd`," < gdb-gui.py.in > gdb-gui.py 8 | 9 | clean: 10 | -rm gdb-gui.py 11 | 12 | hack-gdbinit: all 13 | if test -f $$HOME/.gdbinit && `grep -q gdb-gui $$HOME/.gdbinit`; then \ 14 | :; \ 15 | else \ 16 | echo "source `pwd`/gdb-gui.py" >> $$HOME/.gdbinit; \ 17 | fi 18 | -------------------------------------------------------------------------------- /NOTES: -------------------------------------------------------------------------------- 1 | A few notes on gdb improvements that would help the GUI: 2 | 3 | * The dprintf code here required some hacks. 4 | Some kind of I/O redirection capability would be helpful. 5 | Also this would require being able to subclass a dprintf breakpoint. 6 | Alternatively, a hook on a Breakpoint that is called when a 7 | linespec is resolved would work. Right now you can't make a pending 8 | "gui dprintf". 9 | 10 | * Some events are missing. This means the GUI can't properly react to 11 | changes. For example: 12 | * No "new breakpoint" event. (And this in turn means that more 13 | breakpoint types must be exposed.) 14 | http://sourceware.org/bugzilla/show_bug.cgi?id=15620 15 | * No "new shared library" event. 16 | http://sourceware.org/bugzilla/show_bug.cgi?id=15621 17 | [ we can use the new objfile event instead ] 18 | * No "new thread" event. 19 | http://sourceware.org/bugzilla/show_bug.cgi?id=15622 20 | * No "frame selection" event, e.g. when user types "up". 21 | https://sourceware.org/bugzilla/show_bug.cgi?id=13598 22 | 23 | * The "set font" and "show font" commands need a bogus doc string to 24 | avoid either a doubled doc line or printing that the command isn't 25 | documented. And, "set font" prints a line unconditionally, see bug 26 | 14513. 27 | 28 | * Trying to add a set/show prefix is hard. I had to add two separate 29 | commands and then couldn't get "set gui" (without further args) to 30 | work the way that it does in gdb. Maybe it should just invoke 31 | "help set gui"? 32 | 33 | ================================================================ 34 | 35 | Windows 36 | 37 | assembly view (?) 38 | signal handling? 39 | list of files in the program 40 | redirect gdb stdlog to a window 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | This is gdb-gui, a GUI for gdb. This GUI differs from existing gdb 4 | GUIs in a few ways: 5 | 6 | * It runs in-process. 7 | 8 | * It is written in Python. 9 | 10 | * It is intended to interoperate well with the CLI. 11 | You can pick and choose which windows you want to see, and you can 12 | still do whatever you like in the terminal. 13 | 14 | * It is totally incomplete. 15 | 16 | ## Installing 17 | 18 | To get started, install the prerequisites. This requires GDB 14, 19 | because it uses a new feature there that helps with starting new 20 | Python threads in gdb. 21 | 22 | You'll need a Python-enabled gdb, PyGObject, and PyGktSourceView. 23 | (And maybe more -- if you trip across something, let me know.) 24 | 25 | On Fedora I think this suffices: 26 | 27 | ``` 28 | sudo yum install gdb python-devel gtksourceview3 pygobject3 29 | ``` 30 | 31 | Now type `make` to build the needed shared library. 32 | 33 | The simplest way to make the GUI always be available is to then use: 34 | 35 | ``` 36 | make hack-gdbinit 37 | ``` 38 | 39 | This will edit your `~/.gdbinit` to `source` the appropriate file. If 40 | you don't want to do this, you can just source the `gdb-gui.py` file 41 | from gdb at any time. 42 | 43 | ## Using the GUI 44 | 45 | This package adds a new `gui` command and various subcommands to gdb. 46 | It also adds some new `set gui` parameters. 47 | 48 | A simple command to try is `gui source`, which pops up a source 49 | window. The source window will automatically track your progress when 50 | debugging. You can make multiple source windows; they will be reused 51 | in an LRU fashion. You can set the theme, font, and title format of 52 | source windows using the appropriate `set gui` commands. 53 | 54 | ## Hacking 55 | 56 | If you want to hack on this, you will need Glade to edit the UI 57 | elements. For Fedora 18, you'll need a special hack to make the 58 | gtksourceview widget visible to Glade. 59 | -------------------------------------------------------------------------------- /gdb-gui.py.in: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append("HERE") 4 | import gui.commands 5 | -------------------------------------------------------------------------------- /gui/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2013, 2015, 2024 Tom Tromey 2 | 3 | # This program is free software; you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation; either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | import os.path 17 | 18 | import gi 19 | 20 | 21 | gi.require_version("Gtk", "3.0") 22 | gi.require_version("GtkSource", "3.0") 23 | gi.require_version("Notify", "0.7") 24 | 25 | self_dir = os.path.abspath(os.path.dirname(__file__)) 26 | 27 | # Import anything that defines a command or parameter. 28 | import gui.commands 29 | import gui.framecache 30 | 31 | # Hooks in to gdb. 32 | import gui.notify 33 | import gui.params 34 | import gui.toplevel 35 | -------------------------------------------------------------------------------- /gui/adapt.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015, 2016, 2023 Tom Tromey 2 | 3 | # This program is free software; you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation; either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | # Adapt to gdb issues. 17 | 18 | import gui.params 19 | 20 | # The rule for adding a new entry here is that the bug must have some 21 | # notable user-visible effect. 22 | bugs = { 23 | 15620: """Your gdb doesn't have a "new breakpoint" event. 24 | This means that the source windows will not show you where 25 | breakpoints have been set.""", 26 | 13598: """Your gdb doesn't have a "before prompt" event. 27 | This means that various windows won't be able to react to 28 | commands like "up" or "down".""", 29 | 18385: """Your gdb doesn't expose locations on a gdb.Breakpoint. 30 | This can be worked around, but maybe not always reliably. 31 | This means that sometimes breakpoints won't display in source windows.""", 32 | 18620: """Your gdb doesn't have a "breakpoint modified" event. 33 | This means that when a pending breakpoint is resolved, the GUI won't 34 | be able to update to reflect that fact.""", 35 | } 36 | 37 | _warning = """See https://sourceware.org/bugzilla/show_bug.cgi?id=%s 38 | for more information.""" 39 | 40 | _first_report = True 41 | 42 | 43 | def notify_bug(bugno): 44 | if not gui.params.warn_missing.value: 45 | return 46 | if not (bugno in bugs): 47 | return 48 | print("################") 49 | print(bugs[bugno]) 50 | print(_warning % bugno) 51 | print("") 52 | print("You can use 'set gui mention-missing off' to disable this message.") 53 | print("################") 54 | del bugs[bugno] 55 | -------------------------------------------------------------------------------- /gui/bpcache.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Tom Tromey 2 | 3 | # This program is free software; you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation; either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | import gdb 17 | 18 | import gui.adapt 19 | import gui.events 20 | 21 | # This maps from (FILENAME,LINE) to a set of breakpoints referencing 22 | # that file/line. Then we emit events when entries are created or 23 | # destroyed. 24 | _breakpoint_source_map = {} 25 | 26 | 27 | def _breakpoint_created(bp): 28 | if bp.location is None: 29 | return 30 | gui.adapt.notify_bug(18385) 31 | try: 32 | (rest, locs) = gdb.decode_line(bp.location) 33 | except: 34 | return 35 | if rest is not None: 36 | # Let's assume we couldn't reparse for some reason. 37 | return 38 | for sal in locs: 39 | if sal.symtab is None: 40 | continue 41 | entry = (sal.symtab.fullname(), sal.line) 42 | if entry not in _breakpoint_source_map: 43 | _breakpoint_source_map[entry] = set() 44 | if bp.number not in _breakpoint_source_map[entry]: 45 | _breakpoint_source_map[entry].add(bp.number) 46 | gui.events.location_changed.post(entry, True) 47 | else: 48 | _breakpoint_source_map[entry].add(bp.number) 49 | 50 | 51 | def _breakpoint_deleted(bp): 52 | num = bp.number 53 | for entry in _breakpoint_source_map: 54 | if num in _breakpoint_source_map[entry]: 55 | _breakpoint_source_map[entry].discard(bp.number) 56 | if len(_breakpoint_source_map[entry]) == 0: 57 | gui.events.location_changed.post(entry, False) 58 | 59 | 60 | def _breakpoint_modified(bp): 61 | if bp.enabled: 62 | _breakpoint_created(bp) 63 | else: 64 | _breakpoint_deleted(bp) 65 | 66 | 67 | def any_breakpoint_at(filename, lineno): 68 | entry = (filename, lineno) 69 | if entry not in _breakpoint_source_map: 70 | return False 71 | return len(_breakpoint_source_map[entry]) > 0 72 | 73 | 74 | if not hasattr(gdb.events, "breakpoint_created"): 75 | gui.adapt.notify_bug(15620) 76 | else: 77 | gdb.events.breakpoint_created.connect(_breakpoint_created) 78 | gdb.events.breakpoint_deleted.connect(_breakpoint_deleted) 79 | 80 | if not hasattr(gdb.events, "breakpoint_modified"): 81 | gui.adapt.notify_bug(18620) 82 | else: 83 | gdb.events.breakpoint_modified.connect(_breakpoint_modified) 84 | -------------------------------------------------------------------------------- /gui/commands.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2012, 2013, 2015, 2016, 2023 Tom Tromey 2 | 3 | # This program is free software; you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation; either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | import re 17 | 18 | import gdb 19 | 20 | import gui.adapt 21 | import gui.display 22 | import gui.dprintf 23 | import gui.events 24 | import gui.gdbutil 25 | import gui.logwindow 26 | import gui.source 27 | import gui.stack 28 | import gui.startup 29 | import gui.toplevel 30 | 31 | 32 | class GuiCommand(gdb.Command): 33 | def __init__(self): 34 | super(GuiCommand, self).__init__("gui", gdb.COMMAND_SUPPORT, prefix=True) 35 | 36 | 37 | class GuiSourceCommand(gdb.Command): 38 | """Create a new source window. 39 | Usage: gui source 40 | This creates a new source window in the GUI. Any number of source 41 | windows can be created.""" 42 | 43 | def __init__(self): 44 | super(GuiSourceCommand, self).__init__("gui source", gdb.COMMAND_SUPPORT) 45 | 46 | def invoke(self, arg, from_tty): 47 | self.dont_repeat() 48 | gui.source.lru_handler.new_source_window() 49 | 50 | 51 | class GuiStackCommand(gdb.Command): 52 | """Create a new stack window. 53 | Usage: gui stack 54 | This creates a stack window in the GUI if it does not already exist.""" 55 | 56 | def __init__(self): 57 | super(GuiStackCommand, self).__init__("gui stack", gdb.COMMAND_SUPPORT) 58 | 59 | def invoke(self, arg, from_tty): 60 | self.dont_repeat() 61 | # FIXME does it make sense to have more than one? Maybe if 62 | # we have thread-locking. 63 | # FIXME could have arguments to set various stack flags, 64 | # like whether to show child frames, or just show raw, 65 | # or maybe even whatever 'bt' takes? 66 | gui.stack.show_stack() 67 | 68 | 69 | class GuiListCommand(gdb.Command): 70 | """List some source code in a source window. 71 | Usage: gui list LINESPEC 72 | This command uses LINESPEC to show some source code in a source 73 | window. If a source window is already available, the source is 74 | displayed there. Otherwise, a new source window is made. 75 | LINESPEC is a line specification of the form given to 'break'.""" 76 | 77 | def __init__(self): 78 | super(GuiListCommand, self).__init__("gui list", gdb.COMMAND_SUPPORT) 79 | 80 | def invoke(self, arg, from_tty): 81 | self.dont_repeat() 82 | (extra, sals) = gdb.decode_line(arg) 83 | if extra is not None: 84 | raise gdb.GdbError("unrecognized junk at end of command: " + extra) 85 | if sals is None: 86 | raise gdb.GdbError("not found") 87 | if len(sals) > 1: 88 | print("Ambiguous linespec, only showing first result") 89 | sal = sals[0] 90 | if sal.symtab is None or sal.symtab.filename is None: 91 | raise gdb.GdbError("could not find file for symbol") 92 | gui.source.lru_handler.show_source_gdb( 93 | None, sal.symtab, sal.symtab.fullname(), sal.line 94 | ) 95 | 96 | 97 | class GuiShowCommand(gdb.Command): 98 | """Show the source for a symbol in a source window. 99 | Usage: gui show SYMBOL 100 | This command looks for the definition of SYMBOL in the program and 101 | shows its source location in a source window. If a source window is 102 | already available, the source is displayed there. Otherwise, a new 103 | source window is made.""" 104 | 105 | def __init__(self): 106 | super(GuiShowCommand, self).__init__("gui show", gdb.COMMAND_SUPPORT) 107 | 108 | def invoke(self, arg, from_tty): 109 | self.dont_repeat() 110 | try: 111 | (symbol, ignore) = gdb.lookup_symbol(arg) 112 | except gdb.error: 113 | if gui.gdbutil.is_running(): 114 | raise 115 | symbol = gdb.lookup_global_symbol(arg) 116 | if symbol is None: 117 | raise gdb.GdbError("symbol " + arg + " not found") 118 | if symbol.symtab is None or symbol.symtab.filename is None: 119 | raise gdb.GdbError( 120 | "symbol " + arg + " does not seem to have an associated file" 121 | ) 122 | gui.source.lru_handler.show_source_gdb( 123 | None, symbol.symtab, symbol.symtab.fullname(), symbol.line 124 | ) 125 | 126 | 127 | class GuiLogWindowCommand(gdb.Command): 128 | """Create a new log window. 129 | Usage: gui log 130 | This creates a new "log" window in the GUI. A log window is used 131 | to display output from "gui print", "gui printf", "gui output", 132 | and "gui dprintf". 133 | 134 | Multiple log windows can be created and output can be directed to 135 | a given instance using the "@" syntax, like: 136 | 137 | gui print @5 variable""" 138 | 139 | def __init__(self): 140 | super(GuiLogWindowCommand, self).__init__("gui log", gdb.COMMAND_SUPPORT) 141 | 142 | def invoke(self, arg, from_tty): 143 | self.dont_repeat() 144 | window = gui.logwindow.LogWindow() 145 | print("Created log window %d; now the default" % window.number) 146 | 147 | 148 | class GuiPrintBase(gdb.Command): 149 | def __init__(self, command): 150 | super(GuiPrintBase, self).__init__("gui " + command, gdb.COMMAND_SUPPORT) 151 | self.command = command 152 | 153 | # Given ARG, return a pair (WINDOW, NEW_ARG). 154 | def _parse_arg(self, arg, do_default=True): 155 | arg = arg.strip() 156 | match = re.match("@(\\d+)\\s+(.*)$", arg) 157 | if match is not None: 158 | winno = int(match.group(1)) 159 | arg = match.group(2) 160 | window = gui.toplevel.state.get(winno) 161 | if window is None: 162 | raise gdb.GdbError("could not find window %d" % winno) 163 | if not isinstance(window, gui.logwindow.LogWindow): 164 | raise gdb.GdbError("window %d is not a log window" % winno) 165 | elif do_default: 166 | window = gui.logwindow.default_log_window 167 | if window is None: 168 | raise gdb.GdbError("no default log window") 169 | else: 170 | window = None 171 | return (window, arg) 172 | 173 | def invoke(self, arg, from_tty): 174 | (window, arg) = self._parse_arg(arg) 175 | text = gdb.execute(self.command + " " + arg, from_tty, True) 176 | window.append(text) 177 | 178 | 179 | class GuiPrintCommand(GuiPrintBase): 180 | """Print to a gui log window. 181 | Usage: gui print [@N] ARGS 182 | This is a wrapper for the "print" command that redirects its output 183 | to a "gui log" window. If "@N" is given, then output goes to that 184 | window; otherwise, output goes to the most recently created log window.""" 185 | 186 | def __init__(self): 187 | super(GuiPrintCommand, self).__init__("print") 188 | 189 | 190 | class GuiOutputCommand(GuiPrintBase): 191 | """Output to a gui log window. 192 | Usage: gui output [@N] ARGS 193 | This is a wrapper for the "output" command that redirects its output 194 | to a "gui log" window. If "@N" is given, then output goes to that 195 | window; otherwise, output goes to the most recently created log window.""" 196 | 197 | def __init__(self): 198 | super(GuiOutputCommand, self).__init__("output") 199 | 200 | 201 | class GuiPrintfCommand(GuiPrintBase): 202 | """printf to a gui log window. 203 | Usage: gui printf [@N] ARGS 204 | This is a wrapper for the "printf" command that redirects its output 205 | to a "gui log" window. If "@N" is given, then output goes to that 206 | window; otherwise, output goes to the most recently created log window.""" 207 | 208 | def __init__(self): 209 | super(GuiPrintfCommand, self).__init__("printf") 210 | 211 | 212 | class GuiDprintfCommand(GuiPrintBase): 213 | """dprintf to a gui log window. 214 | Usage: gui dprintf [@N] ARGS 215 | This is a wrapper for the "dprintf" command that redirects its output 216 | to a "gui log" window. If "@N" is given, then output goes to that 217 | window; otherwise, output goes to the most recently created log window.""" 218 | 219 | def __init__(self): 220 | super(GuiDprintfCommand, self).__init__("dprintf") 221 | 222 | def invoke(self, arg, from_tty): 223 | (window, arg) = self._parse_arg(arg, False) 224 | (ignore, arg) = gdb.decode_line(arg) 225 | if arg is None: 226 | raise gdb.GdbError("no printf arguments to 'gui dprintf'") 227 | arg = arg.strip() 228 | if not arg.startswith(","): 229 | raise gdb.GdbError("comma expected after linespec") 230 | arg = arg[1:] 231 | spec = arg[0 : -len(arg)] 232 | gui.dprintf.DPrintfBreakpoint(spec, window, arg) 233 | 234 | 235 | class GuiDisplayCommand(gdb.Command): 236 | """Create a new display window. 237 | Usage: gui display [-diff] COMMAND 238 | 239 | A display window runs a gdb command after operations that change the 240 | current frame or that cause the inferior to stop. If "-diff" is 241 | given, then every time the display is updated, changed lines are 242 | highlighted.""" 243 | 244 | def __init__(self): 245 | super(GuiDisplayCommand, self).__init__("gui display", gdb.COMMAND_SUPPORT) 246 | 247 | def invoke(self, arg, from_tty): 248 | self.dont_repeat() 249 | diff = False 250 | if arg.startswith("-diff "): 251 | diff = True 252 | arg = arg[6:] 253 | gui.display.DisplayWindow(arg, diff) 254 | 255 | def complete(self, text, word): 256 | # FIXME, see 257 | # https://sourceware.org/bugzilla/show_bug.cgi?id=13077 258 | return None 259 | 260 | 261 | class InfoWindowsCommand(gdb.Command): 262 | """List all the GUI windows. 263 | Usage: info windows 264 | This lists all the GUI windows. 265 | Note that this should not be confused with "info win", which is 266 | part of the TUI.""" 267 | 268 | def __init__(self): 269 | super(InfoWindowsCommand, self).__init__("info windows", gdb.COMMAND_SUPPORT) 270 | 271 | def invoke(self, arg, from_tty): 272 | self.dont_repeat() 273 | gui.toplevel.state.display() 274 | 275 | 276 | class DeleteWindowsCommand(gdb.Command): 277 | """Delete a GUI window. 278 | Usage: delete window N 279 | Delete GUI window number N. 280 | A window's number appears in its title bar, and can also be 281 | found using "info windows".""" 282 | 283 | def __init__(self): 284 | super(DeleteWindowsCommand, self).__init__("delete window", gdb.COMMAND_SUPPORT) 285 | 286 | def invoke(self, arg, from_tty): 287 | self.dont_repeat() 288 | winno = int(arg) 289 | window = gui.toplevel.state.get(winno) 290 | if window is not None: 291 | window.destroy() 292 | 293 | 294 | GuiCommand() 295 | GuiSourceCommand() 296 | GuiStackCommand() 297 | GuiLogWindowCommand() 298 | GuiPrintCommand() 299 | GuiOutputCommand() 300 | GuiPrintfCommand() 301 | GuiDprintfCommand() 302 | GuiDisplayCommand() 303 | GuiListCommand() 304 | GuiShowCommand() 305 | InfoWindowsCommand() 306 | DeleteWindowsCommand() 307 | 308 | _can_override = False 309 | 310 | 311 | # A temporary test to see if you have a gdb that supports this. 312 | class TestCommand(gdb.Command): 313 | """A temporary test command created for the GUI. 314 | This does nothing, the GUI startup code uses it to see if 315 | your copy of gdb has some command-overriding support.""" 316 | 317 | def __init__(self, set_it): 318 | super(TestCommand, self).__init__("maint gui-test", gdb.COMMAND_DATA) 319 | self.set_it = set_it 320 | 321 | def invoke(self, arg, from_tty): 322 | if self.set_it: 323 | global _can_override 324 | _can_override = True 325 | else: 326 | try: 327 | super(TestCommand, self).invoke(arg, from_tty) 328 | except: 329 | pass 330 | 331 | 332 | TestCommand(True) 333 | TestCommand(False).invoke("", 0) 334 | 335 | # See framecache.py - we prefer the before_prompt event if it exists; 336 | # but otherwise try the overriding approach. Both of these rely on a 337 | # hacked gdb :-( 338 | if _can_override and not hasattr(gdb.events, "before_prompt"): 339 | 340 | class Overrider(gdb.Command): 341 | def __init__(self, name, event): 342 | super(Overrider, self).__init__(name, gdb.COMMAND_DATA) 343 | self.event = event 344 | 345 | def invoke(self, arg, from_tty): 346 | super(Overrider, self).invoke(arg, from_tty) 347 | self.event.post() 348 | 349 | Overrider("up", gui.events.frame_changed) 350 | Overrider("down", gui.events.frame_changed) 351 | Overrider("frame", gui.events.frame_changed) 352 | -------------------------------------------------------------------------------- /gui/display.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2013, 2015, 2023 Tom Tromey 2 | 3 | # This program is free software; you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation; either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | # Log window. 17 | 18 | from difflib import Differ 19 | 20 | import gdb 21 | from gi.repository import Pango 22 | 23 | import gui.events 24 | import gui.startup 25 | import gui.updatewindow 26 | from gui.startup import in_gdb_thread, in_gtk_thread 27 | 28 | # FIXME: TO DO: 29 | # * highlight the changes 30 | 31 | 32 | class DisplayWindow(gui.updatewindow.UpdateWindow): 33 | def __init__(self, command, diff=False): 34 | self.command = command 35 | self.diff = diff 36 | self.last_text = None 37 | super(DisplayWindow, self).__init__("display") 38 | 39 | @in_gdb_thread 40 | def on_event(self): 41 | try: 42 | text = gdb.execute(self.command, to_string=True) 43 | except gdb.error as what: 44 | text = str(what) 45 | gui.startup.send_to_gtk(lambda: self._update(text)) 46 | 47 | @in_gtk_thread 48 | def gtk_initialize(self): 49 | builder = gui.startup.create_builder("logwindow.xml") 50 | builder.connect_signals(self) 51 | 52 | self.window = builder.get_object("logwindow") 53 | self.view = builder.get_object("textview") 54 | self.view.modify_font(Pango.FontDescription("Fixed")) 55 | 56 | self.buffer = builder.get_object("buffer") 57 | 58 | if self.diff: 59 | self.tag = self.buffer.create_tag("new", foreground="red") 60 | 61 | def _update(self, text): 62 | self.buffer.delete(self.buffer.get_start_iter(), self.buffer.get_end_iter()) 63 | if self.diff: 64 | if self.last_text is None: 65 | self.last_text = text.splitlines(1) 66 | # Fall through. 67 | else: 68 | split = text.splitlines(1) 69 | d = Differ() 70 | for line in d.compare(self.last_text, split): 71 | if line[0] == " ": 72 | self.buffer.insert(self.buffer.get_end_iter(), line[2:]) 73 | elif line[0] == "+": 74 | self.buffer.insert_with_tags( 75 | self.buffer.get_end_iter(), line[2:], self.tag 76 | ) 77 | self.buffer.insert(self.buffer.get_end_iter(), "\n") 78 | self.last_text = split 79 | return 80 | self.buffer.insert_at_cursor(text) 81 | -------------------------------------------------------------------------------- /gui/dprintf.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2013 Tom Tromey 2 | 3 | # This program is free software; you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation; either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | # dprintf-like machinery. 17 | 18 | import gdb 19 | 20 | import gui.logwindow 21 | from gui.startup import in_gdb_thread 22 | 23 | 24 | class DPrintfBreakpoint(gdb.Breakpoint): 25 | @in_gdb_thread 26 | def __init__(self, spec, window, arg): 27 | super(DPrintfBreakpoint, self).__init__(spec, gdb.BP_BREAKPOINT) 28 | self.window = window 29 | self.command = "printf " + arg 30 | 31 | @in_gdb_thread 32 | def stop(self): 33 | window = self.window 34 | if window is not None: 35 | if not window.valid(): 36 | gdb.post_event(self.delete) 37 | return False 38 | else: 39 | window = gui.logwindow.default_log_window 40 | if window is None: 41 | return False 42 | 43 | try: 44 | text = gdb.execute(self.command, False, True) 45 | except something: 46 | text = something 47 | window.append(text) 48 | 49 | return False 50 | -------------------------------------------------------------------------------- /gui/events.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2013, 2015, 2023 Tom Tromey 2 | 3 | # This program is free software; you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation; either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | 17 | class _Event(object): 18 | def __init__(self): 19 | self.funcs = [] 20 | 21 | def connect(self, callback): 22 | self.funcs.append(callback) 23 | 24 | def disconnect(self, callback): 25 | self.funcs.remove(callback) 26 | 27 | def post(self, *args, **kwargs): 28 | for fun in self.funcs: 29 | fun(*args, **kwargs) 30 | 31 | 32 | frame_changed = _Event() 33 | location_changed = _Event() 34 | -------------------------------------------------------------------------------- /gui/framecache.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Tom Tromey 2 | 3 | # This program is free software; you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation; either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | import gdb 17 | 18 | import gui.adapt 19 | import gui.events 20 | import gui.invoker 21 | from gui.startup import in_gdb_thread 22 | 23 | _last_selected_frame = None 24 | 25 | 26 | def check_frame(): 27 | global _last_selected_frame 28 | sel = None 29 | try: 30 | sel = gdb.selected_frame() 31 | except: 32 | pass 33 | if _last_selected_frame is not sel: 34 | _last_selected_frame = sel 35 | gui.events.frame_changed.post() 36 | 37 | 38 | # We need this because we rely on the before_prompt hook to notify us 39 | # of frame changes. A dedicated frame change hook would be better. 40 | class FrameCommandInvoker(gui.invoker.Invoker): 41 | @in_gdb_thread 42 | def do_call(self): 43 | gui.invoker.Invoker.do_call(self) 44 | check_frame() 45 | 46 | 47 | # See my gdb branch on github. 48 | if hasattr(gdb.events, "before_prompt"): 49 | gdb.events.before_prompt.connect(check_frame) 50 | else: 51 | gui.adapt.notify_bug(13598) 52 | -------------------------------------------------------------------------------- /gui/gdbutil.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015, 2023 Tom Tromey 2 | 3 | # This program is free software; you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation; either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | # Little gdb utilities. 17 | 18 | import gdb 19 | import gdb.prompt 20 | 21 | from gui.startup import in_gdb_thread 22 | 23 | gui_prompt_substitutions = dict(gdb.prompt.prompt_substitutions) 24 | 25 | _current_window_for_prompt = None 26 | 27 | 28 | def _prompt_window(attr): 29 | if _current_window_for_prompt is None: 30 | return "" 31 | if attr is None: 32 | return "" 33 | if not hasattr(_current_window_for_prompt, attr): 34 | return None 35 | return str(getattr(_current_window_for_prompt, attr)) 36 | 37 | 38 | gui_prompt_substitutions["W"] = _prompt_window 39 | 40 | 41 | # GDB's API should do this... 42 | def substitute_prompt_with_window(prompt, window): 43 | global _current_window_for_prompt 44 | global gui_prompt_substitutions 45 | save = gdb.prompt.prompt_substitutions 46 | _current_window_for_prompt = window 47 | gdb.prompt.prompt_substitutions = gui_prompt_substitutions 48 | try: 49 | result = gdb.prompt.substitute_prompt(prompt) 50 | finally: 51 | gdb.prompt.prompt_substitutions = save 52 | _current_window_for_prompt = None 53 | return result 54 | 55 | 56 | # GDB's API should do this... 57 | def prompt_help_with_window(window): 58 | global _current_window_for_prompt 59 | global gui_prompt_substitutions 60 | save = gdb.prompt.prompt_substitutions 61 | _current_window_for_prompt = window 62 | gdb.prompt.prompt_substitutions = gui_prompt_substitutions 63 | try: 64 | result = gdb.prompt.prompt_help() 65 | finally: 66 | gdb.prompt.prompt_substitutions = save 67 | _current_window_for_prompt = None 68 | return result 69 | 70 | 71 | @in_gdb_thread 72 | def is_running(): 73 | """Return True if the inferior is running.""" 74 | # This seems good enough for now. 75 | # We can deal with scheduler locking and the rest later. 76 | if gdb.selected_thread() and gdb.selected_thread().is_running(): 77 | return True 78 | return False 79 | -------------------------------------------------------------------------------- /gui/icons/README: -------------------------------------------------------------------------------- 1 | Some of these icons were shamelessly taken from nemiver and Gnome. 2 | 3 | Others come from Feather Icons: https://feathericons.com/ 4 | -------------------------------------------------------------------------------- /gui/icons/arrow-down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gui/icons/arrow-right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gui/icons/arrow-up.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gui/icons/breakpoint-disabled-marker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tromey/gdb-gui/41f4d5c3dc1ec7b067a8b0f23e27810c7bcbe25e/gui/icons/breakpoint-disabled-marker.png -------------------------------------------------------------------------------- /gui/icons/breakpoint-marker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tromey/gdb-gui/41f4d5c3dc1ec7b067a8b0f23e27810c7bcbe25e/gui/icons/breakpoint-marker.png -------------------------------------------------------------------------------- /gui/icons/corner-right-down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gui/icons/corner-right-up.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gui/icons/face-raspberry-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | image/svg+xml 9 | 10 | Gnome Symbolic Icon Theme 11 | 12 | 13 | 14 | 15 | 16 | 17 | Gnome Symbolic Icon Theme 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /gui/icons/fast-forward.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gui/icons/line-pointer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tromey/gdb-gui/41f4d5c3dc1ec7b067a8b0f23e27810c7bcbe25e/gui/icons/line-pointer.png -------------------------------------------------------------------------------- /gui/icons/ok.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tromey/gdb-gui/41f4d5c3dc1ec7b067a8b0f23e27810c7bcbe25e/gui/icons/ok.png -------------------------------------------------------------------------------- /gui/icons/ok.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tromey/gdb-gui/41f4d5c3dc1ec7b067a8b0f23e27810c7bcbe25e/gui/icons/ok.xcf -------------------------------------------------------------------------------- /gui/icons/pause.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gui/invoker.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2013 Tom Tromey 2 | 3 | # This program is free software; you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation; either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | import gdb 17 | 18 | from gui.startup import in_gdb_thread 19 | 20 | 21 | class Invoker(object): 22 | """A simple class that can invoke a gdb command. 23 | This is suitable for use as an event handler in Gtk.""" 24 | 25 | def __init__(self, cmd): 26 | self.cmd = cmd 27 | 28 | # This is invoked in the gdb thread to run the command. 29 | @in_gdb_thread 30 | def do_call(self): 31 | gdb.execute(self.cmd, from_tty=True, to_string=True) 32 | 33 | # The object itself is the Gtk event handler -- though really this 34 | # can be run in any thread. 35 | def __call__(self, *args): 36 | gdb.post_event(self.do_call) 37 | -------------------------------------------------------------------------------- /gui/logwindow.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2013, 2015, 2023 Tom Tromey 2 | 3 | # This program is free software; you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation; either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | # Log window. 17 | 18 | import functools 19 | 20 | import gui.startup 21 | import gui.toplevel 22 | from gui.startup import in_gtk_thread 23 | 24 | default_log_window = None 25 | 26 | 27 | class LogWindow(gui.toplevel.Toplevel): 28 | def __init__(self): 29 | global default_log_window 30 | if default_log_window is not None: 31 | default_log_window.default = "" 32 | default_log_window = self 33 | # For the window title. 34 | self.default = " [Default]" 35 | super(LogWindow, self).__init__("log") 36 | 37 | @in_gtk_thread 38 | def gtk_initialize(self): 39 | builder = gui.startup.create_builder("logwindow.xml") 40 | builder.connect_signals(self) 41 | 42 | self.window = builder.get_object("logwindow") 43 | self.view = builder.get_object("textview") 44 | self.view.modify_font(gui.params.font_manager.get_font()) 45 | self.buffer = builder.get_object("buffer") 46 | 47 | # @in_gtk_thread 48 | # def set_font(self, font): 49 | # self.view.modify_font(Pango.FontDescription(font_name)) 50 | 51 | @in_gtk_thread 52 | def deleted(self, *args): 53 | global default_log_window 54 | if default_log_window == self: 55 | default_log_window = None 56 | for window in gui.toplevel.state.windows(): 57 | if isinstance(window, LogWindow): 58 | default_log_window = window 59 | window.default = " [Default]" 60 | window.update_title() 61 | break 62 | 63 | def _append(self, text): 64 | self.buffer.insert_at_cursor(text) 65 | self.view.scroll_mark_onscreen(self.buffer.get_insert()) 66 | 67 | def append(self, text): 68 | gui.startup.send_to_gtk(functools.partial(self._append, text)) 69 | 70 | @in_gtk_thread 71 | def set_font(self, pango_font): 72 | self.view.modify_font(pango_font) 73 | -------------------------------------------------------------------------------- /gui/logwindow.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | False 7 | GDB Log 8 | 440 9 | 250 10 | 11 | 12 | True 13 | True 14 | in 15 | 16 | 17 | True 18 | True 19 | False 20 | False 21 | buffer 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /gui/notify.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015, 2023 Tom Tromey 2 | 3 | # This program is free software; you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation; either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | # Notifications 17 | 18 | import time 19 | 20 | import gdb 21 | from gi.repository import Notify 22 | 23 | import gui.params 24 | import gui.startup 25 | from gui.startup import in_gdb_thread, in_gtk_thread 26 | 27 | _initialized = False 28 | 29 | _last_time = None 30 | 31 | 32 | @in_gtk_thread 33 | def _show_notification(title, content): 34 | global _initialized 35 | if not _initialized: 36 | _initialized = True 37 | Notify.init("gdb") 38 | n = Notify.Notification.new(title, content) 39 | n.show() 40 | 41 | 42 | @in_gdb_thread 43 | def _on_stop(event): 44 | global _last_time 45 | t = _last_time 46 | _last_time = None 47 | 48 | if ( 49 | t is None 50 | or not gui.params.stop_notification.value 51 | or time.process_time() - t < gui.params.stop_notification_seconds.value 52 | ): 53 | return 54 | 55 | if isinstance(event, gdb.ExitedEvent): 56 | title = "gdb - inferior exited" 57 | if hasattr(event, "exit_code"): 58 | content = "inferior exited with code " + str(event.exit_code) 59 | else: 60 | content = "inferior exited, code unavailable" 61 | elif isinstance(event, gdb.BreakpointEvent): 62 | title = "gdb - inferior stopped" 63 | content = "inferior stopped at breakpoint " + str(event.breakpoints[0].number) 64 | elif isinstance(event, gdb.SignalEvent): 65 | title = "gdb - inferior stopped" 66 | content = "inferior stopped with signal: " + event.stop_signal 67 | else: 68 | title = "gdb - inferior stopped" 69 | content = "inferior stopped, reason unknown" 70 | 71 | gui.startup.send_to_gtk(lambda: _show_notification(title, content)) 72 | 73 | 74 | @in_gdb_thread 75 | def _on_cont(event): 76 | global _last_time 77 | _last_time = time.process_time() 78 | 79 | 80 | gdb.events.stop.connect(_on_stop) 81 | gdb.events.cont.connect(_on_cont) 82 | gdb.events.exited.connect(_on_stop) 83 | -------------------------------------------------------------------------------- /gui/params.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015, 2023 Tom Tromey 2 | 3 | # This program is free software; you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation; either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | # Parameters 17 | 18 | import gdb 19 | import gdb.prompt 20 | from gi.repository import GtkSource, Pango 21 | 22 | import gui.startup 23 | import gui.storage 24 | import gui.toplevel 25 | from gui.startup import in_gdb_thread, in_gtk_thread 26 | 27 | 28 | class _SetBase(gdb.Command): 29 | """Generic command for modifying GUI settings.""" 30 | 31 | def __init__(self): 32 | super(_SetBase, self).__init__("set gui", gdb.COMMAND_NONE, prefix=True) 33 | 34 | 35 | class _SetTitleBase(gdb.Command): 36 | """Generic command for modifying GUI window titles.""" 37 | 38 | def __init__(self): 39 | super(_SetTitleBase, self).__init__( 40 | "set gui title", gdb.COMMAND_NONE, prefix=True 41 | ) 42 | 43 | 44 | class _ShowBase(gdb.Command): 45 | """Generic command for showing GUI settings.""" 46 | 47 | def __init__(self): 48 | super(_ShowBase, self).__init__("show gui", gdb.COMMAND_NONE, prefix=True) 49 | 50 | 51 | class _ShowTitleBase(gdb.Command): 52 | """Generic command for showing GUI window titles.""" 53 | 54 | def __init__(self): 55 | super(_ShowTitleBase, self).__init__( 56 | "show gui title", gdb.COMMAND_NONE, prefix=True 57 | ) 58 | 59 | 60 | # Like gdb.Parameter, but has a default and automatically handles 61 | # storage. 62 | class _StoredParameter(gdb.Parameter): 63 | # NAME_FORMAT is like "%s" - NAME is substituted. 64 | # To construct the parameter name, "gui " is prefixed. 65 | def __init__(self, name_format, name, default, c_class, p_kind, *args): 66 | full_name = "gui " + name_format % name 67 | self.storage_name = "-".join((name_format % name).split(" ")) 68 | storage = gui.storage.storage_manager 69 | super(_StoredParameter, self).__init__(full_name, c_class, p_kind, *args) 70 | if p_kind is gdb.PARAM_BOOLEAN: 71 | val = storage.getboolean(self.storage_name) 72 | elif p_kind is gdb.PARAM_STRING or p_kind is gdb.PARAM_ENUM: 73 | val = storage.get(self.storage_name) 74 | elif p_kind is gdb.PARAM_ZINTEGER: 75 | val = storage.getint(self.storage_name) 76 | else: 77 | raise gdb.error("missing case in gdb gui code") 78 | # Don't record the first setting. 79 | self.storage = None 80 | if val is None: 81 | val = default 82 | if val is not None: 83 | self.value = val 84 | # Start saving changes. 85 | self.storage = storage 86 | self.name = name 87 | 88 | @in_gdb_thread 89 | def get_set_string(self): 90 | if self.storage is not None: 91 | self.storage.set(self.storage_name, self.value) 92 | return "" 93 | 94 | 95 | class _Theme(_StoredParameter): 96 | # Silly gdb requirement. 97 | """""" 98 | 99 | set_doc = "Set the source window theme." 100 | show_doc = "Show the source window theme." 101 | 102 | def __init__(self): 103 | self.manager = GtkSource.StyleSchemeManager.get_default() 104 | super(_Theme, self).__init__( 105 | "%s", 106 | "theme", 107 | None, 108 | gdb.COMMAND_NONE, 109 | gdb.PARAM_ENUM, 110 | # Probably the wrong thread. 111 | self.manager.get_scheme_ids(), 112 | ) 113 | 114 | @in_gdb_thread 115 | def set_buffer_manager(self, b): 116 | self.buffer_manager = b 117 | 118 | @in_gtk_thread 119 | def get_scheme(self): 120 | # Sorta racy 121 | return self.manager.get_scheme(self.value) 122 | 123 | @in_gdb_thread 124 | def get_show_string(self, pvalue): 125 | return "The current theme is: " + self.value 126 | 127 | @in_gdb_thread 128 | def get_set_string(self): 129 | super(_Theme, self).get_set_string() 130 | self.buffer_manager.change_theme() 131 | return "" 132 | 133 | 134 | class _Font(_StoredParameter): 135 | # Silly gdb requirement. 136 | """""" 137 | 138 | set_doc = "Set the source window font." 139 | show_doc = "Show the source window font." 140 | 141 | def __init__(self): 142 | self.manager = GtkSource.StyleSchemeManager.get_default() 143 | super(_Font, self).__init__( 144 | "%s", "font", "monospace", gdb.COMMAND_NONE, gdb.PARAM_STRING 145 | ) 146 | 147 | @in_gtk_thread 148 | def get_font(self): 149 | # Sorta racy 150 | return Pango.FontDescription(self.value) 151 | 152 | @in_gdb_thread 153 | def get_show_string(self, pvalue): 154 | return "The current font is: " + self.value 155 | 156 | @in_gdb_thread 157 | def get_set_string(self): 158 | gui.toplevel.state.set_font(self.value) 159 | super(_Font, self).get_set_string() 160 | return "" 161 | 162 | 163 | title_params = {} 164 | 165 | 166 | class _Title(_StoredParameter): 167 | # Silly gdb requirement. 168 | """""" 169 | 170 | def __init__(self, name, default): 171 | title_params[name] = self 172 | self.name = name 173 | self.set_doc = "Set the %s window title format." % self.name 174 | self.show_doc = "Show the %s window title format." % self.name 175 | self.manager = GtkSource.StyleSchemeManager.get_default() 176 | super(_Title, self).__init__( 177 | "title %s", name, default, gdb.COMMAND_NONE, gdb.PARAM_STRING 178 | ) 179 | val = self.storage.get("title-%s" % name) 180 | if val is not None: 181 | self.value = val 182 | else: 183 | self.value = default 184 | 185 | @in_gdb_thread 186 | def get_show_string(self, pvalue): 187 | return "The current title format for the %s is: %s" % (self.name, self.value) 188 | 189 | @in_gdb_thread 190 | def get_set_string(self): 191 | super(_Title, self).get_set_string() 192 | gui.toplevel.state.update_titles() 193 | return "" 194 | 195 | 196 | class _Missing(_StoredParameter): 197 | # Silly gdb requirement. 198 | """""" 199 | 200 | set_doc = "Set whether to mention missing gdb features." 201 | show_doc = "Show whether to mention missing gdb features." 202 | 203 | def __init__(self): 204 | super(_Missing, self).__init__( 205 | "%s", "mention-missing", True, gdb.COMMAND_NONE, gdb.PARAM_BOOLEAN 206 | ) 207 | 208 | @in_gdb_thread 209 | def get_show_string(self, pvalue): 210 | if self.value: 211 | v = "on" 212 | else: 213 | v = "off" 214 | return "Whether to warn about missing gdb features: " + v 215 | 216 | 217 | class _Lines(_StoredParameter): 218 | # Silly gdb requirement. 219 | """""" 220 | 221 | set_doc = "Set whether to display line numbers in the source window." 222 | show_doc = "Show whether to display line numbers in the source window." 223 | 224 | def __init__(self): 225 | super(_Lines, self).__init__( 226 | "%s", "line-numbers", False, gdb.COMMAND_NONE, gdb.PARAM_BOOLEAN 227 | ) 228 | 229 | @in_gdb_thread 230 | def get_show_string(self, pvalue): 231 | return ( 232 | "Whether to display line numbers in the source window is: %s" % self.value 233 | ) 234 | 235 | @in_gdb_thread 236 | def get_set_string(self): 237 | super(_Lines, self).get_set_string() 238 | gui.toplevel.state.set_line_numbers(self.value) 239 | return "" 240 | 241 | 242 | class _Tabs(_StoredParameter): 243 | # Silly gdb requirement. 244 | """""" 245 | 246 | set_doc = "Set width of tabs in the source window." 247 | show_doc = "Show width of tabs in the source window." 248 | 249 | def __init__(self): 250 | super(_Tabs, self).__init__( 251 | "%s", "tab-width", 8, gdb.COMMAND_NONE, gdb.PARAM_ZINTEGER 252 | ) 253 | 254 | @in_gdb_thread 255 | def get_show_string(self, pvalue): 256 | return "The tab width in the source window is: %d" % self.value 257 | 258 | @in_gdb_thread 259 | def get_set_string(self): 260 | super(_Tabs, self).get_set_string() 261 | gui.toplevel.state.set_tab_width(self.value) 262 | return "" 263 | 264 | 265 | class _StopNotification(_StoredParameter): 266 | # Silly gdb requirement. 267 | """""" 268 | 269 | set_doc = "Set whether stop notifications are displayed." 270 | show_doc = "Show whether stop notifications are displayed." 271 | 272 | def __init__(self): 273 | super(_StopNotification, self).__init__( 274 | "%s", "stop-notification", True, gdb.COMMAND_RUNNING, gdb.PARAM_BOOLEAN 275 | ) 276 | 277 | @in_gdb_thread 278 | def get_show_string(self, pvalue): 279 | return "Whether stop notifications are displayed is: %s" % self.value 280 | 281 | 282 | class _StopNotificationSeconds(_StoredParameter): 283 | # Silly gdb requirement. 284 | """""" 285 | 286 | set_doc = "Set stop notification timeout in seconds." 287 | show_doc = "Show stop notification timeout." 288 | 289 | def __init__(self): 290 | super(_StopNotificationSeconds, self).__init__( 291 | "%s", 292 | "stop-notification-seconds", 293 | 120, 294 | gdb.COMMAND_RUNNING, 295 | gdb.PARAM_ZINTEGER, 296 | ) 297 | 298 | @in_gdb_thread 299 | def get_show_string(self, pvalue): 300 | return "Stop notifications are displayed after %d seconds." % self.value 301 | 302 | 303 | _SetBase() 304 | _SetTitleBase() 305 | _ShowBase() 306 | _ShowTitleBase() 307 | source_theme = _Theme() 308 | font_manager = _Font() 309 | stop_notification = _StopNotification() 310 | stop_notification_seconds = _StopNotificationSeconds() 311 | 312 | _Title("source", "\\W{basename} [GDB Source @\\W{number}]") 313 | _Title("display", "\\W{command} [GDB Display @\\W{number}]") 314 | _Title("log", "[GDB Log @\\W{number}]\\W{default}") 315 | _Title("stack", "[GDB Stack @\\W{number}]") 316 | 317 | warn_missing = _Missing() 318 | line_numbers = _Lines() 319 | tab_width = _Tabs() 320 | -------------------------------------------------------------------------------- /gui/source.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2012, 2013, 2015, 2023, 2024 Tom Tromey 2 | 3 | # This program is free software; you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation; either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | # Source view. 17 | 18 | import os.path 19 | 20 | import gdb 21 | from gi.repository import Gdk, GdkPixbuf, GObject, GtkSource 22 | 23 | import gui 24 | import gui.bpcache 25 | import gui.events 26 | import gui.gdbutil 27 | import gui.params 28 | import gui.startup 29 | import gui.toplevel 30 | import gui.updatewindow 31 | from gui.framecache import FrameCommandInvoker 32 | from gui.invoker import Invoker 33 | from gui.startup import in_gdb_thread, in_gtk_thread 34 | 35 | 36 | class BufferManager: 37 | def __init__(self): 38 | self.buffers = {} 39 | self.lang_manager = None 40 | gui.params.source_theme.set_buffer_manager(self) 41 | gui.events.location_changed.connect(self._location_changed) 42 | # FIXME - emit a warning if this isn't available. 43 | if hasattr(gdb.events, "clear_objfiles"): 44 | gdb.events.clear_objfiles.connect(self._clear_objfiles) 45 | self.empty_buffer = None 46 | 47 | def release_buffer(self, buff): 48 | # FIXME: we should be smart about buffer caching. 49 | # Note that BUFF can be the empty buffer. 50 | pass 51 | 52 | @in_gtk_thread 53 | def get_empty_buffer(self): 54 | if self.empty_buffer is None: 55 | self.empty_buffer = GtkSource.Buffer() 56 | self.empty_buffer.set_style_scheme(gui.params.source_theme.get_scheme()) 57 | return self.empty_buffer 58 | 59 | @in_gtk_thread 60 | def _set_marks(self, buffer, line_set): 61 | iter = buffer.get_iter_at_line(0) 62 | while True: 63 | line = iter.get_line() + 1 64 | if line in line_set: 65 | buffer.create_source_mark(None, "executable", iter) 66 | if not iter.forward_line(): 67 | break 68 | 69 | @in_gdb_thread 70 | def _get_lines_update(self, buffer, symtab): 71 | if hasattr(symtab, "linetable"): 72 | line_set = set(symtab.linetable().source_lines()) 73 | gui.startup.send_to_gtk(lambda: self._set_marks(buffer, line_set)) 74 | 75 | @in_gtk_thread 76 | def get_buffer(self, symtab, filename): 77 | if filename in self.buffers: 78 | return self.buffers[filename] 79 | 80 | if not self.lang_manager: 81 | self.lang_manager = GtkSource.LanguageManager.get_default() 82 | 83 | buff = GtkSource.Buffer() 84 | if filename: 85 | buff.set_language(self.lang_manager.guess_language(filename)) 86 | buff.set_style_scheme(gui.params.source_theme.get_scheme()) 87 | buff.begin_not_undoable_action() 88 | try: 89 | contents = open(filename).read() 90 | except: 91 | return None 92 | buff.set_text(contents) 93 | buff.end_not_undoable_action() 94 | buff.set_modified(False) 95 | buff.filename = filename 96 | 97 | if symtab is not None: 98 | gdb.post_event(lambda: self._get_lines_update(buff, symtab)) 99 | 100 | self.buffers[filename] = buff 101 | return buff 102 | 103 | @in_gtk_thread 104 | def _do_change_theme(self): 105 | new_scheme = gui.params.source_theme.get_scheme() 106 | for filename in self.buffers: 107 | self.buffers[filename].set_style_scheme(new_scheme) 108 | if self.empty_buffer is not None: 109 | self.empty_buffer.set_style_scheme(new_scheme) 110 | 111 | @in_gdb_thread 112 | def change_theme(self): 113 | gui.startup.send_to_gtk(self._do_change_theme) 114 | 115 | @in_gtk_thread 116 | def update_breakpoint_location(self, sal, is_set): 117 | if is_set: 118 | category = "breakpoint" 119 | else: 120 | category = "executable" 121 | [fullname, line] = sal 122 | if fullname in self.buffers: 123 | buffer = self.buffers[fullname] 124 | iter = buffer.get_iter_at_line(line - 1) 125 | buffer.remove_source_marks(iter, iter) 126 | buffer.create_source_mark(None, category, iter) 127 | 128 | @in_gdb_thread 129 | def _location_changed(self, loc, is_set): 130 | gui.startup.send_to_gtk(lambda: self.update_breakpoint_location(loc, is_set)) 131 | 132 | @in_gtk_thread 133 | def _gtk_clear_objfiles(self): 134 | empty_buffer = self.get_empty_buffer() 135 | for window in gui.toplevel.state.windows(): 136 | window.clear_source(empty_buffer) 137 | self.buffers = {} 138 | 139 | @in_gdb_thread 140 | def _clear_objfiles(self, ignore): 141 | gui.startup.send_to_gtk(self._gtk_clear_objfiles) 142 | 143 | @in_gtk_thread 144 | def clear_last_pointer(self): 145 | for key in self.buffers: 146 | buff = self.buffers[key] 147 | # This could probably be more efficient. 148 | buff.remove_source_marks( 149 | buff.get_start_iter(), buff.get_end_iter(), "pointer" 150 | ) 151 | 152 | 153 | buffer_manager = BufferManager() 154 | 155 | 156 | # Return (FRAME, SYMTAB, FILE, LINE) for the selected frame, or, if 157 | # there is no frame, for "main". 158 | @in_gdb_thread 159 | def get_current_location(): 160 | try: 161 | frame = gdb.selected_frame() 162 | sal = frame.find_sal() 163 | symtab = sal.symtab 164 | if symtab is not None: 165 | filename = symtab.fullname() 166 | else: 167 | filename = None 168 | lineno = sal.line 169 | except gdb.error: 170 | # FIXME: should use the static location as set by list etc. 171 | # No frame - try 'main'. 172 | try: 173 | frame = None 174 | sym = gdb.lookup_global_symbol("main") 175 | lineno = sym.line 176 | symtab = sym.symtab 177 | filename = symtab.fullname() 178 | except gdb.error: 179 | # Perhaps no symbol file. 180 | return (None, None, None, None) 181 | except AttributeError: 182 | return (None, None, None, None) 183 | return (frame, symtab, filename, lineno) 184 | 185 | 186 | class LRUHandler: 187 | def __init__(self): 188 | self.windows = [] 189 | self.work_location = None 190 | 191 | # What a lame name. 192 | @in_gdb_thread 193 | def show_source_gdb(self, frame, symtab, srcfile, srcline): 194 | if len(self.windows) == 0: 195 | self.work_location = (frame, symtab, srcfile, srcline) 196 | SourceWindow() 197 | gui.startup.send_to_gtk( 198 | lambda: self.show_source(frame, symtab, srcfile, srcline) 199 | ) 200 | 201 | @in_gdb_thread 202 | def new_source_window(self): 203 | self.work_location = get_current_location() 204 | SourceWindow() 205 | 206 | @in_gdb_thread 207 | def on_event(self, *args): 208 | (frame, symtab, filename, lineno) = get_current_location() 209 | if filename is not None: 210 | gui.startup.send_to_gtk( 211 | lambda: self.show_source(frame, symtab, filename, lineno) 212 | ) 213 | 214 | @in_gdb_thread 215 | def _connect_events(self): 216 | gdb.events.stop.connect(self.on_event) 217 | gui.events.frame_changed.connect(self.on_event) 218 | if hasattr(gdb.events, "new_objfile"): 219 | gdb.events.new_objfile.connect(self._new_objfile) 220 | 221 | @in_gdb_thread 222 | def _disconnect_events(self): 223 | gdb.events.stop.disconnect(self.on_event) 224 | gui.events.frame_changed.disconnect(self.on_event) 225 | 226 | @in_gtk_thread 227 | def pick_window(self, frame): 228 | # If a window is showing FRAME, use it. 229 | # Otherwise, if a window has no frame, use that. 230 | # Otherwise, use the first window. 231 | no_frame = None 232 | for w in self.windows: 233 | # Perhaps this is technically not ok. 234 | # We should document thread-safety a bit better. 235 | # Or just fix it up. 236 | if frame == w.frame: 237 | return w 238 | if w.frame is None and no_frame is None: 239 | no_frame = w 240 | if no_frame is not None: 241 | return no_frame 242 | return self.windows[0] 243 | 244 | @in_gtk_thread 245 | def show_source(self, frame, symtab, srcfile, srcline): 246 | w = self.pick_window(frame) 247 | # LRU policy. 248 | self.windows.remove(w) 249 | self.windows.append(w) 250 | w.frame = frame 251 | w.show_source(symtab, srcfile, srcline) 252 | 253 | @in_gtk_thread 254 | def remove(self, window): 255 | self.windows.remove(window) 256 | if len(self.windows) == 0: 257 | gdb.post_event(self._disconnect_events) 258 | 259 | @in_gtk_thread 260 | def add(self, window): 261 | self.windows.insert(0, window) 262 | if len(self.windows) == 1: 263 | gdb.post_event(self._connect_events) 264 | # Show something. 265 | if self.work_location is not None: 266 | (frame, symtab, filename, lineno) = self.work_location 267 | self.work_location = None 268 | gui.startup.send_to_gtk( 269 | lambda: self.show_source(frame, symtab, filename, lineno) 270 | ) 271 | 272 | @in_gdb_thread 273 | def _new_objfile(self, event): 274 | if len(gdb.objfiles()) == 1: 275 | self.on_event() 276 | 277 | 278 | lru_handler = LRUHandler() 279 | 280 | BUTTON_NAMES = ["step", "next", "continue", "finish", "stop", "up", "down"] 281 | 282 | 283 | class SourceWindow(gui.updatewindow.UpdateWindow): 284 | def _get_pixmap(self, filename): 285 | path = os.path.join(gui.self_dir, filename) 286 | return GdkPixbuf.Pixbuf.new_from_file(path) 287 | 288 | def __init__(self): 289 | super(SourceWindow, self).__init__("source") 290 | gdb.events.cont.connect(self._on_cont_event) 291 | # Update the buttons. 292 | self.on_event() 293 | 294 | @in_gtk_thread 295 | def gtk_initialize(self): 296 | self.frame = None 297 | 298 | self.do_step = Invoker("step") 299 | self.do_next = Invoker("next") 300 | self.do_continue = Invoker("continue") 301 | self.do_finish = Invoker("finish") 302 | self.do_stop = Invoker("interrupt") 303 | self.do_up = FrameCommandInvoker("up") 304 | self.do_down = FrameCommandInvoker("down") 305 | 306 | builder = gui.startup.create_builder("sourcewindow.xml") 307 | builder.connect_signals(self) 308 | self.window = builder.get_object("sourcewindow") 309 | self.view = builder.get_object("view") 310 | 311 | # Maybe there is a cleaner way? 312 | self.buttons = {} 313 | for name in BUTTON_NAMES: 314 | self.buttons[name] = builder.get_object(name) 315 | 316 | self.view.modify_font(gui.params.font_manager.get_font()) 317 | self.view.set_show_line_numbers(gui.params.line_numbers.value) 318 | self.view.set_tab_width(gui.params.tab_width.value) 319 | 320 | attrs = GtkSource.MarkAttributes() 321 | attrs.set_pixbuf(self._get_pixmap("icons/ok.png")) 322 | self.view.set_mark_attributes("executable", attrs, 0) 323 | 324 | attrs = GtkSource.MarkAttributes() 325 | attrs.set_pixbuf(self._get_pixmap("icons/breakpoint-marker.png")) 326 | self.view.set_mark_attributes("breakpoint", attrs, 1) 327 | 328 | attrs = GtkSource.MarkAttributes() 329 | attrs.set_pixbuf(self._get_pixmap("icons/line-pointer.png")) 330 | self.view.set_mark_attributes("pointer", attrs, 2) 331 | 332 | self.view.set_buffer(buffer_manager.get_empty_buffer()) 333 | lru_handler.add(self) 334 | 335 | @in_gtk_thread 336 | def _update_buttons(self, running): 337 | for button in BUTTON_NAMES: 338 | if button == "stop": 339 | self.buttons[button].set_sensitive(running) 340 | else: 341 | self.buttons[button].set_sensitive(not running) 342 | 343 | @in_gdb_thread 344 | def on_event(self): 345 | running = gui.gdbutil.is_running() 346 | gui.startup.send_to_gtk(lambda: self._update_buttons(running)) 347 | 348 | @in_gdb_thread 349 | def _on_cont_event(self, event): 350 | self.on_event() 351 | 352 | @in_gdb_thread 353 | def _disconnect_cont_event(self): 354 | gdb.events.cont.disconnect(self._on_cont_event) 355 | 356 | def deleted(self, *args): 357 | lru_handler.remove(self) 358 | gdb.post_event(self._disconnect_cont_event) 359 | super(SourceWindow, self).deleted() 360 | 361 | def line_mark_activated(self, view, textiter, event): 362 | if event.type != Gdk.EventType.BUTTON_PRESS: 363 | return 364 | if event.button.get_button()[1] != 1: 365 | return 366 | filename = self.view.get_buffer().filename 367 | line = textiter.get_line() + 1 368 | if gui.bpcache.any_breakpoint_at(filename, line): 369 | fun = Invoker("clear %s:%d" % (filename, line)) 370 | else: 371 | fun = Invoker("break %s:%d" % (filename, line)) 372 | fun() 373 | 374 | def _do_scroll(self, buff, srcline): 375 | iter = buff.get_iter_at_line(srcline) 376 | buff.create_source_mark(None, "pointer", iter) 377 | buff.place_cursor(iter) 378 | self.view.scroll_mark_onscreen(buff.get_insert()) 379 | return False 380 | 381 | def show_source(self, symtab, srcfile, srcline): 382 | buff = buffer_manager.get_buffer(symtab, srcfile) 383 | if buff is not None: 384 | old_buffer = self.view.get_buffer() 385 | self.view.set_buffer(buff) 386 | self.fullname = srcfile 387 | self.basename = os.path.basename(srcfile) 388 | self.update_title() 389 | buffer_manager.release_buffer(old_buffer) 390 | buffer_manager.clear_last_pointer() 391 | GObject.idle_add(self._do_scroll, buff, srcline - 1) 392 | # self.view.scroll_to_iter(buff.get_iter_at_line(srcline), 0.0) 393 | 394 | @in_gtk_thread 395 | def set_font(self, pango_font): 396 | self.view.modify_font(pango_font) 397 | 398 | @in_gtk_thread 399 | def set_line_numbers(self, want_lines): 400 | self.view.set_show_line_numbers(want_lines) 401 | 402 | @in_gtk_thread 403 | def set_tab_width(self, width): 404 | self.view.set_tab_width(width) 405 | 406 | @in_gtk_thread 407 | def clear_source(self, buffer): 408 | old_buffer = self.view.get_buffer() 409 | self.view.set_buffer(buffer) 410 | buffer_manager.release_buffer(old_buffer) 411 | -------------------------------------------------------------------------------- /gui/sourcewindow.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | True 8 | False 9 | icons/corner-right-up.svg 10 | 11 | 12 | True 13 | False 14 | icons/arrow-right.svg 15 | 16 | 17 | True 18 | False 19 | icons/corner-right-down.svg 20 | 21 | 22 | True 23 | False 24 | icons/arrow-up.svg 25 | 26 | 27 | True 28 | False 29 | icons/arrow-down.svg 30 | 31 | 32 | True 33 | False 34 | icons/pause.svg 35 | 36 | 37 | True 38 | False 39 | icons/fast-forward.svg 40 | 41 | 42 | False 43 | GDB Source 44 | 600 45 | 400 46 | 47 | 48 | 49 | True 50 | False 51 | vertical 52 | 53 | 54 | True 55 | False 56 | 57 | 58 | True 59 | False 60 | Continue execution 61 | Continue 62 | True 63 | play-image 64 | 65 | 66 | 67 | 68 | False 69 | True 70 | 71 | 72 | 73 | 74 | True 75 | False 76 | Execute one line, stepping over function calls 77 | Next 78 | True 79 | next-image 80 | 81 | 82 | 83 | 84 | False 85 | True 86 | 87 | 88 | 89 | 90 | True 91 | False 92 | Execute one line, stepping into function calls 93 | Step 94 | True 95 | step-image 96 | 97 | 98 | 99 | 100 | False 101 | True 102 | 103 | 104 | 105 | 106 | True 107 | False 108 | Interrupt the program 109 | Stop 110 | True 111 | pause-image 112 | 113 | 114 | 115 | 116 | False 117 | True 118 | 119 | 120 | 121 | 122 | True 123 | False 124 | Run until the current function returns 125 | Finish 126 | True 127 | finish-image 128 | 129 | 130 | 131 | 132 | False 133 | True 134 | 135 | 136 | 137 | 138 | True 139 | False 140 | 141 | 142 | True 143 | True 144 | 145 | 146 | 147 | 148 | True 149 | False 150 | Down one frame 151 | Down 152 | True 153 | down-image 154 | 155 | 156 | 157 | 158 | False 159 | True 160 | 161 | 162 | 163 | 164 | True 165 | False 166 | Up one frame 167 | Up 168 | True 169 | up-image 170 | 171 | 172 | 173 | 174 | False 175 | True 176 | 177 | 178 | 179 | 180 | False 181 | True 182 | 0 183 | 184 | 185 | 186 | 187 | True 188 | True 189 | in 190 | 191 | 192 | True 193 | True 194 | GDK_BUTTON_PRESS_MASK | GDK_STRUCTURE_MASK 195 | True 196 | False 197 | 2 198 | 2 199 | False 200 | True 201 | 4 202 | True 203 | True 204 | False 205 | 206 | 207 | 208 | 209 | 210 | True 211 | True 212 | 1 213 | 214 | 215 | 216 | 217 | 218 | 219 | -------------------------------------------------------------------------------- /gui/stack.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Tom Tromey 2 | 3 | # This program is free software; you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation; either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | # Stack view. 17 | 18 | import gdb 19 | import gdb.FrameDecorator 20 | import gdb.FrameIterator 21 | import gdb.frames 22 | from gi.repository import Gtk 23 | 24 | import gui.params 25 | import gui.startup 26 | import gui.updatewindow 27 | from gui.framecache import FrameCommandInvoker 28 | from gui.startup import in_gdb_thread, in_gtk_thread 29 | 30 | 31 | def format_frame(frame): 32 | result = {} 33 | result["name"] = frame.function() 34 | result["filename"] = frame.filename() 35 | result["pc"] = frame.address() 36 | result["line"] = frame.line() 37 | elided = frame.elided() 38 | if elided is not None: 39 | elided = list(map(format_frame, elided)) 40 | result["elided"] = elided 41 | # Not quite what we want... 42 | result["solib"] = gdb.solib_name(frame.address()) 43 | return result 44 | # FIXME args 45 | 46 | 47 | class StackWindow(gui.updatewindow.UpdateWindow): 48 | def __init__(self): 49 | self.raw = False 50 | super(StackWindow, self).__init__("stack") 51 | # Connect events. 52 | # Update buttons. 53 | 54 | @in_gtk_thread 55 | def gtk_initialize(self): 56 | self.do_up = FrameCommandInvoker("up") 57 | self.do_down = FrameCommandInvoker("down") 58 | builder = gui.startup.create_builder("stackwindow.xml") 59 | builder.connect_signals(self) 60 | 61 | self.window = builder.get_object("stackwindow") 62 | self.view = builder.get_object("view") 63 | self.text = Gtk.TextBuffer() 64 | self.view.set_buffer(self.text) 65 | self.view.modify_font(gui.params.font_manager.get_font()) 66 | 67 | @in_gtk_thread 68 | def _update(self, data): 69 | self.text.delete(self.text.get_start_iter(), self.text.get_end_iter()) 70 | frame_no = 1 71 | for frame in data: 72 | self.text.insert_at_cursor("#%d " % frame_no) 73 | frame_no = frame_no + 1 74 | # Goofball API. 75 | if isinstance(frame["name"], str): 76 | self.text.insert_at_cursor(frame["name"]) 77 | else: 78 | # This is lame but we have no access to minimal 79 | # symbols. 80 | self.text.insert_at_cursor("???") 81 | self.text.insert_at_cursor(" ") 82 | # FIXME args 83 | self.text.insert_at_cursor("\n") 84 | if frame["line"] is not None: 85 | self.text.insert_at_cursor( 86 | " at %s:%d\n" % (frame["filename"], frame["line"]) 87 | ) 88 | if frame["solib"] is not None: 89 | self.text.insert_at_cursor(" [%s]\n" % frame["solib"]) 90 | 91 | @in_gdb_thread 92 | def on_event(self): 93 | frame_iter = None 94 | try: 95 | start_frame = gdb.newest_frame() 96 | if not self.raw: 97 | frame_iter = gdb.frames.execute_frame_filters(start_frame, 0, -1) 98 | if frame_iter is None: 99 | frame_iter = map( 100 | gdb.FrameDecorator.FrameDecorator, 101 | gdb.FrameIterator.FrameIterator(start_frame), 102 | ) 103 | data = list(map(format_frame, frame_iter)) 104 | except gdb.error: 105 | data = [] 106 | gui.startup.send_to_gtk(lambda: self._update(data)) 107 | 108 | @in_gtk_thread 109 | def set_font(self, pango_font): 110 | self.view.modify_font(pango_font) 111 | 112 | 113 | def show_stack(): 114 | # for now 115 | StackWindow() 116 | -------------------------------------------------------------------------------- /gui/stackwindow.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | False 7 | 200 8 | 400 9 | 10 | 11 | True 12 | False 13 | vertical 14 | 15 | 16 | True 17 | False 18 | 19 | 20 | True 21 | False 22 | Down one frame 23 | Down 24 | True 25 | go-down 26 | 27 | 28 | 29 | False 30 | True 31 | 32 | 33 | 34 | 35 | True 36 | False 37 | Up one frame 38 | Up 39 | True 40 | go-up 41 | 42 | 43 | 44 | False 45 | True 46 | 47 | 48 | 49 | 50 | False 51 | True 52 | 0 53 | 54 | 55 | 56 | 57 | True 58 | True 59 | in 60 | 61 | 62 | True 63 | True 64 | False 65 | 2 66 | 2 67 | -1 68 | False 69 | 70 | 71 | 72 | 73 | True 74 | True 75 | 1 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /gui/startup.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2012, 2013, 2015, 2023, 2024 Tom Tromey 2 | 3 | # This program is free software; you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation; either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | import os 17 | import os.path 18 | import queue 19 | import threading 20 | 21 | import gdb 22 | import gi 23 | import gui 24 | 25 | from gi.repository import Gdk, GdkPixbuf, GLib, GObject, Gtk, GtkSource 26 | 27 | (read_pipe, write_pipe) = os.pipe() 28 | 29 | _event_queue = queue.Queue() 30 | 31 | 32 | def send_to_gtk(func): 33 | _event_queue.put(func) 34 | # The payload is arbitrary. 35 | os.write(write_pipe, bytes(1)) 36 | 37 | 38 | class _GtkThread(gdb.Thread): 39 | def handle_queue(self, source, condition): 40 | global _event_queue 41 | os.read(source, 1) 42 | func = _event_queue.get() 43 | func() 44 | return True 45 | 46 | def run(self): 47 | global read_pipe 48 | GObject.io_add_watch(read_pipe, GObject.IO_IN, self.handle_queue) 49 | GObject.type_register(GtkSource.View) 50 | Gtk.main() 51 | 52 | 53 | _gdb_thread = threading.current_thread() 54 | _t = None 55 | 56 | 57 | def start_gtk(): 58 | global _t 59 | if _t is None: 60 | GLib.set_application_name("GDB") 61 | GLib.set_prgname("GDB") 62 | Gdk.set_program_class("GDB") 63 | Gtk.Window.set_default_icon_name("GDB") 64 | path = os.path.join(gui.self_dir, "icons/face-raspberry-symbolic.svg") 65 | Gtk.Window.set_default_icon(GdkPixbuf.Pixbuf.new_from_file(path)) 66 | GObject.threads_init() 67 | Gdk.threads_init() 68 | _t = _GtkThread() 69 | _t.setDaemon(True) 70 | _t.start() 71 | 72 | 73 | def create_builder(filename): 74 | builder = Gtk.Builder() 75 | builder.add_from_file(os.path.join(gui.self_dir, filename)) 76 | return builder 77 | 78 | 79 | def in_gdb_thread(func): 80 | def ensure_gdb_thread(*args, **kwargs): 81 | if threading.current_thread() is not _gdb_thread: 82 | raise RuntimeError("must run '%s' in gdb thread" % repr(func)) 83 | return func(*args, **kwargs) 84 | 85 | return ensure_gdb_thread 86 | 87 | 88 | def in_gtk_thread(func): 89 | def ensure_gtk_thread(*args, **kwargs): 90 | if threading.current_thread() is not _t: 91 | raise RuntimeError("must run '%s' in Gtk thread" % repr(func)) 92 | return func(*args, **kwargs) 93 | 94 | return ensure_gtk_thread 95 | -------------------------------------------------------------------------------- /gui/storage.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015, 2023 Tom Tromey 2 | 3 | # This program is free software; you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation; either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | # Storage management. 17 | 18 | import atexit 19 | import configparser 20 | import errno 21 | import os 22 | 23 | from gi.repository import GLib 24 | 25 | 26 | class StorageManager: 27 | def __init__(self): 28 | self.dir = os.path.join(GLib.get_user_config_dir(), "gdb") 29 | self.file = os.path.join(self.dir, "settings") 30 | try: 31 | os.mkdir(self.dir, 0o700) 32 | except OSError as exc: 33 | if exc.errno is not errno.EEXIST: 34 | self.file = None 35 | self.config = configparser.RawConfigParser() 36 | if self.file is not None: 37 | self.config.read(self.file) 38 | if not self.config.has_section("general"): 39 | self.config.add_section("general") 40 | atexit.register(self.write) 41 | 42 | def get(self, name): 43 | if self.config.has_option("general", name): 44 | return self.config.get("general", name) 45 | return None 46 | 47 | def getboolean(self, name): 48 | if self.config.has_option("general", name): 49 | return self.config.getboolean("general", name) 50 | return None 51 | 52 | def getint(self, name): 53 | if self.config.has_option("general", name): 54 | return self.config.getint("general", name) 55 | return None 56 | 57 | def set(self, name, value): 58 | self.config.set("general", name, value) 59 | 60 | def write(self): 61 | with open(self.file, "wt") as save_file: 62 | self.config.write(save_file) 63 | 64 | 65 | storage_manager = StorageManager() 66 | -------------------------------------------------------------------------------- /gui/toplevel.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2013, 2015 Tom Tromey 2 | 3 | # This program is free software; you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation; either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | # Toplevel handling. 17 | 18 | import threading 19 | 20 | import gdb 21 | from gi.repository import Pango 22 | 23 | import gui.gdbutil 24 | import gui.params 25 | import gui.startup 26 | import gui.storage 27 | from gui.startup import in_gdb_thread, in_gtk_thread 28 | 29 | 30 | class _ToplevelState(object): 31 | def __init__(self): 32 | gui.startup.start_gtk() 33 | # This lock must be held when using the other globals here. 34 | self.toplevel_lock = threading.Lock() 35 | self.next_toplevel = 1 36 | self.toplevels = {} 37 | self.byclass = {} 38 | 39 | def add(self, obj, window_type): 40 | with self.toplevel_lock: 41 | obj.number = self.next_toplevel 42 | self.next_toplevel = self.next_toplevel + 1 43 | self.toplevels[obj.number] = obj 44 | # Each window also has a window number specific to its 45 | # type. Compute this here. 46 | if window_type not in self.toplevels: 47 | self.byclass[window_type] = [] 48 | found = None 49 | for num in range(len(self.byclass[window_type])): 50 | if self.byclass[window_type][num] is None: 51 | found = num 52 | break 53 | if found is None: 54 | self.byclass[window_type].append(obj) 55 | found = len(self.byclass[window_type]) 56 | else: 57 | self.byclass[found] = obj 58 | obj.type_number = found 59 | 60 | def remove(self, obj): 61 | with self.toplevel_lock: 62 | del self.toplevels[obj.number] 63 | self.byclass[obj.type_number] = None 64 | 65 | def get(self, winno): 66 | window = None 67 | with self.toplevel_lock: 68 | if winno in self.toplevels: 69 | window = self.toplevels[winno] 70 | return window 71 | 72 | def display(self): 73 | with self.toplevel_lock: 74 | if len(self.toplevels) == 0: 75 | print("No windows") 76 | return 77 | 78 | print(" Num Name") 79 | for winno in range(1, self.next_toplevel): 80 | if winno in self.toplevels: 81 | window = self.toplevels[winno] 82 | print(" %3d %s" % (window.number, window.window.get_title())) 83 | 84 | @in_gtk_thread 85 | def _do_set_font(self, font_name): 86 | pango_font = Pango.FontDescription(font_name) 87 | with self.toplevel_lock: 88 | for num in self.toplevels: 89 | self.toplevels[num].set_font(pango_font) 90 | 91 | @in_gdb_thread 92 | def set_font(self, font_name): 93 | gui.startup.send_to_gtk(lambda: self._do_set_font(font_name)) 94 | 95 | @in_gtk_thread 96 | def _do_update_titles(self): 97 | with self.toplevel_lock: 98 | for num in self.toplevels: 99 | self.toplevels[num].update_title() 100 | 101 | @in_gdb_thread 102 | def update_titles(self): 103 | gui.startup.send_to_gtk(lambda: self._do_update_titles) 104 | 105 | @in_gtk_thread 106 | def _do_set_line_numbers(self, want_lines): 107 | with self.toplevel_lock: 108 | for num in self.toplevels: 109 | self.toplevels[num].set_line_numbers(want_lines) 110 | 111 | @in_gdb_thread 112 | def set_line_numbers(self, want_lines): 113 | gui.startup.send_to_gtk(lambda: self._do_set_line_numbers(want_lines)) 114 | 115 | @in_gtk_thread 116 | def _do_set_tab_width(self, width): 117 | with self.toplevel_lock: 118 | for num in self.toplevels: 119 | self.toplevels[num].set_title(width) 120 | 121 | @in_gdb_thread 122 | def set_tab_width(self, width): 123 | gui.startup.send_to_gtk(lambda: self._do_set_tab_width(width)) 124 | 125 | @in_gtk_thread 126 | def windows(self): 127 | return list(self.toplevels.values()) 128 | 129 | 130 | state = _ToplevelState() 131 | 132 | 133 | class Toplevel(object): 134 | def __init__(self, window_type): 135 | state.add(self, window_type) 136 | # The subclass must set this. 137 | self.window = None 138 | self.window_type = window_type 139 | self.storage_name = window_type + "-" + str(self.type_number) + "-geom" 140 | gui.startup.send_to_gtk(self._do_gtk_initialize) 141 | 142 | @in_gtk_thread 143 | def gtk_initialize(self): 144 | """Subclasses should implement this method to do initialization 145 | in the Gtk thread.""" 146 | pass 147 | 148 | @in_gtk_thread 149 | def _do_gtk_initialize(self): 150 | self.gtk_initialize() 151 | self.window.connect("configure-event", self._on_resize) 152 | geom = gui.storage.storage_manager.get(self.storage_name) 153 | if geom: 154 | self.window.parse_geometry(geom) 155 | self.update_title() 156 | self.window.show() 157 | 158 | @in_gdb_thread 159 | def _save_size(self, geom): 160 | gui.storage.storage_manager.set(self.storage_name, geom) 161 | 162 | @in_gtk_thread 163 | def _on_resize(self, widget, event): 164 | geom = "%dx%d+%d+%d" % (event.width, event.height, event.x, event.y) 165 | gdb.post_event(lambda: self._save_size(geom)) 166 | return False 167 | 168 | def destroy(self): 169 | state.remove(self) 170 | gui.startup.send_to_gtk(self.window.destroy) 171 | self.window = None 172 | 173 | def valid(self): 174 | return self.window is not None 175 | 176 | @in_gtk_thread 177 | def set_font(self, pango_font): 178 | # Subclasses can override this to be notified when the user 179 | # changes the font. 180 | pass 181 | 182 | @in_gtk_thread 183 | def update_title(self): 184 | fmt = gui.params.title_params[self.window_type].value 185 | title = gui.gdbutil.substitute_prompt_with_window(fmt, self) 186 | self.window.set_title(title) 187 | 188 | @in_gtk_thread 189 | def set_line_numbers(self, want_lines): 190 | pass 191 | 192 | @in_gtk_thread 193 | def set_tab_width(self, width): 194 | pass 195 | 196 | @in_gtk_thread 197 | def clear_source(self, buffer): 198 | pass 199 | -------------------------------------------------------------------------------- /gui/updatewindow.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2013, 2015, 2024 Tom Tromey 2 | 3 | # This program is free software; you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation; either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | # Auto-updating window. 17 | 18 | import gdb 19 | 20 | import gui.events 21 | import gui.startup 22 | from gui.startup import in_gdb_thread, in_gtk_thread 23 | from gui.toplevel import Toplevel 24 | 25 | 26 | class UpdateWindow(Toplevel): 27 | """A window that automatically updates in response to gdb changes. 28 | 29 | In particular, starting or stopping the inferior, or switching 30 | frames, causes this window to be eligible for updates.""" 31 | 32 | def __init__(self, window_type): 33 | super(UpdateWindow, self).__init__(window_type) 34 | self._connect_events() 35 | # Display the data now. 36 | self.on_event() 37 | 38 | # FIXME: really ought to be passing in an event here. 39 | @in_gdb_thread 40 | def on_event(self): 41 | """Subclasses should implement this method. 42 | It is called with no arguments when the state changes.""" 43 | pass 44 | 45 | @in_gdb_thread 46 | def _connect_events(self): 47 | gdb.events.stop.connect(self._on_event) 48 | gui.events.frame_changed.connect(self._on_event) 49 | 50 | @in_gdb_thread 51 | def _disconnect_events(self): 52 | gdb.events.stop.disconnect(self._on_event) 53 | gui.events.frame_changed.disconnect(self._on_event) 54 | 55 | @in_gdb_thread 56 | def _on_event(self, *args): 57 | self.on_event() 58 | 59 | @in_gtk_thread 60 | def deleted(self, *args): 61 | gdb.post_event(self._disconnect_events) 62 | self.destroy() 63 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | include = "\\.py(\\.in)?$" 3 | 4 | [tool.pyright] 5 | typeCheckingMode = "strict" 6 | --------------------------------------------------------------------------------