dotfiles

My personal shell configs and stuff
git clone git://git.alex.balgavy.eu/dotfiles.git
Log | Files | Refs | Submodules | README | LICENSE

gdbinit (62654B)


      1 set startup-with-shell off
      2 python
      3 
      4 # GDB dashboard - Modular visual interface for GDB in Python.
      5 #
      6 # https://github.com/cyrus-and/gdb-dashboard
      7 
      8 import ast
      9 import fcntl
     10 import os
     11 import re
     12 import struct
     13 import termios
     14 import traceback
     15 import math
     16 
     17 # Common attributes ------------------------------------------------------------
     18 
     19 class R():
     20 
     21     @staticmethod
     22     def attributes():
     23         return {
     24             # miscellaneous
     25             'ansi': {
     26                 'doc': 'Control the ANSI output of the dashboard.',
     27                 'default': True,
     28                 'type': bool
     29             },
     30             'syntax_highlighting': {
     31                 'doc': """Pygments style to use for syntax highlighting.
     32 Using an empty string (or a name not in the list) disables this feature.
     33 The list of all the available styles can be obtained with (from GDB itself):
     34 
     35     python from pygments.styles import get_all_styles as styles
     36     python for s in styles(): print(s)
     37 """,
     38                 'default': 'vim',
     39                 'type': str
     40             },
     41             # prompt
     42             'prompt': {
     43                 'doc': """Command prompt.
     44 This value is parsed as a Python format string in which `{status}` is expanded
     45 with the substitution of either `prompt_running` or `prompt_not_running`
     46 attributes, according to the target program status. The resulting string must be
     47 a valid GDB prompt, see the command `python print(gdb.prompt.prompt_help())`""",
     48                 'default': '{status}'
     49             },
     50             'prompt_running': {
     51                 'doc': """`{status}` when the target program is running.
     52 See the `prompt` attribute. This value is parsed as a Python format string in
     53 which `{pid}` is expanded with the process identifier of the target program.""",
     54                 'default': '\[\e[1;35m\]>>>\[\e[0m\]'
     55             },
     56             'prompt_not_running': {
     57                 'doc': '`{status}` when the target program is not running.',
     58                 'default': '\[\e[1;30m\]>>>\[\e[0m\]'
     59             },
     60             # divider
     61             'divider_fill_char_primary': {
     62                 'doc': 'Filler around the label for primary dividers',
     63                 'default': '─'
     64             },
     65             'divider_fill_char_secondary': {
     66                 'doc': 'Filler around the label for secondary dividers',
     67                 'default': '─'
     68             },
     69             'divider_fill_style_primary': {
     70                 'doc': 'Style for `divider_fill_char_primary`',
     71                 'default': '36'
     72             },
     73             'divider_fill_style_secondary': {
     74                 'doc': 'Style for `divider_fill_char_secondary`',
     75                 'default': '1;30'
     76             },
     77             'divider_label_style_on_primary': {
     78                 'doc': 'Label style for non-empty primary dividers',
     79                 'default': '1;33'
     80             },
     81             'divider_label_style_on_secondary': {
     82                 'doc': 'Label style for non-empty secondary dividers',
     83                 'default': '0'
     84             },
     85             'divider_label_style_off_primary': {
     86                 'doc': 'Label style for empty primary dividers',
     87                 'default': '33'
     88             },
     89             'divider_label_style_off_secondary': {
     90                 'doc': 'Label style for empty secondary dividers',
     91                 'default': '1;30'
     92             },
     93             'divider_label_skip': {
     94                 'doc': 'Gap between the aligning border and the label.',
     95                 'default': 3,
     96                 'type': int,
     97                 'check': check_ge_zero
     98             },
     99             'divider_label_margin': {
    100                 'doc': 'Number of spaces around the label.',
    101                 'default': 1,
    102                 'type': int,
    103                 'check': check_ge_zero
    104             },
    105             'divider_label_align_right': {
    106                 'doc': 'Label alignment flag.',
    107                 'default': False,
    108                 'type': bool
    109             },
    110             # common styles
    111             'style_selected_1': {
    112                 'default': '1;32'
    113             },
    114             'style_selected_2': {
    115                 'default': '32'
    116             },
    117             'style_low': {
    118                 'default': '1;30'
    119             },
    120             'style_high': {
    121                 'default': '1;37'
    122             },
    123             'style_error': {
    124                 'default': '31'
    125             }
    126         }
    127 
    128 # Common -----------------------------------------------------------------------
    129 
    130 def run(command):
    131     return gdb.execute(command, to_string=True)
    132 
    133 def ansi(string, style):
    134     if R.ansi:
    135         return '\x1b[{}m{}\x1b[0m'.format(style, string)
    136     else:
    137         return string
    138 
    139 def divider(width, label='', primary=False, active=True):
    140     if primary:
    141         divider_fill_style = R.divider_fill_style_primary
    142         divider_fill_char = R.divider_fill_char_primary
    143         divider_label_style_on = R.divider_label_style_on_primary
    144         divider_label_style_off = R.divider_label_style_off_primary
    145     else:
    146         divider_fill_style = R.divider_fill_style_secondary
    147         divider_fill_char = R.divider_fill_char_secondary
    148         divider_label_style_on = R.divider_label_style_on_secondary
    149         divider_label_style_off = R.divider_label_style_off_secondary
    150     if label:
    151         if active:
    152             divider_label_style = divider_label_style_on
    153         else:
    154             divider_label_style = divider_label_style_off
    155         skip = R.divider_label_skip
    156         margin = R.divider_label_margin
    157         before = ansi(divider_fill_char * skip, divider_fill_style)
    158         middle = ansi(label, divider_label_style)
    159         after_length = width - len(label) - skip - 2 * margin
    160         after = ansi(divider_fill_char * after_length, divider_fill_style)
    161         if R.divider_label_align_right:
    162             before, after = after, before
    163         return ''.join([before, ' ' * margin, middle, ' ' * margin, after])
    164     else:
    165         return ansi(divider_fill_char * width, divider_fill_style)
    166 
    167 def check_gt_zero(x):
    168     return x > 0
    169 
    170 def check_ge_zero(x):
    171     return x >= 0
    172 
    173 def to_unsigned(value, size=8):
    174     # values from GDB can be used transparently but are not suitable for
    175     # being printed as unsigned integers, so a conversion is needed
    176     mask = (2 ** (size * 8)) - 1
    177     return int(value.cast(gdb.Value(mask).type)) & mask
    178 
    179 def to_string(value):
    180     # attempt to convert an inferior value to string; OK when (Python 3 ||
    181     # simple ASCII); otherwise (Python 2.7 && not ASCII) encode the string as
    182     # utf8
    183     try:
    184         value_string = str(value)
    185     except UnicodeEncodeError:
    186         value_string = unicode(value).encode('utf8')
    187     return value_string
    188 
    189 def format_address(address):
    190     pointer_size = gdb.parse_and_eval('$pc').type.sizeof
    191     return ('0x{{:0{}x}}').format(pointer_size * 2).format(address)
    192 
    193 def format_value(value):
    194     # format references as referenced values
    195     # (TYPE_CODE_RVALUE_REF is not supported by old GDB)
    196     if value.type.code in (getattr(gdb, 'TYPE_CODE_REF', None),
    197                            getattr(gdb, 'TYPE_CODE_RVALUE_REF', None)):
    198         try:
    199             return to_string(value.referenced_value())
    200         except gdb.MemoryError:
    201             return to_string(value)
    202     else:
    203         try:
    204             return to_string(value)
    205         except gdb.MemoryError as e:
    206             return ansi(e, R.style_error)
    207 
    208 class Beautifier():
    209     def __init__(self, filename, tab_size=4):
    210         self.tab_spaces = ' ' * tab_size
    211         self.active = False
    212         if not R.ansi:
    213             return
    214         # attempt to set up Pygments
    215         try:
    216             import pygments.lexers
    217             import pygments.formatters
    218             formatter_class = pygments.formatters.Terminal256Formatter
    219             self.formatter = formatter_class(style=R.syntax_highlighting)
    220             self.lexer = pygments.lexers.get_lexer_for_filename(filename)
    221             self.active = True
    222         except ImportError:
    223             # Pygments not available
    224             pass
    225         except pygments.util.ClassNotFound:
    226             # no lexer for this file or invalid style
    227             pass
    228 
    229     def process(self, source):
    230         # convert tabs anyway
    231         source = source.replace('\t', self.tab_spaces)
    232         if self.active:
    233             import pygments
    234             source = pygments.highlight(source, self.lexer, self.formatter)
    235         return source.rstrip('\n')
    236 
    237 # Dashboard --------------------------------------------------------------------
    238 
    239 class Dashboard(gdb.Command):
    240     """Redisplay the dashboard."""
    241 
    242     def __init__(self):
    243         gdb.Command.__init__(self, 'dashboard',
    244                              gdb.COMMAND_USER, gdb.COMPLETE_NONE, True)
    245         self.output = None  # main terminal
    246         # setup subcommands
    247         Dashboard.ConfigurationCommand(self)
    248         Dashboard.OutputCommand(self)
    249         Dashboard.EnabledCommand(self)
    250         Dashboard.LayoutCommand(self)
    251         # setup style commands
    252         Dashboard.StyleCommand(self, 'dashboard', R, R.attributes())
    253         # disabled by default
    254         self.enabled = None
    255         self.disable()
    256 
    257     def on_continue(self, _):
    258         # try to contain the GDB messages in a specified area unless the
    259         # dashboard is printed to a separate file (dashboard -output ...)
    260         if self.is_running() and not self.output:
    261             width = Dashboard.get_term_width()
    262             gdb.write(Dashboard.clear_screen())
    263             gdb.write(divider(width, 'Output/messages', True))
    264             gdb.write('\n')
    265             gdb.flush()
    266 
    267     def on_stop(self, _):
    268         if self.is_running():
    269             self.render(clear_screen=False)
    270 
    271     def on_exit(self, _):
    272         if not self.is_running():
    273             return
    274         # collect all the outputs
    275         outputs = set()
    276         outputs.add(self.output)
    277         outputs.update(module.output for module in self.modules)
    278         outputs.remove(None)
    279         # clean the screen and notify to avoid confusion
    280         for output in outputs:
    281             try:
    282                 with open(output, 'w') as fs:
    283                     fs.write(Dashboard.reset_terminal())
    284                     fs.write(Dashboard.clear_screen())
    285                     fs.write('--- EXITED ---')
    286             except:
    287                 # skip cleanup for invalid outputs
    288                 pass
    289 
    290     def enable(self):
    291         if self.enabled:
    292             return
    293         self.enabled = True
    294         # setup events
    295         gdb.events.cont.connect(self.on_continue)
    296         gdb.events.stop.connect(self.on_stop)
    297         gdb.events.exited.connect(self.on_exit)
    298 
    299     def disable(self):
    300         if not self.enabled:
    301             return
    302         self.enabled = False
    303         # setup events
    304         gdb.events.cont.disconnect(self.on_continue)
    305         gdb.events.stop.disconnect(self.on_stop)
    306         gdb.events.exited.disconnect(self.on_exit)
    307 
    308     def load_modules(self, modules):
    309         self.modules = []
    310         for module in modules:
    311             info = Dashboard.ModuleInfo(self, module)
    312             self.modules.append(info)
    313 
    314     def redisplay(self, style_changed=False):
    315         # manually redisplay the dashboard
    316         if self.is_running() and self.enabled:
    317             self.render(True, style_changed)
    318 
    319     def inferior_pid(self):
    320         return gdb.selected_inferior().pid
    321 
    322     def is_running(self):
    323         return self.inferior_pid() != 0
    324 
    325     def render(self, clear_screen, style_changed=False):
    326         # fetch module content and info
    327         all_disabled = True
    328         display_map = dict()
    329         for module in self.modules:
    330             # fall back to the global value
    331             output = module.output or self.output
    332             # add the instance or None if disabled
    333             if module.enabled:
    334                 all_disabled = False
    335                 instance = module.instance
    336             else:
    337                 instance = None
    338             display_map.setdefault(output, []).append(instance)
    339         # notify the user if the output is empty, on the main terminal
    340         if all_disabled:
    341             # write the error message
    342             width = Dashboard.get_term_width()
    343             gdb.write(divider(width, 'Error', True))
    344             gdb.write('\n')
    345             if self.modules:
    346                 gdb.write('No module to display (see `help dashboard`)')
    347             else:
    348                 gdb.write('No module loaded')
    349             # write the terminator
    350             gdb.write('\n')
    351             gdb.write(divider(width, primary=True))
    352             gdb.write('\n')
    353             gdb.flush()
    354             # continue to allow separate terminals to update
    355         # process each display info
    356         for output, instances in display_map.items():
    357             try:
    358                 fs = None
    359                 # use GDB stream by default
    360                 if output:
    361                     fs = open(output, 'w')
    362                     fd = fs.fileno()
    363                     # setup the terminal
    364                     fs.write(Dashboard.hide_cursor())
    365                 else:
    366                     fs = gdb
    367                     fd = 1  # stdout
    368                 # get the terminal width (default main terminal if either
    369                 # the output is not a file)
    370                 try:
    371                     width = Dashboard.get_term_width(fd)
    372                 except:
    373                     width = Dashboard.get_term_width()
    374                 # clear the "screen" if requested for the main terminal,
    375                 # auxiliary terminals are always cleared
    376                 if fs is not gdb or clear_screen:
    377                     fs.write(Dashboard.clear_screen())
    378                 # show message in separate terminals if all the modules are
    379                 # disabled
    380                 if output != self.output and not any(instances):
    381                     fs.write('--- NO MODULE TO DISPLAY ---\n')
    382                     continue
    383                 # process all the modules for that output
    384                 for n, instance in enumerate(instances, 1):
    385                     # skip disabled modules
    386                     if not instance:
    387                         continue
    388                     # ask the module to generate the content
    389                     lines = instance.lines(width, style_changed)
    390                     # create the divider accordingly
    391                     div = divider(width, instance.label(), True, lines)
    392                     # write the data
    393                     fs.write('\n'.join([div] + lines))
    394                     # write the newline for all but last unless main terminal
    395                     if n != len(instances) or fs is gdb:
    396                         fs.write('\n')
    397                 # write the final newline and the terminator only if it is the
    398                 # main terminal to allow the prompt to display correctly (unless
    399                 # there are no modules to display)
    400                 if fs is gdb and not all_disabled:
    401                     fs.write(divider(width, primary=True))
    402                     fs.write('\n')
    403                 fs.flush()
    404             except Exception as e:
    405                 cause = traceback.format_exc().strip()
    406                 Dashboard.err('Cannot write the dashboard\n{}'.format(cause))
    407             finally:
    408                 # don't close gdb stream
    409                 if fs and fs is not gdb:
    410                     fs.close()
    411 
    412 # Utility methods --------------------------------------------------------------
    413 
    414     @staticmethod
    415     def start():
    416         # initialize the dashboard
    417         dashboard = Dashboard()
    418         Dashboard.set_custom_prompt(dashboard)
    419         # parse Python inits, load modules then parse GDB inits
    420         Dashboard.parse_inits(True)
    421         modules = Dashboard.get_modules()
    422         dashboard.load_modules(modules)
    423         Dashboard.parse_inits(False)
    424         # GDB overrides
    425         run('set pagination off')
    426         # enable and display if possible (program running)
    427         dashboard.enable()
    428         dashboard.redisplay()
    429 
    430     @staticmethod
    431     def get_term_width(fd=1):  # defaults to the main terminal
    432         # first 2 shorts (4 byte) of struct winsize
    433         raw = fcntl.ioctl(fd, termios.TIOCGWINSZ, ' ' * 4)
    434         height, width = struct.unpack('hh', raw)
    435         return int(width)
    436 
    437     @staticmethod
    438     def set_custom_prompt(dashboard):
    439         def custom_prompt(_):
    440             # render thread status indicator
    441             if dashboard.is_running():
    442                 pid = dashboard.inferior_pid()
    443                 status = R.prompt_running.format(pid=pid)
    444             else:
    445                 status = R.prompt_not_running
    446             # build prompt
    447             prompt = R.prompt.format(status=status)
    448             prompt = gdb.prompt.substitute_prompt(prompt)
    449             return prompt + ' '  # force trailing space
    450         gdb.prompt_hook = custom_prompt
    451 
    452     @staticmethod
    453     def parse_inits(python):
    454         for root, dirs, files in os.walk(os.path.expanduser('~/.gdbinit.d/')):
    455             dirs.sort()
    456             for init in sorted(files):
    457                 path = os.path.join(root, init)
    458                 _, ext = os.path.splitext(path)
    459                 # either load Python files or GDB
    460                 if python == (ext == '.py'):
    461                     gdb.execute('source ' + path)
    462 
    463     @staticmethod
    464     def get_modules():
    465         # scan the scope for modules
    466         modules = []
    467         for name in globals():
    468             obj = globals()[name]
    469             try:
    470                 if issubclass(obj, Dashboard.Module):
    471                     modules.append(obj)
    472             except TypeError:
    473                 continue
    474         # sort modules alphabetically
    475         modules.sort(key=lambda x: x.__name__)
    476         return modules
    477 
    478     @staticmethod
    479     def create_command(name, invoke, doc, is_prefix, complete=None):
    480         Class = type('', (gdb.Command,), {'invoke': invoke, '__doc__': doc})
    481         Class(name, gdb.COMMAND_USER, complete or gdb.COMPLETE_NONE, is_prefix)
    482 
    483     @staticmethod
    484     def err(string):
    485         print(ansi(string, R.style_error))
    486 
    487     @staticmethod
    488     def complete(word, candidates):
    489         return filter(lambda candidate: candidate.startswith(word), candidates)
    490 
    491     @staticmethod
    492     def parse_arg(arg):
    493         # encode unicode GDB command arguments as utf8 in Python 2.7
    494         if type(arg) is not str:
    495             arg = arg.encode('utf8')
    496         return arg
    497 
    498     @staticmethod
    499     def clear_screen():
    500         # ANSI: move the cursor to top-left corner and clear the screen
    501         return '\x1b[H\x1b[J'
    502 
    503     @staticmethod
    504     def hide_cursor():
    505         # ANSI: hide cursor
    506         return '\x1b[?25l'
    507 
    508     @staticmethod
    509     def reset_terminal():
    510         # ANSI: reset to initial state
    511         return '\x1bc'
    512 
    513 # Module descriptor ------------------------------------------------------------
    514 
    515     class ModuleInfo:
    516 
    517         def __init__(self, dashboard, module):
    518             self.name = module.__name__.lower()  # from class to module name
    519             self.enabled = True
    520             self.output = None  # value from the dashboard by default
    521             self.instance = module()
    522             self.doc = self.instance.__doc__ or '(no documentation)'
    523             self.prefix = 'dashboard {}'.format(self.name)
    524             # add GDB commands
    525             self.add_main_command(dashboard)
    526             self.add_output_command(dashboard)
    527             self.add_style_command(dashboard)
    528             self.add_subcommands(dashboard)
    529 
    530         def add_main_command(self, dashboard):
    531             module = self
    532             def invoke(self, arg, from_tty, info=self):
    533                 arg = Dashboard.parse_arg(arg)
    534                 if arg == '':
    535                     info.enabled ^= True
    536                     if dashboard.is_running():
    537                         dashboard.redisplay()
    538                     else:
    539                         status = 'enabled' if info.enabled else 'disabled'
    540                         print('{} module {}'.format(module.name, status))
    541                 else:
    542                     Dashboard.err('Wrong argument "{}"'.format(arg))
    543             doc_brief = 'Configure the {} module.'.format(self.name)
    544             doc_extended = 'Toggle the module visibility.'
    545             doc = '{}\n{}\n\n{}'.format(doc_brief, doc_extended, self.doc)
    546             Dashboard.create_command(self.prefix, invoke, doc, True)
    547 
    548         def add_output_command(self, dashboard):
    549             Dashboard.OutputCommand(dashboard, self.prefix, self)
    550 
    551         def add_style_command(self, dashboard):
    552             if 'attributes' in dir(self.instance):
    553                 Dashboard.StyleCommand(dashboard, self.prefix, self.instance,
    554                                        self.instance.attributes())
    555 
    556         def add_subcommands(self, dashboard):
    557             if 'commands' in dir(self.instance):
    558                 for name, command in self.instance.commands().items():
    559                     self.add_subcommand(dashboard, name, command)
    560 
    561         def add_subcommand(self, dashboard, name, command):
    562             action = command['action']
    563             doc = command['doc']
    564             complete = command.get('complete')
    565             def invoke(self, arg, from_tty, info=self):
    566                 arg = Dashboard.parse_arg(arg)
    567                 if info.enabled:
    568                     try:
    569                         action(arg)
    570                     except Exception as e:
    571                         Dashboard.err(e)
    572                         return
    573                     # don't catch redisplay errors
    574                     dashboard.redisplay()
    575                 else:
    576                     Dashboard.err('Module disabled')
    577             prefix = '{} {}'.format(self.prefix, name)
    578             Dashboard.create_command(prefix, invoke, doc, False, complete)
    579 
    580 # GDB commands -----------------------------------------------------------------
    581 
    582     def invoke(self, arg, from_tty):
    583         arg = Dashboard.parse_arg(arg)
    584         # show messages for checks in redisplay
    585         if arg != '':
    586             Dashboard.err('Wrong argument "{}"'.format(arg))
    587         elif not self.is_running():
    588             Dashboard.err('Is the target program running?')
    589         else:
    590             self.redisplay()
    591 
    592     class ConfigurationCommand(gdb.Command):
    593         """Dump the dashboard configuration (layout, styles, outputs).
    594 With an optional argument the configuration will be written to the specified
    595 file."""
    596 
    597         def __init__(self, dashboard):
    598             gdb.Command.__init__(self, 'dashboard -configuration',
    599                                  gdb.COMMAND_USER, gdb.COMPLETE_FILENAME)
    600             self.dashboard = dashboard
    601 
    602         def invoke(self, arg, from_tty):
    603             arg = Dashboard.parse_arg(arg)
    604             if arg:
    605                 with open(os.path.expanduser(arg), 'w') as fs:
    606                     fs.write('# auto generated by GDB dashboard\n\n')
    607                     self.dump(fs)
    608             self.dump(gdb)
    609 
    610         def dump(self, fs):
    611             # dump layout
    612             self.dump_layout(fs)
    613             # dump styles
    614             self.dump_style(fs, R)
    615             for module in self.dashboard.modules:
    616                 self.dump_style(fs, module.instance, module.prefix)
    617             # dump outputs
    618             self.dump_output(fs, self.dashboard)
    619             for module in self.dashboard.modules:
    620                 self.dump_output(fs, module, module.prefix)
    621 
    622         def dump_layout(self, fs):
    623             layout = ['dashboard -layout']
    624             for module in self.dashboard.modules:
    625                 mark = '' if module.enabled else '!'
    626                 layout.append('{}{}'.format(mark, module.name))
    627             fs.write(' '.join(layout))
    628             fs.write('\n')
    629 
    630         def dump_style(self, fs, obj, prefix='dashboard'):
    631             attributes = getattr(obj, 'attributes', lambda: dict())()
    632             for name, attribute in attributes.items():
    633                 real_name = attribute.get('name', name)
    634                 default = attribute.get('default')
    635                 value = getattr(obj, real_name)
    636                 if value != default:
    637                     fs.write('{} -style {} {!r}\n'.format(prefix, name, value))
    638 
    639         def dump_output(self, fs, obj, prefix='dashboard'):
    640             output = getattr(obj, 'output')
    641             if output:
    642                 fs.write('{} -output {}\n'.format(prefix, output))
    643 
    644     class OutputCommand(gdb.Command):
    645         """Set the output file/TTY for both the dashboard and modules.
    646 The dashboard/module will be written to the specified file, which will be
    647 created if it does not exist. If the specified file identifies a terminal then
    648 its width will be used to format the dashboard, otherwise falls back to the
    649 width of the main GDB terminal. Without argument the dashboard, the
    650 output/messages and modules which do not specify the output will be printed on
    651 standard output (default). Without argument the module will be printed where the
    652 dashboard will be printed."""
    653 
    654         def __init__(self, dashboard, prefix=None, obj=None):
    655             if not prefix:
    656                 prefix = 'dashboard'
    657             if not obj:
    658                 obj = dashboard
    659             prefix = prefix + ' -output'
    660             gdb.Command.__init__(self, prefix,
    661                                  gdb.COMMAND_USER, gdb.COMPLETE_FILENAME)
    662             self.dashboard = dashboard
    663             self.obj = obj  # None means the dashboard itself
    664 
    665         def invoke(self, arg, from_tty):
    666             arg = Dashboard.parse_arg(arg)
    667             # display a message in a separate terminal if released (note that
    668             # the check if this is the last module to use the output is not
    669             # performed since if that's not the case the message will be
    670             # overwritten)
    671             if self.obj.output:
    672                 try:
    673                     with open(self.obj.output, 'w') as fs:
    674                         fs.write(Dashboard.clear_screen())
    675                         fs.write('--- RELEASED ---\n')
    676                 except:
    677                     # just do nothing if the file is not writable
    678                     pass
    679             # set or open the output file
    680             if arg == '':
    681                 self.obj.output = None
    682             else:
    683                 self.obj.output = arg
    684             # redisplay the dashboard in the new output
    685             self.dashboard.redisplay()
    686 
    687     class EnabledCommand(gdb.Command):
    688         """Enable or disable the dashboard [on|off].
    689 The current status is printed if no argument is present."""
    690 
    691         def __init__(self, dashboard):
    692             gdb.Command.__init__(self, 'dashboard -enabled', gdb.COMMAND_USER)
    693             self.dashboard = dashboard
    694 
    695         def invoke(self, arg, from_tty):
    696             arg = Dashboard.parse_arg(arg)
    697             if arg == '':
    698                 status = 'enabled' if self.dashboard.enabled else 'disabled'
    699                 print('The dashboard is {}'.format(status))
    700             elif arg == 'on':
    701                 self.dashboard.enable()
    702                 self.dashboard.redisplay()
    703             elif arg == 'off':
    704                 self.dashboard.disable()
    705             else:
    706                 msg = 'Wrong argument "{}"; expecting "on" or "off"'
    707                 Dashboard.err(msg.format(arg))
    708 
    709         def complete(self, text, word):
    710             return Dashboard.complete(word, ['on', 'off'])
    711 
    712     class LayoutCommand(gdb.Command):
    713         """Set or show the dashboard layout.
    714 Accepts a space-separated list of directive. Each directive is in the form
    715 "[!]<module>". Modules in the list are placed in the dashboard in the same order
    716 as they appear and those prefixed by "!" are disabled by default. Omitted
    717 modules are hidden and placed at the bottom in alphabetical order. Without
    718 arguments the current layout is shown where the first line uses the same form
    719 expected by the input while the remaining depict the current status of output
    720 files."""
    721 
    722         def __init__(self, dashboard):
    723             gdb.Command.__init__(self, 'dashboard -layout', gdb.COMMAND_USER)
    724             self.dashboard = dashboard
    725 
    726         def invoke(self, arg, from_tty):
    727             arg = Dashboard.parse_arg(arg)
    728             directives = str(arg).split()
    729             if directives:
    730                 self.layout(directives)
    731                 if from_tty and not self.dashboard.is_running():
    732                     self.show()
    733             else:
    734                 self.show()
    735 
    736         def show(self):
    737             global_str = 'Global'
    738             max_name_len = len(global_str)
    739             # print directives
    740             modules = []
    741             for module in self.dashboard.modules:
    742                 max_name_len = max(max_name_len, len(module.name))
    743                 mark = '' if module.enabled else '!'
    744                 modules.append('{}{}'.format(mark, module.name))
    745             print(' '.join(modules))
    746             # print outputs
    747             default = '(default)'
    748             fmt = '{{:{}s}}{{}}'.format(max_name_len + 2)
    749             print(('\n' + fmt + '\n').format(global_str,
    750                                              self.dashboard.output or default))
    751             for module in self.dashboard.modules:
    752                 style = R.style_high if module.enabled else R.style_low
    753                 line = fmt.format(module.name, module.output or default)
    754                 print(ansi(line, style))
    755 
    756         def layout(self, directives):
    757             modules = self.dashboard.modules
    758             # reset visibility
    759             for module in modules:
    760                 module.enabled = False
    761             # move and enable the selected modules on top
    762             last = 0
    763             n_enabled = 0
    764             for directive in directives:
    765                 # parse next directive
    766                 enabled = (directive[0] != '!')
    767                 name = directive[not enabled:]
    768                 try:
    769                     # it may actually start from last, but in this way repeated
    770                     # modules can be handled transparently and without error
    771                     todo = enumerate(modules[last:], start=last)
    772                     index = next(i for i, m in todo if name == m.name)
    773                     modules[index].enabled = enabled
    774                     modules.insert(last, modules.pop(index))
    775                     last += 1
    776                     n_enabled += enabled
    777                 except StopIteration:
    778                     def find_module(x):
    779                         return x.name == name
    780                     first_part = modules[:last]
    781                     if len(list(filter(find_module, first_part))) == 0:
    782                         Dashboard.err('Cannot find module "{}"'.format(name))
    783                     else:
    784                         Dashboard.err('Module "{}" already set'.format(name))
    785                     continue
    786             # redisplay the dashboard
    787             if n_enabled:
    788                 self.dashboard.redisplay()
    789 
    790         def complete(self, text, word):
    791             all_modules = (m.name for m in self.dashboard.modules)
    792             return Dashboard.complete(word, all_modules)
    793 
    794     class StyleCommand(gdb.Command):
    795         """Access the stylable attributes.
    796 Without arguments print all the stylable attributes. Subcommands are used to set
    797 or print (when the value is omitted) individual attributes."""
    798 
    799         def __init__(self, dashboard, prefix, obj, attributes):
    800             self.prefix = prefix + ' -style'
    801             gdb.Command.__init__(self, self.prefix,
    802                                  gdb.COMMAND_USER, gdb.COMPLETE_NONE, True)
    803             self.dashboard = dashboard
    804             self.obj = obj
    805             self.attributes = attributes
    806             self.add_styles()
    807 
    808         def add_styles(self):
    809             this = self
    810             for name, attribute in self.attributes.items():
    811                 # fetch fields
    812                 attr_name = attribute.get('name', name)
    813                 attr_type = attribute.get('type', str)
    814                 attr_check = attribute.get('check', lambda _: True)
    815                 attr_default = attribute['default']
    816                 # set the default value (coerced to the type)
    817                 value = attr_type(attr_default)
    818                 setattr(self.obj, attr_name, value)
    819                 # create the command
    820                 def invoke(self, arg, from_tty, name=name, attr_name=attr_name,
    821                            attr_type=attr_type, attr_check=attr_check):
    822                     new_value = Dashboard.parse_arg(arg)
    823                     if new_value == '':
    824                         # print the current value
    825                         value = getattr(this.obj, attr_name)
    826                         print('{} = {!r}'.format(name, value))
    827                     else:
    828                         try:
    829                             # convert and check the new value
    830                             parsed = ast.literal_eval(new_value)
    831                             value = attr_type(parsed)
    832                             if not attr_check(value):
    833                                 msg = 'Invalid value "{}" for "{}"'
    834                                 raise Exception(msg.format(new_value, name))
    835                         except Exception as e:
    836                             Dashboard.err(e)
    837                         else:
    838                             # set and redisplay
    839                             setattr(this.obj, attr_name, value)
    840                             this.dashboard.redisplay(True)
    841                 prefix = self.prefix + ' ' + name
    842                 doc = attribute.get('doc', 'This style is self-documenting')
    843                 Dashboard.create_command(prefix, invoke, doc, False)
    844 
    845         def invoke(self, arg, from_tty):
    846             # an argument here means that the provided attribute is invalid
    847             if arg:
    848                 Dashboard.err('Invalid argument "{}"'.format(arg))
    849                 return
    850             # print all the pairs
    851             for name, attribute in self.attributes.items():
    852                 attr_name = attribute.get('name', name)
    853                 value = getattr(self.obj, attr_name)
    854                 print('{} = {!r}'.format(name, value))
    855 
    856 # Base module ------------------------------------------------------------------
    857 
    858     # just a tag
    859     class Module():
    860         pass
    861 
    862 # Default modules --------------------------------------------------------------
    863 
    864 class Source(Dashboard.Module):
    865     """Show the program source code, if available."""
    866 
    867     def __init__(self):
    868         self.file_name = None
    869         self.source_lines = []
    870         self.ts = None
    871         self.highlighted = False
    872 
    873     def label(self):
    874         return 'Source'
    875 
    876     def lines(self, term_width, style_changed):
    877         # skip if the current thread is not stopped
    878         if not gdb.selected_thread().is_stopped():
    879             return []
    880         # try to fetch the current line (skip if no line information)
    881         sal = gdb.selected_frame().find_sal()
    882         current_line = sal.line
    883         if current_line == 0:
    884             return []
    885         # reload the source file if changed
    886         file_name = sal.symtab.fullname()
    887         ts = None
    888         try:
    889             ts = os.path.getmtime(file_name)
    890         except:
    891             pass  # delay error check to open()
    892         if (style_changed or
    893                 file_name != self.file_name or  # different file name
    894                 ts and ts > self.ts):  # file modified in the meanwhile
    895             self.file_name = file_name
    896             self.ts = ts
    897             try:
    898                 highlighter = Beautifier(self.file_name, self.tab_size)
    899                 self.highlighted = highlighter.active
    900                 with open(self.file_name) as source_file:
    901                     source = highlighter.process(source_file.read())
    902                     self.source_lines = source.split('\n')
    903             except Exception as e:
    904                 msg = 'Cannot display "{}" ({})'.format(self.file_name, e)
    905                 return [ansi(msg, R.style_error)]
    906         # compute the line range
    907         start = max(current_line - 1 - self.context, 0)
    908         end = min(current_line - 1 + self.context + 1, len(self.source_lines))
    909         # return the source code listing
    910         out = []
    911         number_format = '{{:>{}}}'.format(len(str(end)))
    912         for number, line in enumerate(self.source_lines[start:end], start + 1):
    913             # properly handle UTF-8 source files
    914             line = to_string(line)
    915             if int(number) == current_line:
    916                 # the current line has a different style without ANSI
    917                 if R.ansi:
    918                     if self.highlighted:
    919                         line_format = ansi(number_format,
    920                                            R.style_selected_1) + ' {}'
    921                     else:
    922                         line_format = ansi(number_format + ' {}',
    923                                            R.style_selected_1)
    924                 else:
    925                     # just show a plain text indicator
    926                     line_format = number_format + '>{}'
    927             else:
    928                 line_format = ansi(number_format, R.style_low) + ' {}'
    929             out.append(line_format.format(number, line.rstrip('\n')))
    930         return out
    931 
    932     def attributes(self):
    933         return {
    934             'context': {
    935                 'doc': 'Number of context lines.',
    936                 'default': 10,
    937                 'type': int,
    938                 'check': check_ge_zero
    939             },
    940             'tab-size': {
    941                 'doc': 'Number of spaces used to display the tab character.',
    942                 'default': 4,
    943                 'name': 'tab_size',
    944                 'type': int,
    945                 'check': check_gt_zero
    946             }
    947         }
    948 
    949 class Assembly(Dashboard.Module):
    950     """Show the disassembled code surrounding the program counter. The
    951 instructions constituting the current statement are marked, if available."""
    952 
    953     def label(self):
    954         return 'Assembly'
    955 
    956     def lines(self, term_width, style_changed):
    957         # skip if the current thread is not stopped
    958         if not gdb.selected_thread().is_stopped():
    959             return []
    960         line_info = None
    961         frame = gdb.selected_frame()  # PC is here
    962         disassemble = frame.architecture().disassemble
    963         try:
    964             # try to fetch the function boundaries using the disassemble command
    965             output = run('disassemble').split('\n')
    966             start = int(re.split('[ :]', output[1][3:], 1)[0], 16)
    967             end = int(re.split('[ :]', output[-3][3:], 1)[0], 16)
    968             asm = disassemble(start, end_pc=end)
    969             # find the location of the PC
    970             pc_index = next(index for index, instr in enumerate(asm)
    971                             if instr['addr'] == frame.pc())
    972             start = max(pc_index - self.context, 0)
    973             end = pc_index + self.context + 1
    974             asm = asm[start:end]
    975             # if there are line information then use it, it may be that
    976             # line_info is not None but line_info.last is None
    977             line_info = gdb.find_pc_line(frame.pc())
    978             line_info = line_info if line_info.last else None
    979         except (gdb.error, StopIteration):
    980             # if it is not possible (stripped binary or the PC is not present in
    981             # the output of `disassemble` as per issue #31) start from PC and
    982             # end after twice the context
    983             try:
    984                 asm = disassemble(frame.pc(), count=2 * self.context + 1)
    985             except gdb.error as e:
    986                 msg = '{}'.format(e)
    987                 return [ansi(msg, R.style_error)]
    988         # fetch function start if available
    989         func_start = None
    990         if self.show_function and frame.name():
    991             try:
    992                 # it may happen that the frame name is the whole function
    993                 # declaration, instead of just the name, e.g., 'getkey()', so it
    994                 # would be treated as a function call by 'gdb.parse_and_eval',
    995                 # hence the trim, see #87 and #88
    996                 value = gdb.parse_and_eval(frame.name().split('(')[0]).address
    997                 func_start = to_unsigned(value)
    998             except gdb.error:
    999                 pass  # e.g., @plt
   1000         # fetch the assembly flavor and the extension used by Pygments
   1001         try:
   1002             flavor = gdb.parameter('disassembly-flavor')
   1003         except:
   1004             flavor = None  # not always defined (see #36)
   1005         filename = {
   1006             'att': '.s',
   1007             'intel': '.asm'
   1008         }.get(flavor, '.s')
   1009         # prepare the highlighter
   1010         highlighter = Beautifier(filename)
   1011         # compute the maximum offset size
   1012         if func_start:
   1013             max_offset = max(len(str(abs(asm[0]['addr'] - func_start))),
   1014                              len(str(abs(asm[-1]['addr'] - func_start))))
   1015         # return the machine code
   1016         max_length = max(instr['length'] for instr in asm)
   1017         inferior = gdb.selected_inferior()
   1018         out = []
   1019         for index, instr in enumerate(asm):
   1020             addr = instr['addr']
   1021             length = instr['length']
   1022             text = instr['asm']
   1023             addr_str = format_address(addr)
   1024             if self.show_opcodes:
   1025                 # fetch and format opcode
   1026                 region = inferior.read_memory(addr, length)
   1027                 opcodes = (' '.join('{:02x}'.format(ord(byte))
   1028                                     for byte in region))
   1029                 opcodes += (max_length - len(region)) * 3 * ' ' + ' '
   1030             else:
   1031                 opcodes = ''
   1032             # compute the offset if available
   1033             if self.show_function:
   1034                 if func_start:
   1035                     offset = '{:+d}'.format(addr - func_start)
   1036                     offset = offset.ljust(max_offset + 1)  # sign
   1037                     func_info = '{}{}'.format(frame.name(), offset)
   1038                 else:
   1039                     func_info = '?'
   1040             else:
   1041                 func_info = ''
   1042             format_string = '{}{}{}{}{}'
   1043             indicator = ' '
   1044             text = ' ' + highlighter.process(text)
   1045             if addr == frame.pc():
   1046                 if not R.ansi:
   1047                     indicator = '>'
   1048                 addr_str = ansi(addr_str, R.style_selected_1)
   1049                 indicator = ansi(indicator, R.style_selected_1)
   1050                 opcodes = ansi(opcodes, R.style_selected_1)
   1051                 func_info = ansi(func_info, R.style_selected_1)
   1052                 if not highlighter.active:
   1053                     text = ansi(text, R.style_selected_1)
   1054             elif line_info and line_info.pc <= addr < line_info.last:
   1055                 if not R.ansi:
   1056                     indicator = ':'
   1057                 addr_str = ansi(addr_str, R.style_selected_2)
   1058                 indicator = ansi(indicator, R.style_selected_2)
   1059                 opcodes = ansi(opcodes, R.style_selected_2)
   1060                 func_info = ansi(func_info, R.style_selected_2)
   1061                 if not highlighter.active:
   1062                     text = ansi(text, R.style_selected_2)
   1063             else:
   1064                 addr_str = ansi(addr_str, R.style_low)
   1065                 func_info = ansi(func_info, R.style_low)
   1066             out.append(format_string.format(addr_str, indicator,
   1067                                             opcodes, func_info, text))
   1068         return out
   1069 
   1070     def attributes(self):
   1071         return {
   1072             'context': {
   1073                 'doc': 'Number of context instructions.',
   1074                 'default': 3,
   1075                 'type': int,
   1076                 'check': check_ge_zero
   1077             },
   1078             'opcodes': {
   1079                 'doc': 'Opcodes visibility flag.',
   1080                 'default': False,
   1081                 'name': 'show_opcodes',
   1082                 'type': bool
   1083             },
   1084             'function': {
   1085                 'doc': 'Function information visibility flag.',
   1086                 'default': True,
   1087                 'name': 'show_function',
   1088                 'type': bool
   1089             }
   1090         }
   1091 
   1092 class Stack(Dashboard.Module):
   1093     """Show the current stack trace including the function name and the file
   1094 location, if available. Optionally list the frame arguments and locals too."""
   1095 
   1096     def label(self):
   1097         return 'Stack'
   1098 
   1099     def lines(self, term_width, style_changed):
   1100         # skip if the current thread is not stopped
   1101         if not gdb.selected_thread().is_stopped():
   1102             return []
   1103         # find the selected frame (i.e., the first to display)
   1104         selected_index = 0
   1105         frame = gdb.newest_frame()
   1106         while frame:
   1107             if frame == gdb.selected_frame():
   1108                 break
   1109             frame = frame.older()
   1110             selected_index += 1
   1111         # format up to "limit" frames
   1112         frames = []
   1113         number = selected_index
   1114         more = False
   1115         while frame:
   1116             # the first is the selected one
   1117             selected = (len(frames) == 0)
   1118             # fetch frame info
   1119             style = R.style_selected_1 if selected else R.style_selected_2
   1120             frame_id = ansi(str(number), style)
   1121             info = Stack.get_pc_line(frame, style)
   1122             frame_lines = []
   1123             frame_lines.append('[{}] {}'.format(frame_id, info))
   1124             # fetch frame arguments and locals
   1125             decorator = gdb.FrameDecorator.FrameDecorator(frame)
   1126             separator = ansi(', ', R.style_low)
   1127             strip_newlines = re.compile(r'$\s*', re.MULTILINE)
   1128             if self.show_arguments:
   1129                 def prefix(line):
   1130                     return Stack.format_line('arg', line)
   1131                 frame_args = decorator.frame_args()
   1132                 args_lines = Stack.fetch_frame_info(frame, frame_args)
   1133                 if args_lines:
   1134                     if self.compact:
   1135                         args_line = separator.join(args_lines)
   1136                         args_line = strip_newlines.sub('', args_line)
   1137                         single_line = prefix(args_line)
   1138                         frame_lines.append(single_line)
   1139                     else:
   1140                         frame_lines.extend(map(prefix, args_lines))
   1141                 else:
   1142                     frame_lines.append(ansi('(no arguments)', R.style_low))
   1143             if self.show_locals:
   1144                 def prefix(line):
   1145                     return Stack.format_line('loc', line)
   1146                 frame_locals = decorator.frame_locals()
   1147                 locals_lines = Stack.fetch_frame_info(frame, frame_locals)
   1148                 if locals_lines:
   1149                     if self.compact:
   1150                         locals_line = separator.join(locals_lines)
   1151                         locals_line = strip_newlines.sub('', locals_line)
   1152                         single_line = prefix(locals_line)
   1153                         frame_lines.append(single_line)
   1154                     else:
   1155                         frame_lines.extend(map(prefix, locals_lines))
   1156                 else:
   1157                     frame_lines.append(ansi('(no locals)', R.style_low))
   1158             # add frame
   1159             frames.append(frame_lines)
   1160             # next
   1161             frame = frame.older()
   1162             number += 1
   1163             # check finished according to the limit
   1164             if self.limit and len(frames) == self.limit:
   1165                 # more frames to show but limited
   1166                 if frame:
   1167                     more = True
   1168                 break
   1169         # format the output
   1170         lines = []
   1171         for frame_lines in frames:
   1172             lines.extend(frame_lines)
   1173         # add the placeholder
   1174         if more:
   1175             lines.append('[{}]'.format(ansi('+', R.style_selected_2)))
   1176         return lines
   1177 
   1178     @staticmethod
   1179     def format_line(prefix, line):
   1180         prefix = ansi(prefix, R.style_low)
   1181         return '{} {}'.format(prefix, line)
   1182 
   1183     @staticmethod
   1184     def fetch_frame_info(frame, data):
   1185         lines = []
   1186         for elem in data or []:
   1187             name = elem.sym
   1188             equal = ansi('=', R.style_low)
   1189             value = format_value(elem.sym.value(frame))
   1190             lines.append('{} {} {}'.format(name, equal, value))
   1191         return lines
   1192 
   1193     @staticmethod
   1194     def get_pc_line(frame, style):
   1195         frame_pc = ansi(format_address(frame.pc()), style)
   1196         info = 'from {}'.format(frame_pc)
   1197         if frame.name():
   1198             frame_name = ansi(frame.name(), style)
   1199             try:
   1200                 # try to compute the offset relative to the current function (it
   1201                 # may happen that the frame name is the whole function
   1202                 # declaration, instead of just the name, e.g., 'getkey()', so it
   1203                 # would be treated as a function call by 'gdb.parse_and_eval',
   1204                 # hence the trim, see #87 and #88)
   1205                 value = gdb.parse_and_eval(frame.name().split('(')[0]).address
   1206                 # it can be None even if it is part of the "stack" (C++)
   1207                 if value:
   1208                     func_start = to_unsigned(value)
   1209                     offset = frame.pc() - func_start
   1210                     frame_name += '+' + ansi(str(offset), style)
   1211             except gdb.error:
   1212                 pass  # e.g., @plt
   1213             info += ' in {}'.format(frame_name)
   1214             sal = frame.find_sal()
   1215             if sal.symtab:
   1216                 file_name = ansi(sal.symtab.filename, style)
   1217                 file_line = ansi(str(sal.line), style)
   1218                 info += ' at {}:{}'.format(file_name, file_line)
   1219         return info
   1220 
   1221     def attributes(self):
   1222         return {
   1223             'limit': {
   1224                 'doc': 'Maximum number of displayed frames (0 means no limit).',
   1225                 'default': 2,
   1226                 'type': int,
   1227                 'check': check_ge_zero
   1228             },
   1229             'arguments': {
   1230                 'doc': 'Frame arguments visibility flag.',
   1231                 'default': True,
   1232                 'name': 'show_arguments',
   1233                 'type': bool
   1234             },
   1235             'locals': {
   1236                 'doc': 'Frame locals visibility flag.',
   1237                 'default': False,
   1238                 'name': 'show_locals',
   1239                 'type': bool
   1240             },
   1241             'compact': {
   1242                 'doc': 'Single-line display flag.',
   1243                 'default': False,
   1244                 'type': bool
   1245             }
   1246         }
   1247 
   1248 class History(Dashboard.Module):
   1249     """List the last entries of the value history."""
   1250 
   1251     def label(self):
   1252         return 'History'
   1253 
   1254     def lines(self, term_width, style_changed):
   1255         out = []
   1256         # fetch last entries
   1257         for i in range(-self.limit + 1, 1):
   1258             try:
   1259                 value = format_value(gdb.history(i))
   1260                 value_id = ansi('$${}', R.style_low).format(abs(i))
   1261                 line = '{} = {}'.format(value_id, value)
   1262                 out.append(line)
   1263             except gdb.error:
   1264                 continue
   1265         return out
   1266 
   1267     def attributes(self):
   1268         return {
   1269             'limit': {
   1270                 'doc': 'Maximum number of values to show.',
   1271                 'default': 3,
   1272                 'type': int,
   1273                 'check': check_gt_zero
   1274             }
   1275         }
   1276 
   1277 class Memory(Dashboard.Module):
   1278     """Allow to inspect memory regions."""
   1279 
   1280     @staticmethod
   1281     def format_byte(byte):
   1282         # `type(byte) is bytes` in Python 3
   1283         if byte.isspace():
   1284             return ' '
   1285         elif 0x20 < ord(byte) < 0x7e:
   1286             return chr(ord(byte))
   1287         else:
   1288             return '.'
   1289 
   1290     @staticmethod
   1291     def parse_as_address(expression):
   1292         value = gdb.parse_and_eval(expression)
   1293         return to_unsigned(value)
   1294 
   1295     def __init__(self):
   1296         self.row_length = 16
   1297         self.table = {}
   1298 
   1299     def format_memory(self, start, memory):
   1300         out = []
   1301         for i in range(0, len(memory), self.row_length):
   1302             region = memory[i:i + self.row_length]
   1303             pad = self.row_length - len(region)
   1304             address = format_address(start + i)
   1305             hexa = (' '.join('{:02x}'.format(ord(byte)) for byte in region))
   1306             text = (''.join(Memory.format_byte(byte) for byte in region))
   1307             out.append('{} {}{} {}{}'.format(ansi(address, R.style_low),
   1308                                              hexa,
   1309                                              ansi(pad * ' --', R.style_low),
   1310                                              ansi(text, R.style_high),
   1311                                              ansi(pad * '.', R.style_low)))
   1312         return out
   1313 
   1314     def label(self):
   1315         return 'Memory'
   1316 
   1317     def lines(self, term_width, style_changed):
   1318         out = []
   1319         inferior = gdb.selected_inferior()
   1320         for address, length in sorted(self.table.items()):
   1321             try:
   1322                 memory = inferior.read_memory(address, length)
   1323                 out.extend(self.format_memory(address, memory))
   1324             except gdb.error:
   1325                 msg = 'Cannot access {} bytes starting at {}'
   1326                 msg = msg.format(length, format_address(address))
   1327                 out.append(ansi(msg, R.style_error))
   1328             out.append(divider(term_width))
   1329         # drop last divider
   1330         if out:
   1331             del out[-1]
   1332         return out
   1333 
   1334     def watch(self, arg):
   1335         if arg:
   1336             address, _, length = arg.partition(' ')
   1337             address = Memory.parse_as_address(address)
   1338             if length:
   1339                 length = Memory.parse_as_address(length)
   1340             else:
   1341                 length = self.row_length
   1342             self.table[address] = length
   1343         else:
   1344             raise Exception('Specify an address')
   1345 
   1346     def unwatch(self, arg):
   1347         if arg:
   1348             try:
   1349                 del self.table[Memory.parse_as_address(arg)]
   1350             except KeyError:
   1351                 raise Exception('Memory region not watched')
   1352         else:
   1353             raise Exception('Specify an address')
   1354 
   1355     def clear(self, arg):
   1356         self.table.clear()
   1357 
   1358     def commands(self):
   1359         return {
   1360             'watch': {
   1361                 'action': self.watch,
   1362                 'doc': 'Watch a memory region by address and length.\n'
   1363                        'The length defaults to 16 byte.',
   1364                 'complete': gdb.COMPLETE_EXPRESSION
   1365             },
   1366             'unwatch': {
   1367                 'action': self.unwatch,
   1368                 'doc': 'Stop watching a memory region by address.',
   1369                 'complete': gdb.COMPLETE_EXPRESSION
   1370             },
   1371             'clear': {
   1372                 'action': self.clear,
   1373                 'doc': 'Clear all the watched regions.'
   1374             }
   1375         }
   1376 
   1377 class Registers(Dashboard.Module):
   1378     """Show the CPU registers and their values."""
   1379 
   1380     def __init__(self):
   1381         self.table = {}
   1382 
   1383     def label(self):
   1384         return 'Registers'
   1385 
   1386     def lines(self, term_width, style_changed):
   1387         # skip if the current thread is not stopped
   1388         if not gdb.selected_thread().is_stopped():
   1389             return []
   1390         # fetch registers status
   1391         registers = []
   1392         for reg_info in run('info registers').strip().split('\n'):
   1393             # fetch register and update the table
   1394             name = reg_info.split(None, 1)[0]
   1395             # Exclude registers with a dot '.' or parse_and_eval() will fail
   1396             if '.' in name:
   1397                 continue
   1398             value = gdb.parse_and_eval('${}'.format(name))
   1399             string_value = Registers.format_value(value)
   1400             changed = self.table and (self.table.get(name, '') != string_value)
   1401             self.table[name] = string_value
   1402             registers.append((name, string_value, changed))
   1403         # split registers in rows and columns, each column is composed of name,
   1404         # space, value and another trailing space which is skipped in the last
   1405         # column (hence term_width + 1)
   1406         max_name = max(len(name) for name, _, _ in registers)
   1407         max_value = max(len(value) for _, value, _ in registers)
   1408         max_width = max_name + max_value + 2
   1409         per_line = int((term_width + 1) / max_width) or 1
   1410         # redistribute extra space among columns
   1411         extra = int((term_width + 1 - max_width * per_line) / per_line)
   1412         if per_line == 1:
   1413             # center when there is only one column
   1414             max_name += int(extra / 2)
   1415             max_value += int(extra / 2)
   1416         else:
   1417             max_value += extra
   1418         # format registers info
   1419         partial = []
   1420         for name, value, changed in registers:
   1421             styled_name = ansi(name.rjust(max_name), R.style_low)
   1422             value_style = R.style_selected_1 if changed else ''
   1423             styled_value = ansi(value.ljust(max_value), value_style)
   1424             partial.append(styled_name + ' ' + styled_value)
   1425         out = []
   1426         if self.column_major:
   1427             num_lines = int(math.ceil(float(len(partial)) / per_line))
   1428             for i in range(num_lines):
   1429                 line = ' '.join(partial[i:len(partial):num_lines]).rstrip()
   1430                 real_n_col = math.ceil(float(len(partial)) / num_lines)
   1431                 line = ' ' * int((per_line - real_n_col) * max_width / 2) + line
   1432                 out.append(line)
   1433         else:
   1434             for i in range(0, len(partial), per_line):
   1435                 out.append(' '.join(partial[i:i + per_line]).rstrip())
   1436         return out
   1437 
   1438     def attributes(self):
   1439         return {
   1440             'column-major': {
   1441                 'doc': 'Whether to show registers in columns instead of rows.',
   1442                 'default': False,
   1443                 'name': 'column_major',
   1444                 'type': bool
   1445             }
   1446         }
   1447 
   1448     @staticmethod
   1449     def format_value(value):
   1450         try:
   1451             if value.type.code in [gdb.TYPE_CODE_INT, gdb.TYPE_CODE_PTR]:
   1452                 int_value = to_unsigned(value, value.type.sizeof)
   1453                 value_format = '0x{{:0{}x}}'.format(2 * value.type.sizeof)
   1454                 return value_format.format(int_value)
   1455         except (gdb.error, ValueError):
   1456             # convert to unsigned but preserve code and flags information
   1457             pass
   1458         return str(value)
   1459 
   1460 class Threads(Dashboard.Module):
   1461     """List the currently available threads."""
   1462 
   1463     def label(self):
   1464         return 'Threads'
   1465 
   1466     def lines(self, term_width, style_changed):
   1467         out = []
   1468         selected_thread = gdb.selected_thread()
   1469         # do not restore the selected frame if the thread is not stopped
   1470         restore_frame = gdb.selected_thread().is_stopped()
   1471         if restore_frame:
   1472             selected_frame = gdb.selected_frame()
   1473         for thread in gdb.Inferior.threads(gdb.selected_inferior()):
   1474             # skip running threads if requested
   1475             if self.skip_running and thread.is_running():
   1476                 continue
   1477             is_selected = (thread.ptid == selected_thread.ptid)
   1478             style = R.style_selected_1 if is_selected else R.style_selected_2
   1479             number = ansi(str(thread.num), style)
   1480             tid = ansi(str(thread.ptid[1] or thread.ptid[2]), style)
   1481             info = '[{}] id {}'.format(number, tid)
   1482             if thread.name:
   1483                 info += ' name {}'.format(ansi(thread.name, style))
   1484             # switch thread to fetch info (unless is running in non-stop mode)
   1485             try:
   1486                 thread.switch()
   1487                 frame = gdb.newest_frame()
   1488                 info += ' ' + Stack.get_pc_line(frame, style)
   1489             except gdb.error:
   1490                 info += ' (running)'
   1491             out.append(info)
   1492         # restore thread and frame
   1493         selected_thread.switch()
   1494         if restore_frame:
   1495             selected_frame.select()
   1496         return out
   1497 
   1498     def attributes(self):
   1499         return {
   1500             'skip-running': {
   1501                 'doc': 'Skip running threads.',
   1502                 'default': False,
   1503                 'name': 'skip_running',
   1504                 'type': bool
   1505             }
   1506         }
   1507 
   1508 class Expressions(Dashboard.Module):
   1509     """Watch user expressions."""
   1510 
   1511     def __init__(self):
   1512         self.number = 1
   1513         self.table = {}
   1514 
   1515     def label(self):
   1516         return 'Expressions'
   1517 
   1518     def lines(self, term_width, style_changed):
   1519         out = []
   1520         for number, expression in sorted(self.table.items()):
   1521             try:
   1522                 value = format_value(gdb.parse_and_eval(expression))
   1523             except gdb.error as e:
   1524                 value = ansi(e, R.style_error)
   1525             number = ansi(number, R.style_selected_2)
   1526             expression = ansi(expression, R.style_low)
   1527             out.append('[{}] {} = {}'.format(number, expression, value))
   1528         return out
   1529 
   1530     def watch(self, arg):
   1531         if arg:
   1532             self.table[self.number] = arg
   1533             self.number += 1
   1534         else:
   1535             raise Exception('Specify an expression')
   1536 
   1537     def unwatch(self, arg):
   1538         if arg:
   1539             try:
   1540                 del self.table[int(arg)]
   1541             except:
   1542                 raise Exception('Expression not watched')
   1543         else:
   1544             raise Exception('Specify an identifier')
   1545 
   1546     def clear(self, arg):
   1547         self.table.clear()
   1548 
   1549     def commands(self):
   1550         return {
   1551             'watch': {
   1552                 'action': self.watch,
   1553                 'doc': 'Watch an expression.',
   1554                 'complete': gdb.COMPLETE_EXPRESSION
   1555             },
   1556             'unwatch': {
   1557                 'action': self.unwatch,
   1558                 'doc': 'Stop watching an expression by id.',
   1559                 'complete': gdb.COMPLETE_EXPRESSION
   1560             },
   1561             'clear': {
   1562                 'action': self.clear,
   1563                 'doc': 'Clear all the watched expressions.'
   1564             }
   1565         }
   1566 
   1567 # XXX traceback line numbers in this Python block must be increased by 1
   1568 end
   1569 
   1570 # Better GDB defaults ----------------------------------------------------------
   1571 
   1572 set history save
   1573 set verbose off
   1574 set print pretty on
   1575 set print array off
   1576 set print array-indexes on
   1577 set python print-stack full
   1578 
   1579 # Start ------------------------------------------------------------------------
   1580 
   1581 python Dashboard.start()
   1582 
   1583 # ------------------------------------------------------------------------------
   1584 # Copyright (c) 2015-2017 Andrea Cardaci <cyrus.and@gmail.com>
   1585 #
   1586 # Permission is hereby granted, free of charge, to any person obtaining a copy
   1587 # of this software and associated documentation files (the "Software"), to deal
   1588 # in the Software without restriction, including without limitation the rights
   1589 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   1590 # copies of the Software, and to permit persons to whom the Software is
   1591 # furnished to do so, subject to the following conditions:
   1592 #
   1593 # The above copyright notice and this permission notice shall be included in all
   1594 # copies or substantial portions of the Software.
   1595 #
   1596 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   1597 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   1598 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   1599 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   1600 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   1601 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
   1602 # SOFTWARE.
   1603 # ------------------------------------------------------------------------------
   1604 # vim: filetype=python
   1605 # Local Variables:
   1606 # mode: python
   1607 # End: