diff --git a/NOTES b/NOTES new file mode 100644 index 0000000..1a6a966 --- /dev/null +++ b/NOTES @@ -0,0 +1,8 @@ +A few notes on gdb improvements that would help the GUI: + +* The dprintf code here required some hacks. + Some kind of I/O redirection capability would be helpful. + Also this would require being able to subclass a dprintf breakpoint. + Alternatively, a hook on a Breakpoint that is called when a + linespec is resolved would work. Right now you can't make a pending + "gui dprintf". diff --git a/gui/commands.py b/gui/commands.py index 148f2a1..b5f43ee 100644 --- a/gui/commands.py +++ b/gui/commands.py @@ -16,6 +16,10 @@ import gdb import gui.startup import gui.source +import gui.logwindow +import gui.toplevel +import gui.dprintf +import re class GuiCommand(gdb.Command): def __init__(self): @@ -37,5 +41,115 @@ class GuiSourceCommand(gdb.Command): gui.startup.start_gtk() gui.startup.send_to_gtk(gui.source.SourceWindow) +class GuiLogWindowCommand(gdb.Command): + """Create a new log window. + Usage: gui log + This creates a new "log" window in the GUI. A log window is used + to display output from "gui print", "gui printf", "gui output", + and "gui dprintf". + + Multiple log windows can be created and output can be directed to + a given instance using the "@" syntax, like: + + gui printf @5 "hello\n" + """ + + def __init__(self): + super(GuiLogWindowCommand, self).__init__('gui log', + gdb.COMMAND_SUPPORT) + + def invoke(self, arg, from_tty): + self.dont_repeat() + gui.startup.start_gtk() + window = gui.logwindow.LogWindow() + print "Created log window %d; now the default" % window.number + +class GuiPrintBase(gdb.Command): + def __init__(self, command): + super(GuiPrintBase, self).__init__('gui ' + command, + gdb.COMMAND_SUPPORT) + self.command = command + + # Given ARG, return a pair (WINDOW, NEW_ARG). + def _parse_arg(self, arg, do_default = True): + arg = arg.strip() + match = re.match('@(\\d+)\\s+(.*)$', arg) + if match is not None: + winno = int(match.group(1)) + arg = match.group(2) + window = gui.toplevel.state.get(winno) + if window is None: + raise gdb.GdbError('could not find window %d' % winno) + if not isinstance(window, gui.logwindow.LogWindow): + raise gdb.GdbError('window %d is not a log window' % winno) + elif do_default: + window = gui.logwindow.default_log_window + if window is None: + raise gdb.GdbError('no default log window') + else: + window = None + return (window, arg) + + def invoke(self, arg, from_tty): + (window, arg) = self._parse_arg(arg) + text = gdb.execute(self.command + ' ' + arg, from_tty, True) + window.append(text) + +class GuiPrintCommand(GuiPrintBase): + def __init__(self): + super(GuiPrintCommand, self).__init__('print') + +class GuiOutputCommand(GuiPrintBase): + def __init__(self): + super(GuiOutputCommand, self).__init__('output') + +class GuiPrintfCommand(GuiPrintBase): + def __init__(self): + super(GuiPrintfCommand, self).__init__('printf') + +class GuiDprintfCommand(GuiPrintBase): + def __init__(self): + super(GuiDprintfCommand, self).__init__('dprintf') + + def invoke(self, arg, from_tty): + (window, arg) = self._parse_arg(arg, False) + orig_arg = arg + (ignore, arg) = gdb.decode_line(arg) + if arg is None: + raise gdb.GdbError("no printf arguments to 'gui dprintf'") + arg = arg.strip() + if not arg.startswith(','): + raise gdb.GdbError("comma expected after linespec") + arg = arg[1:] + spec = arg[0 : -len(arg)] + DPrintfBreakpoint(spec, window, arg) + +class InfoWindowsCommand(gdb.Command): + def __init__(self): + super(InfoWindowsCommand, self).__init__('info windows', + gdb.COMMAND_SUPPORT) + + def invoke(self, arg, from_tty): + self.dont_repeat() + gui.toplevel.state.display() + +class DeleteWindowsCommand(gdb.Command): + def __init__(self): + super(DeleteWindowsCommand, self).__init__('delete window', + gdb.COMMAND_SUPPORT) + + def invoke(self, arg, from_tty): + self.dont_repeat() + winno = int(arg) + window = gui.toplevel.state.get(winno) + if window is not None: + window.destroy() + GuiCommand() GuiSourceCommand() +GuiLogWindowCommand() +GuiPrintCommand() +GuiOutputCommand() +GuiPrintfCommand() +InfoWindowsCommand() +DeleteWindowsCommand() diff --git a/gui/dprintf.py b/gui/dprintf.py new file mode 100644 index 0000000..bee4f49 --- /dev/null +++ b/gui/dprintf.py @@ -0,0 +1,44 @@ +# Copyright (C) 2013 Tom Tromey + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# dprintf-like machinery. + +import gdb +import gui.logwindow + +class DPrintfBreakpoint(gdb.Breakpoint): + def __init__(self, spec, window, arg): + super(DPrintfBreakpoint, self).__init__(spec, gdb.BP_BREAKPOINT) + self.window = window + self.command = 'printf ' + arg + + def stop(self): + window = self.window + if window is not None: + if not window.valid(): + gdb.post_event(self.delete) + return False + else: + window = gui.logwindow.default_log_window + if window is None: + return False + + try: + text = gdb.execute(self.command, False, True) + except something: + text = something + window.append(text) + + return False diff --git a/gui/logwindow.py b/gui/logwindow.py new file mode 100644 index 0000000..3fbba37 --- /dev/null +++ b/gui/logwindow.py @@ -0,0 +1,55 @@ +# Copyright (C) 2013 Tom Tromey + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Log window. + +import gdb +import gui.toplevel +import gui.startup +from gi.repository import Gtk +import os.path +import functools + +default_log_window = None + +class LogWindow(gui.toplevel.Toplevel): + def __init__(self): + super(LogWindow, self).__init__() + global default_log_window + default_log_window = self + gui.startup.send_to_gtk(self._initialize) + + def _initialize(self): + builder = Gtk.Builder() + builder.add_from_file(os.path.join(gui.self_dir, 'logwindow.xml')) + builder.connect_signals(self) + + self.window = builder.get_object('logwindow') + self.view = builder.get_object('textview') + self.buffer = builder.get_object('buffer') + + self.window.set_title('GDB Log @%d' % self.number) + self.window.show() + + def deleted(self, widget, event): + if default_log_window == self: + default_log_window = None + + def _append(self, text): + self.buffer.insert_at_cursor(text) + self.view.scroll_mark_onscreen(self.buffer.get_insert()) + + def append(self, text): + gui.startup.send_to_gtk(functools.partial(self._append, text)) diff --git a/gui/logwindow.xml b/gui/logwindow.xml new file mode 100644 index 0000000..36ef108 --- /dev/null +++ b/gui/logwindow.xml @@ -0,0 +1,28 @@ + + + + + + False + GDB Log + 440 + 250 + + + True + True + in + + + True + True + False + False + buffer + + + + + + + diff --git a/gui/toplevel.py b/gui/toplevel.py index 3731f38..45064f0 100644 --- a/gui/toplevel.py +++ b/gui/toplevel.py @@ -56,38 +56,18 @@ class _ToplevelState(object): print ' %3d %s' % (window.number, window.window.get_title()) -_toplevel_state = _ToplevelState() +state = _ToplevelState() class Toplevel(object): def __init__(self): - _toplevel_state.add(self) + state.add(self) # The subclass must set this. self.window = None def destroy(self): - _toplevel_state.remove(self) + state.remove(self) gui.startup.send_to_gtk(self.window.destroy) + self.window = None -class InfoWindowsCommand(gdb.Command): - def __init__(self): - super(InfoWindowsCommand, self).__init__('info windows', - gdb.COMMAND_SUPPORT) - - def invoke(self, arg, from_tty): - self.dont_repeat() - _toplevel_state.display() - -class DeleteWindowsCommand(gdb.Command): - def __init__(self): - super(DeleteWindowsCommand, self).__init__('delete window', - gdb.COMMAND_SUPPORT) - - def invoke(self, arg, from_tty): - self.dont_repeat() - winno = int(arg) - window = _toplevel_state.get(winno) - if window is not None: - window.destroy() - -InfoWindowsCommand() -DeleteWindowsCommand() + def valid(self): + return self.window is not None