commit 7990cf04158e3d1a12eaca97c3fc61f28d55196e
parent c61153ade0edf457c824284e0c53bf2d3c14f212
Author: Alex Balgavy <a.balgavy@gmail.com>
Date: Sat, 16 Jun 2018 03:05:29 +0200
gdbinit file for a cool gdb interface
Diffstat:
A | gdbinit | | | 1606 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
1 file changed, 1606 insertions(+), 0 deletions(-)
diff --git a/gdbinit b/gdbinit
@@ -0,0 +1,1606 @@
+python
+
+# GDB dashboard - Modular visual interface for GDB in Python.
+#
+# https://github.com/cyrus-and/gdb-dashboard
+
+import ast
+import fcntl
+import os
+import re
+import struct
+import termios
+import traceback
+import math
+
+# Common attributes ------------------------------------------------------------
+
+class R():
+
+ @staticmethod
+ def attributes():
+ return {
+ # miscellaneous
+ 'ansi': {
+ 'doc': 'Control the ANSI output of the dashboard.',
+ 'default': True,
+ 'type': bool
+ },
+ 'syntax_highlighting': {
+ 'doc': """Pygments style to use for syntax highlighting.
+Using an empty string (or a name not in the list) disables this feature.
+The list of all the available styles can be obtained with (from GDB itself):
+
+ python from pygments.styles import get_all_styles as styles
+ python for s in styles(): print(s)
+""",
+ 'default': 'vim',
+ 'type': str
+ },
+ # prompt
+ 'prompt': {
+ 'doc': """Command prompt.
+This value is parsed as a Python format string in which `{status}` is expanded
+with the substitution of either `prompt_running` or `prompt_not_running`
+attributes, according to the target program status. The resulting string must be
+a valid GDB prompt, see the command `python print(gdb.prompt.prompt_help())`""",
+ 'default': '{status}'
+ },
+ 'prompt_running': {
+ 'doc': """`{status}` when the target program is running.
+See the `prompt` attribute. This value is parsed as a Python format string in
+which `{pid}` is expanded with the process identifier of the target program.""",
+ 'default': '\[\e[1;35m\]>>>\[\e[0m\]'
+ },
+ 'prompt_not_running': {
+ 'doc': '`{status}` when the target program is not running.',
+ 'default': '\[\e[1;30m\]>>>\[\e[0m\]'
+ },
+ # divider
+ 'divider_fill_char_primary': {
+ 'doc': 'Filler around the label for primary dividers',
+ 'default': '─'
+ },
+ 'divider_fill_char_secondary': {
+ 'doc': 'Filler around the label for secondary dividers',
+ 'default': '─'
+ },
+ 'divider_fill_style_primary': {
+ 'doc': 'Style for `divider_fill_char_primary`',
+ 'default': '36'
+ },
+ 'divider_fill_style_secondary': {
+ 'doc': 'Style for `divider_fill_char_secondary`',
+ 'default': '1;30'
+ },
+ 'divider_label_style_on_primary': {
+ 'doc': 'Label style for non-empty primary dividers',
+ 'default': '1;33'
+ },
+ 'divider_label_style_on_secondary': {
+ 'doc': 'Label style for non-empty secondary dividers',
+ 'default': '0'
+ },
+ 'divider_label_style_off_primary': {
+ 'doc': 'Label style for empty primary dividers',
+ 'default': '33'
+ },
+ 'divider_label_style_off_secondary': {
+ 'doc': 'Label style for empty secondary dividers',
+ 'default': '1;30'
+ },
+ 'divider_label_skip': {
+ 'doc': 'Gap between the aligning border and the label.',
+ 'default': 3,
+ 'type': int,
+ 'check': check_ge_zero
+ },
+ 'divider_label_margin': {
+ 'doc': 'Number of spaces around the label.',
+ 'default': 1,
+ 'type': int,
+ 'check': check_ge_zero
+ },
+ 'divider_label_align_right': {
+ 'doc': 'Label alignment flag.',
+ 'default': False,
+ 'type': bool
+ },
+ # common styles
+ 'style_selected_1': {
+ 'default': '1;32'
+ },
+ 'style_selected_2': {
+ 'default': '32'
+ },
+ 'style_low': {
+ 'default': '1;30'
+ },
+ 'style_high': {
+ 'default': '1;37'
+ },
+ 'style_error': {
+ 'default': '31'
+ }
+ }
+
+# Common -----------------------------------------------------------------------
+
+def run(command):
+ return gdb.execute(command, to_string=True)
+
+def ansi(string, style):
+ if R.ansi:
+ return '\x1b[{}m{}\x1b[0m'.format(style, string)
+ else:
+ return string
+
+def divider(width, label='', primary=False, active=True):
+ if primary:
+ divider_fill_style = R.divider_fill_style_primary
+ divider_fill_char = R.divider_fill_char_primary
+ divider_label_style_on = R.divider_label_style_on_primary
+ divider_label_style_off = R.divider_label_style_off_primary
+ else:
+ divider_fill_style = R.divider_fill_style_secondary
+ divider_fill_char = R.divider_fill_char_secondary
+ divider_label_style_on = R.divider_label_style_on_secondary
+ divider_label_style_off = R.divider_label_style_off_secondary
+ if label:
+ if active:
+ divider_label_style = divider_label_style_on
+ else:
+ divider_label_style = divider_label_style_off
+ skip = R.divider_label_skip
+ margin = R.divider_label_margin
+ before = ansi(divider_fill_char * skip, divider_fill_style)
+ middle = ansi(label, divider_label_style)
+ after_length = width - len(label) - skip - 2 * margin
+ after = ansi(divider_fill_char * after_length, divider_fill_style)
+ if R.divider_label_align_right:
+ before, after = after, before
+ return ''.join([before, ' ' * margin, middle, ' ' * margin, after])
+ else:
+ return ansi(divider_fill_char * width, divider_fill_style)
+
+def check_gt_zero(x):
+ return x > 0
+
+def check_ge_zero(x):
+ return x >= 0
+
+def to_unsigned(value, size=8):
+ # values from GDB can be used transparently but are not suitable for
+ # being printed as unsigned integers, so a conversion is needed
+ mask = (2 ** (size * 8)) - 1
+ return int(value.cast(gdb.Value(mask).type)) & mask
+
+def to_string(value):
+ # attempt to convert an inferior value to string; OK when (Python 3 ||
+ # simple ASCII); otherwise (Python 2.7 && not ASCII) encode the string as
+ # utf8
+ try:
+ value_string = str(value)
+ except UnicodeEncodeError:
+ value_string = unicode(value).encode('utf8')
+ return value_string
+
+def format_address(address):
+ pointer_size = gdb.parse_and_eval('$pc').type.sizeof
+ return ('0x{{:0{}x}}').format(pointer_size * 2).format(address)
+
+def format_value(value):
+ # format references as referenced values
+ # (TYPE_CODE_RVALUE_REF is not supported by old GDB)
+ if value.type.code in (getattr(gdb, 'TYPE_CODE_REF', None),
+ getattr(gdb, 'TYPE_CODE_RVALUE_REF', None)):
+ try:
+ return to_string(value.referenced_value())
+ except gdb.MemoryError:
+ return to_string(value)
+ else:
+ try:
+ return to_string(value)
+ except gdb.MemoryError as e:
+ return ansi(e, R.style_error)
+
+class Beautifier():
+ def __init__(self, filename, tab_size=4):
+ self.tab_spaces = ' ' * tab_size
+ self.active = False
+ if not R.ansi:
+ return
+ # attempt to set up Pygments
+ try:
+ import pygments.lexers
+ import pygments.formatters
+ formatter_class = pygments.formatters.Terminal256Formatter
+ self.formatter = formatter_class(style=R.syntax_highlighting)
+ self.lexer = pygments.lexers.get_lexer_for_filename(filename)
+ self.active = True
+ except ImportError:
+ # Pygments not available
+ pass
+ except pygments.util.ClassNotFound:
+ # no lexer for this file or invalid style
+ pass
+
+ def process(self, source):
+ # convert tabs anyway
+ source = source.replace('\t', self.tab_spaces)
+ if self.active:
+ import pygments
+ source = pygments.highlight(source, self.lexer, self.formatter)
+ return source.rstrip('\n')
+
+# Dashboard --------------------------------------------------------------------
+
+class Dashboard(gdb.Command):
+ """Redisplay the dashboard."""
+
+ def __init__(self):
+ gdb.Command.__init__(self, 'dashboard',
+ gdb.COMMAND_USER, gdb.COMPLETE_NONE, True)
+ self.output = None # main terminal
+ # setup subcommands
+ Dashboard.ConfigurationCommand(self)
+ Dashboard.OutputCommand(self)
+ Dashboard.EnabledCommand(self)
+ Dashboard.LayoutCommand(self)
+ # setup style commands
+ Dashboard.StyleCommand(self, 'dashboard', R, R.attributes())
+ # disabled by default
+ self.enabled = None
+ self.disable()
+
+ def on_continue(self, _):
+ # try to contain the GDB messages in a specified area unless the
+ # dashboard is printed to a separate file (dashboard -output ...)
+ if self.is_running() and not self.output:
+ width = Dashboard.get_term_width()
+ gdb.write(Dashboard.clear_screen())
+ gdb.write(divider(width, 'Output/messages', True))
+ gdb.write('\n')
+ gdb.flush()
+
+ def on_stop(self, _):
+ if self.is_running():
+ self.render(clear_screen=False)
+
+ def on_exit(self, _):
+ if not self.is_running():
+ return
+ # collect all the outputs
+ outputs = set()
+ outputs.add(self.output)
+ outputs.update(module.output for module in self.modules)
+ outputs.remove(None)
+ # clean the screen and notify to avoid confusion
+ for output in outputs:
+ try:
+ with open(output, 'w') as fs:
+ fs.write(Dashboard.reset_terminal())
+ fs.write(Dashboard.clear_screen())
+ fs.write('--- EXITED ---')
+ except:
+ # skip cleanup for invalid outputs
+ pass
+
+ def enable(self):
+ if self.enabled:
+ return
+ self.enabled = True
+ # setup events
+ gdb.events.cont.connect(self.on_continue)
+ gdb.events.stop.connect(self.on_stop)
+ gdb.events.exited.connect(self.on_exit)
+
+ def disable(self):
+ if not self.enabled:
+ return
+ self.enabled = False
+ # setup events
+ gdb.events.cont.disconnect(self.on_continue)
+ gdb.events.stop.disconnect(self.on_stop)
+ gdb.events.exited.disconnect(self.on_exit)
+
+ def load_modules(self, modules):
+ self.modules = []
+ for module in modules:
+ info = Dashboard.ModuleInfo(self, module)
+ self.modules.append(info)
+
+ def redisplay(self, style_changed=False):
+ # manually redisplay the dashboard
+ if self.is_running() and self.enabled:
+ self.render(True, style_changed)
+
+ def inferior_pid(self):
+ return gdb.selected_inferior().pid
+
+ def is_running(self):
+ return self.inferior_pid() != 0
+
+ def render(self, clear_screen, style_changed=False):
+ # fetch module content and info
+ all_disabled = True
+ display_map = dict()
+ for module in self.modules:
+ # fall back to the global value
+ output = module.output or self.output
+ # add the instance or None if disabled
+ if module.enabled:
+ all_disabled = False
+ instance = module.instance
+ else:
+ instance = None
+ display_map.setdefault(output, []).append(instance)
+ # notify the user if the output is empty, on the main terminal
+ if all_disabled:
+ # write the error message
+ width = Dashboard.get_term_width()
+ gdb.write(divider(width, 'Error', True))
+ gdb.write('\n')
+ if self.modules:
+ gdb.write('No module to display (see `help dashboard`)')
+ else:
+ gdb.write('No module loaded')
+ # write the terminator
+ gdb.write('\n')
+ gdb.write(divider(width, primary=True))
+ gdb.write('\n')
+ gdb.flush()
+ # continue to allow separate terminals to update
+ # process each display info
+ for output, instances in display_map.items():
+ try:
+ fs = None
+ # use GDB stream by default
+ if output:
+ fs = open(output, 'w')
+ fd = fs.fileno()
+ # setup the terminal
+ fs.write(Dashboard.hide_cursor())
+ else:
+ fs = gdb
+ fd = 1 # stdout
+ # get the terminal width (default main terminal if either
+ # the output is not a file)
+ try:
+ width = Dashboard.get_term_width(fd)
+ except:
+ width = Dashboard.get_term_width()
+ # clear the "screen" if requested for the main terminal,
+ # auxiliary terminals are always cleared
+ if fs is not gdb or clear_screen:
+ fs.write(Dashboard.clear_screen())
+ # show message in separate terminals if all the modules are
+ # disabled
+ if output != self.output and not any(instances):
+ fs.write('--- NO MODULE TO DISPLAY ---\n')
+ continue
+ # process all the modules for that output
+ for n, instance in enumerate(instances, 1):
+ # skip disabled modules
+ if not instance:
+ continue
+ # ask the module to generate the content
+ lines = instance.lines(width, style_changed)
+ # create the divider accordingly
+ div = divider(width, instance.label(), True, lines)
+ # write the data
+ fs.write('\n'.join([div] + lines))
+ # write the newline for all but last unless main terminal
+ if n != len(instances) or fs is gdb:
+ fs.write('\n')
+ # write the final newline and the terminator only if it is the
+ # main terminal to allow the prompt to display correctly (unless
+ # there are no modules to display)
+ if fs is gdb and not all_disabled:
+ fs.write(divider(width, primary=True))
+ fs.write('\n')
+ fs.flush()
+ except Exception as e:
+ cause = traceback.format_exc().strip()
+ Dashboard.err('Cannot write the dashboard\n{}'.format(cause))
+ finally:
+ # don't close gdb stream
+ if fs and fs is not gdb:
+ fs.close()
+
+# Utility methods --------------------------------------------------------------
+
+ @staticmethod
+ def start():
+ # initialize the dashboard
+ dashboard = Dashboard()
+ Dashboard.set_custom_prompt(dashboard)
+ # parse Python inits, load modules then parse GDB inits
+ Dashboard.parse_inits(True)
+ modules = Dashboard.get_modules()
+ dashboard.load_modules(modules)
+ Dashboard.parse_inits(False)
+ # GDB overrides
+ run('set pagination off')
+ # enable and display if possible (program running)
+ dashboard.enable()
+ dashboard.redisplay()
+
+ @staticmethod
+ def get_term_width(fd=1): # defaults to the main terminal
+ # first 2 shorts (4 byte) of struct winsize
+ raw = fcntl.ioctl(fd, termios.TIOCGWINSZ, ' ' * 4)
+ height, width = struct.unpack('hh', raw)
+ return int(width)
+
+ @staticmethod
+ def set_custom_prompt(dashboard):
+ def custom_prompt(_):
+ # render thread status indicator
+ if dashboard.is_running():
+ pid = dashboard.inferior_pid()
+ status = R.prompt_running.format(pid=pid)
+ else:
+ status = R.prompt_not_running
+ # build prompt
+ prompt = R.prompt.format(status=status)
+ prompt = gdb.prompt.substitute_prompt(prompt)
+ return prompt + ' ' # force trailing space
+ gdb.prompt_hook = custom_prompt
+
+ @staticmethod
+ def parse_inits(python):
+ for root, dirs, files in os.walk(os.path.expanduser('~/.gdbinit.d/')):
+ dirs.sort()
+ for init in sorted(files):
+ path = os.path.join(root, init)
+ _, ext = os.path.splitext(path)
+ # either load Python files or GDB
+ if python == (ext == '.py'):
+ gdb.execute('source ' + path)
+
+ @staticmethod
+ def get_modules():
+ # scan the scope for modules
+ modules = []
+ for name in globals():
+ obj = globals()[name]
+ try:
+ if issubclass(obj, Dashboard.Module):
+ modules.append(obj)
+ except TypeError:
+ continue
+ # sort modules alphabetically
+ modules.sort(key=lambda x: x.__name__)
+ return modules
+
+ @staticmethod
+ def create_command(name, invoke, doc, is_prefix, complete=None):
+ Class = type('', (gdb.Command,), {'invoke': invoke, '__doc__': doc})
+ Class(name, gdb.COMMAND_USER, complete or gdb.COMPLETE_NONE, is_prefix)
+
+ @staticmethod
+ def err(string):
+ print(ansi(string, R.style_error))
+
+ @staticmethod
+ def complete(word, candidates):
+ return filter(lambda candidate: candidate.startswith(word), candidates)
+
+ @staticmethod
+ def parse_arg(arg):
+ # encode unicode GDB command arguments as utf8 in Python 2.7
+ if type(arg) is not str:
+ arg = arg.encode('utf8')
+ return arg
+
+ @staticmethod
+ def clear_screen():
+ # ANSI: move the cursor to top-left corner and clear the screen
+ return '\x1b[H\x1b[J'
+
+ @staticmethod
+ def hide_cursor():
+ # ANSI: hide cursor
+ return '\x1b[?25l'
+
+ @staticmethod
+ def reset_terminal():
+ # ANSI: reset to initial state
+ return '\x1bc'
+
+# Module descriptor ------------------------------------------------------------
+
+ class ModuleInfo:
+
+ def __init__(self, dashboard, module):
+ self.name = module.__name__.lower() # from class to module name
+ self.enabled = True
+ self.output = None # value from the dashboard by default
+ self.instance = module()
+ self.doc = self.instance.__doc__ or '(no documentation)'
+ self.prefix = 'dashboard {}'.format(self.name)
+ # add GDB commands
+ self.add_main_command(dashboard)
+ self.add_output_command(dashboard)
+ self.add_style_command(dashboard)
+ self.add_subcommands(dashboard)
+
+ def add_main_command(self, dashboard):
+ module = self
+ def invoke(self, arg, from_tty, info=self):
+ arg = Dashboard.parse_arg(arg)
+ if arg == '':
+ info.enabled ^= True
+ if dashboard.is_running():
+ dashboard.redisplay()
+ else:
+ status = 'enabled' if info.enabled else 'disabled'
+ print('{} module {}'.format(module.name, status))
+ else:
+ Dashboard.err('Wrong argument "{}"'.format(arg))
+ doc_brief = 'Configure the {} module.'.format(self.name)
+ doc_extended = 'Toggle the module visibility.'
+ doc = '{}\n{}\n\n{}'.format(doc_brief, doc_extended, self.doc)
+ Dashboard.create_command(self.prefix, invoke, doc, True)
+
+ def add_output_command(self, dashboard):
+ Dashboard.OutputCommand(dashboard, self.prefix, self)
+
+ def add_style_command(self, dashboard):
+ if 'attributes' in dir(self.instance):
+ Dashboard.StyleCommand(dashboard, self.prefix, self.instance,
+ self.instance.attributes())
+
+ def add_subcommands(self, dashboard):
+ if 'commands' in dir(self.instance):
+ for name, command in self.instance.commands().items():
+ self.add_subcommand(dashboard, name, command)
+
+ def add_subcommand(self, dashboard, name, command):
+ action = command['action']
+ doc = command['doc']
+ complete = command.get('complete')
+ def invoke(self, arg, from_tty, info=self):
+ arg = Dashboard.parse_arg(arg)
+ if info.enabled:
+ try:
+ action(arg)
+ except Exception as e:
+ Dashboard.err(e)
+ return
+ # don't catch redisplay errors
+ dashboard.redisplay()
+ else:
+ Dashboard.err('Module disabled')
+ prefix = '{} {}'.format(self.prefix, name)
+ Dashboard.create_command(prefix, invoke, doc, False, complete)
+
+# GDB commands -----------------------------------------------------------------
+
+ def invoke(self, arg, from_tty):
+ arg = Dashboard.parse_arg(arg)
+ # show messages for checks in redisplay
+ if arg != '':
+ Dashboard.err('Wrong argument "{}"'.format(arg))
+ elif not self.is_running():
+ Dashboard.err('Is the target program running?')
+ else:
+ self.redisplay()
+
+ class ConfigurationCommand(gdb.Command):
+ """Dump the dashboard configuration (layout, styles, outputs).
+With an optional argument the configuration will be written to the specified
+file."""
+
+ def __init__(self, dashboard):
+ gdb.Command.__init__(self, 'dashboard -configuration',
+ gdb.COMMAND_USER, gdb.COMPLETE_FILENAME)
+ self.dashboard = dashboard
+
+ def invoke(self, arg, from_tty):
+ arg = Dashboard.parse_arg(arg)
+ if arg:
+ with open(os.path.expanduser(arg), 'w') as fs:
+ fs.write('# auto generated by GDB dashboard\n\n')
+ self.dump(fs)
+ self.dump(gdb)
+
+ def dump(self, fs):
+ # dump layout
+ self.dump_layout(fs)
+ # dump styles
+ self.dump_style(fs, R)
+ for module in self.dashboard.modules:
+ self.dump_style(fs, module.instance, module.prefix)
+ # dump outputs
+ self.dump_output(fs, self.dashboard)
+ for module in self.dashboard.modules:
+ self.dump_output(fs, module, module.prefix)
+
+ def dump_layout(self, fs):
+ layout = ['dashboard -layout']
+ for module in self.dashboard.modules:
+ mark = '' if module.enabled else '!'
+ layout.append('{}{}'.format(mark, module.name))
+ fs.write(' '.join(layout))
+ fs.write('\n')
+
+ def dump_style(self, fs, obj, prefix='dashboard'):
+ attributes = getattr(obj, 'attributes', lambda: dict())()
+ for name, attribute in attributes.items():
+ real_name = attribute.get('name', name)
+ default = attribute.get('default')
+ value = getattr(obj, real_name)
+ if value != default:
+ fs.write('{} -style {} {!r}\n'.format(prefix, name, value))
+
+ def dump_output(self, fs, obj, prefix='dashboard'):
+ output = getattr(obj, 'output')
+ if output:
+ fs.write('{} -output {}\n'.format(prefix, output))
+
+ class OutputCommand(gdb.Command):
+ """Set the output file/TTY for both the dashboard and modules.
+The dashboard/module will be written to the specified file, which will be
+created if it does not exist. If the specified file identifies a terminal then
+its width will be used to format the dashboard, otherwise falls back to the
+width of the main GDB terminal. Without argument the dashboard, the
+output/messages and modules which do not specify the output will be printed on
+standard output (default). Without argument the module will be printed where the
+dashboard will be printed."""
+
+ def __init__(self, dashboard, prefix=None, obj=None):
+ if not prefix:
+ prefix = 'dashboard'
+ if not obj:
+ obj = dashboard
+ prefix = prefix + ' -output'
+ gdb.Command.__init__(self, prefix,
+ gdb.COMMAND_USER, gdb.COMPLETE_FILENAME)
+ self.dashboard = dashboard
+ self.obj = obj # None means the dashboard itself
+
+ def invoke(self, arg, from_tty):
+ arg = Dashboard.parse_arg(arg)
+ # display a message in a separate terminal if released (note that
+ # the check if this is the last module to use the output is not
+ # performed since if that's not the case the message will be
+ # overwritten)
+ if self.obj.output:
+ try:
+ with open(self.obj.output, 'w') as fs:
+ fs.write(Dashboard.clear_screen())
+ fs.write('--- RELEASED ---\n')
+ except:
+ # just do nothing if the file is not writable
+ pass
+ # set or open the output file
+ if arg == '':
+ self.obj.output = None
+ else:
+ self.obj.output = arg
+ # redisplay the dashboard in the new output
+ self.dashboard.redisplay()
+
+ class EnabledCommand(gdb.Command):
+ """Enable or disable the dashboard [on|off].
+The current status is printed if no argument is present."""
+
+ def __init__(self, dashboard):
+ gdb.Command.__init__(self, 'dashboard -enabled', gdb.COMMAND_USER)
+ self.dashboard = dashboard
+
+ def invoke(self, arg, from_tty):
+ arg = Dashboard.parse_arg(arg)
+ if arg == '':
+ status = 'enabled' if self.dashboard.enabled else 'disabled'
+ print('The dashboard is {}'.format(status))
+ elif arg == 'on':
+ self.dashboard.enable()
+ self.dashboard.redisplay()
+ elif arg == 'off':
+ self.dashboard.disable()
+ else:
+ msg = 'Wrong argument "{}"; expecting "on" or "off"'
+ Dashboard.err(msg.format(arg))
+
+ def complete(self, text, word):
+ return Dashboard.complete(word, ['on', 'off'])
+
+ class LayoutCommand(gdb.Command):
+ """Set or show the dashboard layout.
+Accepts a space-separated list of directive. Each directive is in the form
+"[!]<module>". Modules in the list are placed in the dashboard in the same order
+as they appear and those prefixed by "!" are disabled by default. Omitted
+modules are hidden and placed at the bottom in alphabetical order. Without
+arguments the current layout is shown where the first line uses the same form
+expected by the input while the remaining depict the current status of output
+files."""
+
+ def __init__(self, dashboard):
+ gdb.Command.__init__(self, 'dashboard -layout', gdb.COMMAND_USER)
+ self.dashboard = dashboard
+
+ def invoke(self, arg, from_tty):
+ arg = Dashboard.parse_arg(arg)
+ directives = str(arg).split()
+ if directives:
+ self.layout(directives)
+ if from_tty and not self.dashboard.is_running():
+ self.show()
+ else:
+ self.show()
+
+ def show(self):
+ global_str = 'Global'
+ max_name_len = len(global_str)
+ # print directives
+ modules = []
+ for module in self.dashboard.modules:
+ max_name_len = max(max_name_len, len(module.name))
+ mark = '' if module.enabled else '!'
+ modules.append('{}{}'.format(mark, module.name))
+ print(' '.join(modules))
+ # print outputs
+ default = '(default)'
+ fmt = '{{:{}s}}{{}}'.format(max_name_len + 2)
+ print(('\n' + fmt + '\n').format(global_str,
+ self.dashboard.output or default))
+ for module in self.dashboard.modules:
+ style = R.style_high if module.enabled else R.style_low
+ line = fmt.format(module.name, module.output or default)
+ print(ansi(line, style))
+
+ def layout(self, directives):
+ modules = self.dashboard.modules
+ # reset visibility
+ for module in modules:
+ module.enabled = False
+ # move and enable the selected modules on top
+ last = 0
+ n_enabled = 0
+ for directive in directives:
+ # parse next directive
+ enabled = (directive[0] != '!')
+ name = directive[not enabled:]
+ try:
+ # it may actually start from last, but in this way repeated
+ # modules can be handled transparently and without error
+ todo = enumerate(modules[last:], start=last)
+ index = next(i for i, m in todo if name == m.name)
+ modules[index].enabled = enabled
+ modules.insert(last, modules.pop(index))
+ last += 1
+ n_enabled += enabled
+ except StopIteration:
+ def find_module(x):
+ return x.name == name
+ first_part = modules[:last]
+ if len(list(filter(find_module, first_part))) == 0:
+ Dashboard.err('Cannot find module "{}"'.format(name))
+ else:
+ Dashboard.err('Module "{}" already set'.format(name))
+ continue
+ # redisplay the dashboard
+ if n_enabled:
+ self.dashboard.redisplay()
+
+ def complete(self, text, word):
+ all_modules = (m.name for m in self.dashboard.modules)
+ return Dashboard.complete(word, all_modules)
+
+ class StyleCommand(gdb.Command):
+ """Access the stylable attributes.
+Without arguments print all the stylable attributes. Subcommands are used to set
+or print (when the value is omitted) individual attributes."""
+
+ def __init__(self, dashboard, prefix, obj, attributes):
+ self.prefix = prefix + ' -style'
+ gdb.Command.__init__(self, self.prefix,
+ gdb.COMMAND_USER, gdb.COMPLETE_NONE, True)
+ self.dashboard = dashboard
+ self.obj = obj
+ self.attributes = attributes
+ self.add_styles()
+
+ def add_styles(self):
+ this = self
+ for name, attribute in self.attributes.items():
+ # fetch fields
+ attr_name = attribute.get('name', name)
+ attr_type = attribute.get('type', str)
+ attr_check = attribute.get('check', lambda _: True)
+ attr_default = attribute['default']
+ # set the default value (coerced to the type)
+ value = attr_type(attr_default)
+ setattr(self.obj, attr_name, value)
+ # create the command
+ def invoke(self, arg, from_tty, name=name, attr_name=attr_name,
+ attr_type=attr_type, attr_check=attr_check):
+ new_value = Dashboard.parse_arg(arg)
+ if new_value == '':
+ # print the current value
+ value = getattr(this.obj, attr_name)
+ print('{} = {!r}'.format(name, value))
+ else:
+ try:
+ # convert and check the new value
+ parsed = ast.literal_eval(new_value)
+ value = attr_type(parsed)
+ if not attr_check(value):
+ msg = 'Invalid value "{}" for "{}"'
+ raise Exception(msg.format(new_value, name))
+ except Exception as e:
+ Dashboard.err(e)
+ else:
+ # set and redisplay
+ setattr(this.obj, attr_name, value)
+ this.dashboard.redisplay(True)
+ prefix = self.prefix + ' ' + name
+ doc = attribute.get('doc', 'This style is self-documenting')
+ Dashboard.create_command(prefix, invoke, doc, False)
+
+ def invoke(self, arg, from_tty):
+ # an argument here means that the provided attribute is invalid
+ if arg:
+ Dashboard.err('Invalid argument "{}"'.format(arg))
+ return
+ # print all the pairs
+ for name, attribute in self.attributes.items():
+ attr_name = attribute.get('name', name)
+ value = getattr(self.obj, attr_name)
+ print('{} = {!r}'.format(name, value))
+
+# Base module ------------------------------------------------------------------
+
+ # just a tag
+ class Module():
+ pass
+
+# Default modules --------------------------------------------------------------
+
+class Source(Dashboard.Module):
+ """Show the program source code, if available."""
+
+ def __init__(self):
+ self.file_name = None
+ self.source_lines = []
+ self.ts = None
+ self.highlighted = False
+
+ def label(self):
+ return 'Source'
+
+ def lines(self, term_width, style_changed):
+ # skip if the current thread is not stopped
+ if not gdb.selected_thread().is_stopped():
+ return []
+ # try to fetch the current line (skip if no line information)
+ sal = gdb.selected_frame().find_sal()
+ current_line = sal.line
+ if current_line == 0:
+ return []
+ # reload the source file if changed
+ file_name = sal.symtab.fullname()
+ ts = None
+ try:
+ ts = os.path.getmtime(file_name)
+ except:
+ pass # delay error check to open()
+ if (style_changed or
+ file_name != self.file_name or # different file name
+ ts and ts > self.ts): # file modified in the meanwhile
+ self.file_name = file_name
+ self.ts = ts
+ try:
+ highlighter = Beautifier(self.file_name, self.tab_size)
+ self.highlighted = highlighter.active
+ with open(self.file_name) as source_file:
+ source = highlighter.process(source_file.read())
+ self.source_lines = source.split('\n')
+ except Exception as e:
+ msg = 'Cannot display "{}" ({})'.format(self.file_name, e)
+ return [ansi(msg, R.style_error)]
+ # compute the line range
+ start = max(current_line - 1 - self.context, 0)
+ end = min(current_line - 1 + self.context + 1, len(self.source_lines))
+ # return the source code listing
+ out = []
+ number_format = '{{:>{}}}'.format(len(str(end)))
+ for number, line in enumerate(self.source_lines[start:end], start + 1):
+ # properly handle UTF-8 source files
+ line = to_string(line)
+ if int(number) == current_line:
+ # the current line has a different style without ANSI
+ if R.ansi:
+ if self.highlighted:
+ line_format = ansi(number_format,
+ R.style_selected_1) + ' {}'
+ else:
+ line_format = ansi(number_format + ' {}',
+ R.style_selected_1)
+ else:
+ # just show a plain text indicator
+ line_format = number_format + '>{}'
+ else:
+ line_format = ansi(number_format, R.style_low) + ' {}'
+ out.append(line_format.format(number, line.rstrip('\n')))
+ return out
+
+ def attributes(self):
+ return {
+ 'context': {
+ 'doc': 'Number of context lines.',
+ 'default': 5,
+ 'type': int,
+ 'check': check_ge_zero
+ },
+ 'tab-size': {
+ 'doc': 'Number of spaces used to display the tab character.',
+ 'default': 4,
+ 'name': 'tab_size',
+ 'type': int,
+ 'check': check_gt_zero
+ }
+ }
+
+class Assembly(Dashboard.Module):
+ """Show the disassembled code surrounding the program counter. The
+instructions constituting the current statement are marked, if available."""
+
+ def label(self):
+ return 'Assembly'
+
+ def lines(self, term_width, style_changed):
+ # skip if the current thread is not stopped
+ if not gdb.selected_thread().is_stopped():
+ return []
+ line_info = None
+ frame = gdb.selected_frame() # PC is here
+ disassemble = frame.architecture().disassemble
+ try:
+ # try to fetch the function boundaries using the disassemble command
+ output = run('disassemble').split('\n')
+ start = int(re.split('[ :]', output[1][3:], 1)[0], 16)
+ end = int(re.split('[ :]', output[-3][3:], 1)[0], 16)
+ asm = disassemble(start, end_pc=end)
+ # find the location of the PC
+ pc_index = next(index for index, instr in enumerate(asm)
+ if instr['addr'] == frame.pc())
+ start = max(pc_index - self.context, 0)
+ end = pc_index + self.context + 1
+ asm = asm[start:end]
+ # if there are line information then use it, it may be that
+ # line_info is not None but line_info.last is None
+ line_info = gdb.find_pc_line(frame.pc())
+ line_info = line_info if line_info.last else None
+ except (gdb.error, StopIteration):
+ # if it is not possible (stripped binary or the PC is not present in
+ # the output of `disassemble` as per issue #31) start from PC and
+ # end after twice the context
+ try:
+ asm = disassemble(frame.pc(), count=2 * self.context + 1)
+ except gdb.error as e:
+ msg = '{}'.format(e)
+ return [ansi(msg, R.style_error)]
+ # fetch function start if available
+ func_start = None
+ if self.show_function and frame.name():
+ try:
+ # it may happen that the frame name is the whole function
+ # declaration, instead of just the name, e.g., 'getkey()', so it
+ # would be treated as a function call by 'gdb.parse_and_eval',
+ # hence the trim, see #87 and #88
+ value = gdb.parse_and_eval(frame.name().split('(')[0]).address
+ func_start = to_unsigned(value)
+ except gdb.error:
+ pass # e.g., @plt
+ # fetch the assembly flavor and the extension used by Pygments
+ try:
+ flavor = gdb.parameter('disassembly-flavor')
+ except:
+ flavor = None # not always defined (see #36)
+ filename = {
+ 'att': '.s',
+ 'intel': '.asm'
+ }.get(flavor, '.s')
+ # prepare the highlighter
+ highlighter = Beautifier(filename)
+ # compute the maximum offset size
+ if func_start:
+ max_offset = max(len(str(abs(asm[0]['addr'] - func_start))),
+ len(str(abs(asm[-1]['addr'] - func_start))))
+ # return the machine code
+ max_length = max(instr['length'] for instr in asm)
+ inferior = gdb.selected_inferior()
+ out = []
+ for index, instr in enumerate(asm):
+ addr = instr['addr']
+ length = instr['length']
+ text = instr['asm']
+ addr_str = format_address(addr)
+ if self.show_opcodes:
+ # fetch and format opcode
+ region = inferior.read_memory(addr, length)
+ opcodes = (' '.join('{:02x}'.format(ord(byte))
+ for byte in region))
+ opcodes += (max_length - len(region)) * 3 * ' ' + ' '
+ else:
+ opcodes = ''
+ # compute the offset if available
+ if self.show_function:
+ if func_start:
+ offset = '{:+d}'.format(addr - func_start)
+ offset = offset.ljust(max_offset + 1) # sign
+ func_info = '{}{}'.format(frame.name(), offset)
+ else:
+ func_info = '?'
+ else:
+ func_info = ''
+ format_string = '{}{}{}{}{}'
+ indicator = ' '
+ text = ' ' + highlighter.process(text)
+ if addr == frame.pc():
+ if not R.ansi:
+ indicator = '>'
+ addr_str = ansi(addr_str, R.style_selected_1)
+ indicator = ansi(indicator, R.style_selected_1)
+ opcodes = ansi(opcodes, R.style_selected_1)
+ func_info = ansi(func_info, R.style_selected_1)
+ if not highlighter.active:
+ text = ansi(text, R.style_selected_1)
+ elif line_info and line_info.pc <= addr < line_info.last:
+ if not R.ansi:
+ indicator = ':'
+ addr_str = ansi(addr_str, R.style_selected_2)
+ indicator = ansi(indicator, R.style_selected_2)
+ opcodes = ansi(opcodes, R.style_selected_2)
+ func_info = ansi(func_info, R.style_selected_2)
+ if not highlighter.active:
+ text = ansi(text, R.style_selected_2)
+ else:
+ addr_str = ansi(addr_str, R.style_low)
+ func_info = ansi(func_info, R.style_low)
+ out.append(format_string.format(addr_str, indicator,
+ opcodes, func_info, text))
+ return out
+
+ def attributes(self):
+ return {
+ 'context': {
+ 'doc': 'Number of context instructions.',
+ 'default': 3,
+ 'type': int,
+ 'check': check_ge_zero
+ },
+ 'opcodes': {
+ 'doc': 'Opcodes visibility flag.',
+ 'default': False,
+ 'name': 'show_opcodes',
+ 'type': bool
+ },
+ 'function': {
+ 'doc': 'Function information visibility flag.',
+ 'default': True,
+ 'name': 'show_function',
+ 'type': bool
+ }
+ }
+
+class Stack(Dashboard.Module):
+ """Show the current stack trace including the function name and the file
+location, if available. Optionally list the frame arguments and locals too."""
+
+ def label(self):
+ return 'Stack'
+
+ def lines(self, term_width, style_changed):
+ # skip if the current thread is not stopped
+ if not gdb.selected_thread().is_stopped():
+ return []
+ # find the selected frame (i.e., the first to display)
+ selected_index = 0
+ frame = gdb.newest_frame()
+ while frame:
+ if frame == gdb.selected_frame():
+ break
+ frame = frame.older()
+ selected_index += 1
+ # format up to "limit" frames
+ frames = []
+ number = selected_index
+ more = False
+ while frame:
+ # the first is the selected one
+ selected = (len(frames) == 0)
+ # fetch frame info
+ style = R.style_selected_1 if selected else R.style_selected_2
+ frame_id = ansi(str(number), style)
+ info = Stack.get_pc_line(frame, style)
+ frame_lines = []
+ frame_lines.append('[{}] {}'.format(frame_id, info))
+ # fetch frame arguments and locals
+ decorator = gdb.FrameDecorator.FrameDecorator(frame)
+ separator = ansi(', ', R.style_low)
+ strip_newlines = re.compile(r'$\s*', re.MULTILINE)
+ if self.show_arguments:
+ def prefix(line):
+ return Stack.format_line('arg', line)
+ frame_args = decorator.frame_args()
+ args_lines = Stack.fetch_frame_info(frame, frame_args)
+ if args_lines:
+ if self.compact:
+ args_line = separator.join(args_lines)
+ args_line = strip_newlines.sub('', args_line)
+ single_line = prefix(args_line)
+ frame_lines.append(single_line)
+ else:
+ frame_lines.extend(map(prefix, args_lines))
+ else:
+ frame_lines.append(ansi('(no arguments)', R.style_low))
+ if self.show_locals:
+ def prefix(line):
+ return Stack.format_line('loc', line)
+ frame_locals = decorator.frame_locals()
+ locals_lines = Stack.fetch_frame_info(frame, frame_locals)
+ if locals_lines:
+ if self.compact:
+ locals_line = separator.join(locals_lines)
+ locals_line = strip_newlines.sub('', locals_line)
+ single_line = prefix(locals_line)
+ frame_lines.append(single_line)
+ else:
+ frame_lines.extend(map(prefix, locals_lines))
+ else:
+ frame_lines.append(ansi('(no locals)', R.style_low))
+ # add frame
+ frames.append(frame_lines)
+ # next
+ frame = frame.older()
+ number += 1
+ # check finished according to the limit
+ if self.limit and len(frames) == self.limit:
+ # more frames to show but limited
+ if frame:
+ more = True
+ break
+ # format the output
+ lines = []
+ for frame_lines in frames:
+ lines.extend(frame_lines)
+ # add the placeholder
+ if more:
+ lines.append('[{}]'.format(ansi('+', R.style_selected_2)))
+ return lines
+
+ @staticmethod
+ def format_line(prefix, line):
+ prefix = ansi(prefix, R.style_low)
+ return '{} {}'.format(prefix, line)
+
+ @staticmethod
+ def fetch_frame_info(frame, data):
+ lines = []
+ for elem in data or []:
+ name = elem.sym
+ equal = ansi('=', R.style_low)
+ value = format_value(elem.sym.value(frame))
+ lines.append('{} {} {}'.format(name, equal, value))
+ return lines
+
+ @staticmethod
+ def get_pc_line(frame, style):
+ frame_pc = ansi(format_address(frame.pc()), style)
+ info = 'from {}'.format(frame_pc)
+ if frame.name():
+ frame_name = ansi(frame.name(), style)
+ try:
+ # try to compute the offset relative to the current function (it
+ # may happen that the frame name is the whole function
+ # declaration, instead of just the name, e.g., 'getkey()', so it
+ # would be treated as a function call by 'gdb.parse_and_eval',
+ # hence the trim, see #87 and #88)
+ value = gdb.parse_and_eval(frame.name().split('(')[0]).address
+ # it can be None even if it is part of the "stack" (C++)
+ if value:
+ func_start = to_unsigned(value)
+ offset = frame.pc() - func_start
+ frame_name += '+' + ansi(str(offset), style)
+ except gdb.error:
+ pass # e.g., @plt
+ info += ' in {}'.format(frame_name)
+ sal = frame.find_sal()
+ if sal.symtab:
+ file_name = ansi(sal.symtab.filename, style)
+ file_line = ansi(str(sal.line), style)
+ info += ' at {}:{}'.format(file_name, file_line)
+ return info
+
+ def attributes(self):
+ return {
+ 'limit': {
+ 'doc': 'Maximum number of displayed frames (0 means no limit).',
+ 'default': 2,
+ 'type': int,
+ 'check': check_ge_zero
+ },
+ 'arguments': {
+ 'doc': 'Frame arguments visibility flag.',
+ 'default': True,
+ 'name': 'show_arguments',
+ 'type': bool
+ },
+ 'locals': {
+ 'doc': 'Frame locals visibility flag.',
+ 'default': False,
+ 'name': 'show_locals',
+ 'type': bool
+ },
+ 'compact': {
+ 'doc': 'Single-line display flag.',
+ 'default': False,
+ 'type': bool
+ }
+ }
+
+class History(Dashboard.Module):
+ """List the last entries of the value history."""
+
+ def label(self):
+ return 'History'
+
+ def lines(self, term_width, style_changed):
+ out = []
+ # fetch last entries
+ for i in range(-self.limit + 1, 1):
+ try:
+ value = format_value(gdb.history(i))
+ value_id = ansi('$${}', R.style_low).format(abs(i))
+ line = '{} = {}'.format(value_id, value)
+ out.append(line)
+ except gdb.error:
+ continue
+ return out
+
+ def attributes(self):
+ return {
+ 'limit': {
+ 'doc': 'Maximum number of values to show.',
+ 'default': 3,
+ 'type': int,
+ 'check': check_gt_zero
+ }
+ }
+
+class Memory(Dashboard.Module):
+ """Allow to inspect memory regions."""
+
+ @staticmethod
+ def format_byte(byte):
+ # `type(byte) is bytes` in Python 3
+ if byte.isspace():
+ return ' '
+ elif 0x20 < ord(byte) < 0x7e:
+ return chr(ord(byte))
+ else:
+ return '.'
+
+ @staticmethod
+ def parse_as_address(expression):
+ value = gdb.parse_and_eval(expression)
+ return to_unsigned(value)
+
+ def __init__(self):
+ self.row_length = 16
+ self.table = {}
+
+ def format_memory(self, start, memory):
+ out = []
+ for i in range(0, len(memory), self.row_length):
+ region = memory[i:i + self.row_length]
+ pad = self.row_length - len(region)
+ address = format_address(start + i)
+ hexa = (' '.join('{:02x}'.format(ord(byte)) for byte in region))
+ text = (''.join(Memory.format_byte(byte) for byte in region))
+ out.append('{} {}{} {}{}'.format(ansi(address, R.style_low),
+ hexa,
+ ansi(pad * ' --', R.style_low),
+ ansi(text, R.style_high),
+ ansi(pad * '.', R.style_low)))
+ return out
+
+ def label(self):
+ return 'Memory'
+
+ def lines(self, term_width, style_changed):
+ out = []
+ inferior = gdb.selected_inferior()
+ for address, length in sorted(self.table.items()):
+ try:
+ memory = inferior.read_memory(address, length)
+ out.extend(self.format_memory(address, memory))
+ except gdb.error:
+ msg = 'Cannot access {} bytes starting at {}'
+ msg = msg.format(length, format_address(address))
+ out.append(ansi(msg, R.style_error))
+ out.append(divider(term_width))
+ # drop last divider
+ if out:
+ del out[-1]
+ return out
+
+ def watch(self, arg):
+ if arg:
+ address, _, length = arg.partition(' ')
+ address = Memory.parse_as_address(address)
+ if length:
+ length = Memory.parse_as_address(length)
+ else:
+ length = self.row_length
+ self.table[address] = length
+ else:
+ raise Exception('Specify an address')
+
+ def unwatch(self, arg):
+ if arg:
+ try:
+ del self.table[Memory.parse_as_address(arg)]
+ except KeyError:
+ raise Exception('Memory region not watched')
+ else:
+ raise Exception('Specify an address')
+
+ def clear(self, arg):
+ self.table.clear()
+
+ def commands(self):
+ return {
+ 'watch': {
+ 'action': self.watch,
+ 'doc': 'Watch a memory region by address and length.\n'
+ 'The length defaults to 16 byte.',
+ 'complete': gdb.COMPLETE_EXPRESSION
+ },
+ 'unwatch': {
+ 'action': self.unwatch,
+ 'doc': 'Stop watching a memory region by address.',
+ 'complete': gdb.COMPLETE_EXPRESSION
+ },
+ 'clear': {
+ 'action': self.clear,
+ 'doc': 'Clear all the watched regions.'
+ }
+ }
+
+class Registers(Dashboard.Module):
+ """Show the CPU registers and their values."""
+
+ def __init__(self):
+ self.table = {}
+
+ def label(self):
+ return 'Registers'
+
+ def lines(self, term_width, style_changed):
+ # skip if the current thread is not stopped
+ if not gdb.selected_thread().is_stopped():
+ return []
+ # fetch registers status
+ registers = []
+ for reg_info in run('info registers').strip().split('\n'):
+ # fetch register and update the table
+ name = reg_info.split(None, 1)[0]
+ # Exclude registers with a dot '.' or parse_and_eval() will fail
+ if '.' in name:
+ continue
+ value = gdb.parse_and_eval('${}'.format(name))
+ string_value = Registers.format_value(value)
+ changed = self.table and (self.table.get(name, '') != string_value)
+ self.table[name] = string_value
+ registers.append((name, string_value, changed))
+ # split registers in rows and columns, each column is composed of name,
+ # space, value and another trailing space which is skipped in the last
+ # column (hence term_width + 1)
+ max_name = max(len(name) for name, _, _ in registers)
+ max_value = max(len(value) for _, value, _ in registers)
+ max_width = max_name + max_value + 2
+ per_line = int((term_width + 1) / max_width) or 1
+ # redistribute extra space among columns
+ extra = int((term_width + 1 - max_width * per_line) / per_line)
+ if per_line == 1:
+ # center when there is only one column
+ max_name += int(extra / 2)
+ max_value += int(extra / 2)
+ else:
+ max_value += extra
+ # format registers info
+ partial = []
+ for name, value, changed in registers:
+ styled_name = ansi(name.rjust(max_name), R.style_low)
+ value_style = R.style_selected_1 if changed else ''
+ styled_value = ansi(value.ljust(max_value), value_style)
+ partial.append(styled_name + ' ' + styled_value)
+ out = []
+ if self.column_major:
+ num_lines = int(math.ceil(float(len(partial)) / per_line))
+ for i in range(num_lines):
+ line = ' '.join(partial[i:len(partial):num_lines]).rstrip()
+ real_n_col = math.ceil(float(len(partial)) / num_lines)
+ line = ' ' * int((per_line - real_n_col) * max_width / 2) + line
+ out.append(line)
+ else:
+ for i in range(0, len(partial), per_line):
+ out.append(' '.join(partial[i:i + per_line]).rstrip())
+ return out
+
+ def attributes(self):
+ return {
+ 'column-major': {
+ 'doc': 'Whether to show registers in columns instead of rows.',
+ 'default': False,
+ 'name': 'column_major',
+ 'type': bool
+ }
+ }
+
+ @staticmethod
+ def format_value(value):
+ try:
+ if value.type.code in [gdb.TYPE_CODE_INT, gdb.TYPE_CODE_PTR]:
+ int_value = to_unsigned(value, value.type.sizeof)
+ value_format = '0x{{:0{}x}}'.format(2 * value.type.sizeof)
+ return value_format.format(int_value)
+ except (gdb.error, ValueError):
+ # convert to unsigned but preserve code and flags information
+ pass
+ return str(value)
+
+class Threads(Dashboard.Module):
+ """List the currently available threads."""
+
+ def label(self):
+ return 'Threads'
+
+ def lines(self, term_width, style_changed):
+ out = []
+ selected_thread = gdb.selected_thread()
+ # do not restore the selected frame if the thread is not stopped
+ restore_frame = gdb.selected_thread().is_stopped()
+ if restore_frame:
+ selected_frame = gdb.selected_frame()
+ for thread in gdb.Inferior.threads(gdb.selected_inferior()):
+ # skip running threads if requested
+ if self.skip_running and thread.is_running():
+ continue
+ is_selected = (thread.ptid == selected_thread.ptid)
+ style = R.style_selected_1 if is_selected else R.style_selected_2
+ number = ansi(str(thread.num), style)
+ tid = ansi(str(thread.ptid[1] or thread.ptid[2]), style)
+ info = '[{}] id {}'.format(number, tid)
+ if thread.name:
+ info += ' name {}'.format(ansi(thread.name, style))
+ # switch thread to fetch info (unless is running in non-stop mode)
+ try:
+ thread.switch()
+ frame = gdb.newest_frame()
+ info += ' ' + Stack.get_pc_line(frame, style)
+ except gdb.error:
+ info += ' (running)'
+ out.append(info)
+ # restore thread and frame
+ selected_thread.switch()
+ if restore_frame:
+ selected_frame.select()
+ return out
+
+ def attributes(self):
+ return {
+ 'skip-running': {
+ 'doc': 'Skip running threads.',
+ 'default': False,
+ 'name': 'skip_running',
+ 'type': bool
+ }
+ }
+
+class Expressions(Dashboard.Module):
+ """Watch user expressions."""
+
+ def __init__(self):
+ self.number = 1
+ self.table = {}
+
+ def label(self):
+ return 'Expressions'
+
+ def lines(self, term_width, style_changed):
+ out = []
+ for number, expression in sorted(self.table.items()):
+ try:
+ value = format_value(gdb.parse_and_eval(expression))
+ except gdb.error as e:
+ value = ansi(e, R.style_error)
+ number = ansi(number, R.style_selected_2)
+ expression = ansi(expression, R.style_low)
+ out.append('[{}] {} = {}'.format(number, expression, value))
+ return out
+
+ def watch(self, arg):
+ if arg:
+ self.table[self.number] = arg
+ self.number += 1
+ else:
+ raise Exception('Specify an expression')
+
+ def unwatch(self, arg):
+ if arg:
+ try:
+ del self.table[int(arg)]
+ except:
+ raise Exception('Expression not watched')
+ else:
+ raise Exception('Specify an identifier')
+
+ def clear(self, arg):
+ self.table.clear()
+
+ def commands(self):
+ return {
+ 'watch': {
+ 'action': self.watch,
+ 'doc': 'Watch an expression.',
+ 'complete': gdb.COMPLETE_EXPRESSION
+ },
+ 'unwatch': {
+ 'action': self.unwatch,
+ 'doc': 'Stop watching an expression by id.',
+ 'complete': gdb.COMPLETE_EXPRESSION
+ },
+ 'clear': {
+ 'action': self.clear,
+ 'doc': 'Clear all the watched expressions.'
+ }
+ }
+
+# XXX traceback line numbers in this Python block must be increased by 1
+end
+
+# Better GDB defaults ----------------------------------------------------------
+
+set history save
+set verbose off
+set print pretty on
+set print array off
+set print array-indexes on
+set python print-stack full
+
+# Start ------------------------------------------------------------------------
+
+python Dashboard.start()
+
+# ------------------------------------------------------------------------------
+# Copyright (c) 2015-2017 Andrea Cardaci <cyrus.and@gmail.com>
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ------------------------------------------------------------------------------
+# vim: filetype=python
+# Local Variables:
+# mode: python
+# End: