2383 lines
		
	
	
		
			92 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			2383 lines
		
	
	
		
			92 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
python
 | 
						|
 | 
						|
# GDB dashboard - Modular visual interface for GDB in Python.
 | 
						|
#
 | 
						|
# https://github.com/cyrus-and/gdb-dashboard
 | 
						|
 | 
						|
# License ----------------------------------------------------------------------
 | 
						|
 | 
						|
# Copyright (c) 2015-2023 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.
 | 
						|
 | 
						|
# Imports ----------------------------------------------------------------------
 | 
						|
 | 
						|
import ast
 | 
						|
import io
 | 
						|
import itertools
 | 
						|
import math
 | 
						|
import os
 | 
						|
import re
 | 
						|
import struct
 | 
						|
import traceback
 | 
						|
 | 
						|
# 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 *
 | 
						|
    python for style in get_all_styles(): print(style)''',
 | 
						|
                'default': 'monokai'
 | 
						|
            },
 | 
						|
            'discard_scrollback': {
 | 
						|
                'doc': '''Discard the scrollback buffer at each redraw.
 | 
						|
 | 
						|
This makes scrolling less confusing by discarding the previously printed
 | 
						|
dashboards but only works with certain terminals.''',
 | 
						|
                'default': True,
 | 
						|
                'type': bool
 | 
						|
            },
 | 
						|
            # values formatting
 | 
						|
            'compact_values': {
 | 
						|
                'doc': 'Display complex objects in a single line.',
 | 
						|
                'default': True,
 | 
						|
                'type': bool
 | 
						|
            },
 | 
						|
            'max_value_length': {
 | 
						|
                'doc': 'Maximum length of displayed values before truncation.',
 | 
						|
                'default': 100,
 | 
						|
                'type': int
 | 
						|
            },
 | 
						|
            'value_truncation_string': {
 | 
						|
                'doc': 'String to use to mark value truncation.',
 | 
						|
                'default': '…',
 | 
						|
            },
 | 
						|
            'dereference': {
 | 
						|
                'doc': 'Annotate pointers with the pointed value.',
 | 
						|
                'default': True,
 | 
						|
                'type': bool
 | 
						|
            },
 | 
						|
            # prompt
 | 
						|
            'prompt': {
 | 
						|
                'doc': '''GDB prompt.
 | 
						|
 | 
						|
This value is used as a Python format string where `{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': '''Define the value of `{status}` when the target program is running.
 | 
						|
 | 
						|
See the `prompt` attribute. This value is used as a Python format string where
 | 
						|
`{pid}` is expanded with the process identifier of the target program.''',
 | 
						|
                'default': r'\[\e[1;35m\]>>>\[\e[0m\]'
 | 
						|
            },
 | 
						|
            'prompt_not_running': {
 | 
						|
                'doc': '''Define the value of `{status}` when the target program is running.
 | 
						|
 | 
						|
See the `prompt` attribute. This value is used as a Python format string.''',
 | 
						|
                'default': r'\[\e[90m\]>>>\[\e[0m\]'
 | 
						|
            },
 | 
						|
            # divider
 | 
						|
            'omit_divider': {
 | 
						|
                'doc': 'Omit the divider in external outputs when only one module is displayed.',
 | 
						|
                'default': False,
 | 
						|
                'type': bool
 | 
						|
            },
 | 
						|
            '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': '90'
 | 
						|
            },
 | 
						|
            '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': '1;37'
 | 
						|
            },
 | 
						|
            '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': '90'
 | 
						|
            },
 | 
						|
            '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': '90'
 | 
						|
            },
 | 
						|
            'style_high': {
 | 
						|
                'default': '1;37'
 | 
						|
            },
 | 
						|
            'style_error': {
 | 
						|
                'default': '31'
 | 
						|
            },
 | 
						|
            'style_critical': {
 | 
						|
                'default': '0;41'
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
# Common -----------------------------------------------------------------------
 | 
						|
 | 
						|
class Beautifier():
 | 
						|
 | 
						|
    def __init__(self, hint, tab_size=4):
 | 
						|
        self.tab_spaces = ' ' * tab_size if tab_size else None
 | 
						|
        self.active = False
 | 
						|
        if not R.ansi or not R.syntax_highlighting:
 | 
						|
            return
 | 
						|
        # attempt to set up Pygments
 | 
						|
        try:
 | 
						|
            import pygments
 | 
						|
            from pygments.lexers import GasLexer, NasmLexer
 | 
						|
            from pygments.formatters import Terminal256Formatter
 | 
						|
            if hint == 'att':
 | 
						|
                self.lexer = GasLexer()
 | 
						|
            elif hint == 'intel':
 | 
						|
                self.lexer = NasmLexer()
 | 
						|
            else:
 | 
						|
                from pygments.lexers import get_lexer_for_filename
 | 
						|
                self.lexer = get_lexer_for_filename(hint, stripnl=False)
 | 
						|
            self.formatter = Terminal256Formatter(style=R.syntax_highlighting)
 | 
						|
            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 if requested
 | 
						|
        if self.tab_spaces:
 | 
						|
            source = source.replace('\t', self.tab_spaces)
 | 
						|
        if self.active:
 | 
						|
            import pygments
 | 
						|
            source = pygments.highlight(source, self.lexer, self.formatter)
 | 
						|
        return source.rstrip('\n')
 | 
						|
 | 
						|
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')
 | 
						|
    except gdb.error as e:
 | 
						|
        value_string = ansi(e, R.style_error)
 | 
						|
    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, compact=None):
 | 
						|
    # 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:
 | 
						|
            value = value.referenced_value()
 | 
						|
        except gdb.error as e:
 | 
						|
            return ansi(e, R.style_error)
 | 
						|
    # format the value
 | 
						|
    out = to_string(value)
 | 
						|
    # dereference up to the actual value if requested
 | 
						|
    if R.dereference and value.type.code == gdb.TYPE_CODE_PTR:
 | 
						|
        while value.type.code == gdb.TYPE_CODE_PTR:
 | 
						|
            try:
 | 
						|
                value = value.dereference()
 | 
						|
            except gdb.error as e:
 | 
						|
                break
 | 
						|
        else:
 | 
						|
            formatted = to_string(value)
 | 
						|
            out += '{} {}'.format(ansi(':', R.style_low), formatted)
 | 
						|
    # compact the value
 | 
						|
    if compact is not None and compact or R.compact_values:
 | 
						|
        out = re.sub(r'$\s*', '', out, flags=re.MULTILINE)
 | 
						|
    # truncate the value
 | 
						|
    if R.max_value_length > 0 and len(out) > R.max_value_length:
 | 
						|
        out = out[0:R.max_value_length] + ansi(R.value_truncation_string, R.style_critical)
 | 
						|
    return out
 | 
						|
 | 
						|
# XXX parsing the output of `info breakpoints` is apparently the best option
 | 
						|
# right now, see: https://sourceware.org/bugzilla/show_bug.cgi?id=18385
 | 
						|
# XXX GDB version 7.11 (quire recent) does not have the pending field, so
 | 
						|
# fall back to the parsed information
 | 
						|
def fetch_breakpoints(watchpoints=False, pending=False):
 | 
						|
    # fetch breakpoints addresses
 | 
						|
    parsed_breakpoints = dict()
 | 
						|
    catch_what_regex = re.compile(r'([^,]+".*")?[^,]*')
 | 
						|
    for line in run('info breakpoints').split('\n'):
 | 
						|
        # just keep numbered lines
 | 
						|
        if not line or not line[0].isdigit():
 | 
						|
            continue
 | 
						|
        # extract breakpoint number, address and pending status
 | 
						|
        fields = line.split()
 | 
						|
        number = int(fields[0].split('.')[0])
 | 
						|
        try:
 | 
						|
            if len(fields) >= 5 and fields[1] == 'breakpoint':
 | 
						|
                # multiple breakpoints have no address yet
 | 
						|
                is_pending = fields[4] == '<PENDING>'
 | 
						|
                is_multiple = fields[4] == '<MULTIPLE>'
 | 
						|
                address = None if is_multiple or is_pending else int(fields[4], 16)
 | 
						|
                is_enabled = fields[3] == 'y'
 | 
						|
                address_info = address, is_enabled
 | 
						|
                parsed_breakpoints[number] = [address_info], is_pending, ''
 | 
						|
            elif len(fields) >= 5 and fields[1] == 'catchpoint':
 | 
						|
                # only take before comma, but ignore commas in quotes
 | 
						|
                what = catch_what_regex.search(' '.join(fields[4:]))[0].strip()
 | 
						|
                parsed_breakpoints[number] = [], False, what
 | 
						|
            elif len(fields) >= 3 and number in parsed_breakpoints:
 | 
						|
                # add this address to the list of multiple locations
 | 
						|
                address = int(fields[2], 16)
 | 
						|
                is_enabled = fields[1] == 'y'
 | 
						|
                address_info = address, is_enabled
 | 
						|
                parsed_breakpoints[number][0].append(address_info)
 | 
						|
            else:
 | 
						|
                # watchpoints
 | 
						|
                parsed_breakpoints[number] = [], False, ''
 | 
						|
        except ValueError:
 | 
						|
            pass
 | 
						|
    # fetch breakpoints from the API and complement with address and source
 | 
						|
    # information
 | 
						|
    breakpoints = []
 | 
						|
    # XXX in older versions gdb.breakpoints() returns None
 | 
						|
    for gdb_breakpoint in gdb.breakpoints() or []:
 | 
						|
        # skip internal breakpoints
 | 
						|
        if gdb_breakpoint.number < 0:
 | 
						|
            continue
 | 
						|
        addresses, is_pending, what = parsed_breakpoints[gdb_breakpoint.number]
 | 
						|
        is_pending = getattr(gdb_breakpoint, 'pending', is_pending)
 | 
						|
        if not pending and is_pending:
 | 
						|
            continue
 | 
						|
        if not watchpoints and gdb_breakpoint.type != gdb.BP_BREAKPOINT:
 | 
						|
            continue
 | 
						|
        # add useful fields to the object
 | 
						|
        breakpoint = dict()
 | 
						|
        breakpoint['number'] = gdb_breakpoint.number
 | 
						|
        breakpoint['type'] = gdb_breakpoint.type
 | 
						|
        breakpoint['enabled'] = gdb_breakpoint.enabled
 | 
						|
        breakpoint['location'] = gdb_breakpoint.location
 | 
						|
        breakpoint['expression'] = gdb_breakpoint.expression
 | 
						|
        breakpoint['condition'] = gdb_breakpoint.condition
 | 
						|
        breakpoint['temporary'] = gdb_breakpoint.temporary
 | 
						|
        breakpoint['hit_count'] = gdb_breakpoint.hit_count
 | 
						|
        breakpoint['pending'] = is_pending
 | 
						|
        breakpoint['what'] = what
 | 
						|
        # add addresses and source information
 | 
						|
        breakpoint['addresses'] = []
 | 
						|
        for address, is_enabled in addresses:
 | 
						|
            if address:
 | 
						|
                sal = gdb.find_pc_line(address)
 | 
						|
            breakpoint['addresses'].append({
 | 
						|
                'address': address,
 | 
						|
                'enabled': is_enabled,
 | 
						|
                'file_name': sal.symtab.filename if address and sal.symtab else None,
 | 
						|
                'file_line': sal.line if address else None
 | 
						|
            })
 | 
						|
        breakpoints.append(breakpoint)
 | 
						|
    return breakpoints
 | 
						|
 | 
						|
# Dashboard --------------------------------------------------------------------
 | 
						|
 | 
						|
class Dashboard(gdb.Command):
 | 
						|
    '''Redisplay the dashboard.'''
 | 
						|
 | 
						|
    def __init__(self):
 | 
						|
        gdb.Command.__init__(self, 'dashboard', gdb.COMMAND_USER, gdb.COMPLETE_NONE, True)
 | 
						|
        # setup subcommands
 | 
						|
        Dashboard.ConfigurationCommand(self)
 | 
						|
        Dashboard.OutputCommand(self)
 | 
						|
        Dashboard.EnabledCommand(self)
 | 
						|
        Dashboard.LayoutCommand(self)
 | 
						|
        # setup style commands
 | 
						|
        Dashboard.StyleCommand(self, 'dashboard', R, R.attributes())
 | 
						|
        # main terminal
 | 
						|
        self.output = None
 | 
						|
        # used to inhibit redisplays during init parsing
 | 
						|
        self.inhibited = None
 | 
						|
        # enabled by default
 | 
						|
        self.enabled = None
 | 
						|
        self.enable()
 | 
						|
 | 
						|
    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 ...)
 | 
						|
        # or there are no modules to display in the main terminal
 | 
						|
        enabled_modules = list(filter(lambda m: not m.output and m.enabled, self.modules))
 | 
						|
        if self.is_running() and not self.output and len(enabled_modules) > 0:
 | 
						|
            width, _ = Dashboard.get_term_size()
 | 
						|
            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)
 | 
						|
        # reset the terminal status
 | 
						|
        for output in outputs:
 | 
						|
            try:
 | 
						|
                with open(output, 'w') as fs:
 | 
						|
                    fs.write(Dashboard.reset_terminal())
 | 
						|
            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 not self.inhibited:
 | 
						|
            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)
 | 
						|
        # process each display info
 | 
						|
        for output, instances in display_map.items():
 | 
						|
            try:
 | 
						|
                buf = ''
 | 
						|
                # use GDB stream by default
 | 
						|
                fs = None
 | 
						|
                if output:
 | 
						|
                    fs = open(output, 'w')
 | 
						|
                    fd = fs.fileno()
 | 
						|
                    fs.write(Dashboard.setup_terminal())
 | 
						|
                else:
 | 
						|
                    fs = gdb
 | 
						|
                    fd = 1  # stdout
 | 
						|
                # get the terminal size (default main terminal if either the
 | 
						|
                # output is not a file)
 | 
						|
                try:
 | 
						|
                    width, height = Dashboard.get_term_size(fd)
 | 
						|
                except:
 | 
						|
                    width, height = Dashboard.get_term_size()
 | 
						|
                # clear the "screen" if requested for the main terminal,
 | 
						|
                # auxiliary terminals are always cleared
 | 
						|
                if fs is not gdb or clear_screen:
 | 
						|
                    buf += Dashboard.clear_screen()
 | 
						|
                # show message if all the modules in this output are disabled
 | 
						|
                if not any(instances):
 | 
						|
                    # skip the main terminal
 | 
						|
                    if fs is gdb:
 | 
						|
                        continue
 | 
						|
                    # write the error message
 | 
						|
                    buf += divider(width, 'Warning', True)
 | 
						|
                    buf += '\n'
 | 
						|
                    if self.modules:
 | 
						|
                        buf += 'No module to display (see `dashboard -layout`)'
 | 
						|
                    else:
 | 
						|
                        buf += 'No module loaded'
 | 
						|
                    buf += '\n'
 | 
						|
                    fs.write(buf)
 | 
						|
                    continue
 | 
						|
                # process all the modules for that output
 | 
						|
                for n, instance in enumerate(instances, 1):
 | 
						|
                    # skip disabled modules
 | 
						|
                    if not instance:
 | 
						|
                        continue
 | 
						|
                    try:
 | 
						|
                        # ask the module to generate the content
 | 
						|
                        lines = instance.lines(width, height, style_changed)
 | 
						|
                    except Exception as e:
 | 
						|
                        # allow to continue on exceptions in modules
 | 
						|
                        stacktrace = traceback.format_exc().strip()
 | 
						|
                        lines = [ansi(stacktrace, R.style_error)]
 | 
						|
                    # create the divider if needed
 | 
						|
                    div = []
 | 
						|
                    if not R.omit_divider or len(instances) > 1 or fs is gdb:
 | 
						|
                        div = [divider(width, instance.label(), True, lines)]
 | 
						|
                    # write the data
 | 
						|
                    buf += '\n'.join(div + lines)
 | 
						|
                    # write the newline for all but last unless main terminal
 | 
						|
                    if n != len(instances) or fs is gdb:
 | 
						|
                        buf += '\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:
 | 
						|
                    buf += divider(width, primary=True)
 | 
						|
                    buf += '\n'
 | 
						|
                fs.write(buf)
 | 
						|
            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():
 | 
						|
        # save the instance for customization convenience
 | 
						|
        global dashboard
 | 
						|
        # initialize the dashboard
 | 
						|
        dashboard = Dashboard()
 | 
						|
        Dashboard.set_custom_prompt(dashboard)
 | 
						|
        # parse Python inits, load modules then parse GDB inits
 | 
						|
        dashboard.inhibited = True
 | 
						|
        Dashboard.parse_inits(True)
 | 
						|
        modules = Dashboard.get_modules()
 | 
						|
        dashboard.load_modules(modules)
 | 
						|
        Dashboard.parse_inits(False)
 | 
						|
        dashboard.inhibited = False
 | 
						|
        # GDB overrides
 | 
						|
        run('set pagination off')
 | 
						|
        # display if possible (program running and not explicitly disabled by
 | 
						|
        # some configuration file)
 | 
						|
        if dashboard.enabled:
 | 
						|
            dashboard.redisplay()
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def get_term_size(fd=1):  # defaults to the main terminal
 | 
						|
        try:
 | 
						|
            if sys.platform == 'win32':
 | 
						|
                import curses
 | 
						|
                # XXX always neglects the fd parameter
 | 
						|
                height, width = curses.initscr().getmaxyx()
 | 
						|
                curses.endwin()
 | 
						|
                return int(width), int(height)
 | 
						|
            else:
 | 
						|
                import termios
 | 
						|
                import fcntl
 | 
						|
                # first 2 shorts (4 byte) of struct winsize
 | 
						|
                raw = fcntl.ioctl(fd, termios.TIOCGWINSZ, ' ' * 4)
 | 
						|
                height, width = struct.unpack('hh', raw)
 | 
						|
                return int(width), int(height)
 | 
						|
        except (ImportError, OSError):
 | 
						|
            # this happens when no curses library is found on windows or when
 | 
						|
            # the terminal is not properly configured
 | 
						|
            return 80, 24  # hardcoded fallback value
 | 
						|
 | 
						|
    @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):
 | 
						|
        # paths where the .gdbinit.d directory might be
 | 
						|
        search_paths = [
 | 
						|
            '/etc/gdb-dashboard',
 | 
						|
            '{}/gdb-dashboard'.format(os.getenv('XDG_CONFIG_HOME', '~/.config')),
 | 
						|
            '~/Library/Preferences/gdb-dashboard',
 | 
						|
            '~/.gdbinit.d'
 | 
						|
        ]
 | 
						|
        # expand the tilde and walk the paths
 | 
						|
        inits_dirs = (os.walk(os.path.expanduser(path)) for path in search_paths)
 | 
						|
        # process all the init files in order
 | 
						|
        for root, dirs, files in itertools.chain.from_iterable(inits_dirs):
 | 
						|
            dirs.sort()
 | 
						|
            # skipping dotfiles
 | 
						|
            for init in sorted(file for file in files if not file.startswith('.')):
 | 
						|
                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):
 | 
						|
        if callable(complete):
 | 
						|
            Class = type('', (gdb.Command,), {
 | 
						|
                '__doc__': doc,
 | 
						|
                'invoke': invoke,
 | 
						|
                'complete': complete
 | 
						|
            })
 | 
						|
            Class(name, gdb.COMMAND_USER, prefix=is_prefix)
 | 
						|
        else:
 | 
						|
            Class = type('', (gdb.Command,), {
 | 
						|
                '__doc__': doc,
 | 
						|
                'invoke': invoke
 | 
						|
            })
 | 
						|
            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
 | 
						|
        # (optionally also clear the scrollback buffer if supported by the
 | 
						|
        # terminal)
 | 
						|
        return '\x1b[H\x1b[2J' + ('\x1b[3J' if R.discard_scrollback else '')
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def setup_terminal():
 | 
						|
        # ANSI: enable alternative screen buffer and hide cursor
 | 
						|
        return '\x1b[?1049h\x1b[?25l'
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def reset_terminal():
 | 
						|
        # ANSI: disable alternative screen buffer and show cursor
 | 
						|
        return '\x1b[?1049l\x1b[?25h'
 | 
						|
 | 
						|
# 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, with no arguments toggles its visibility.'.format(self.name)
 | 
						|
            doc = '{}\n\n{}'.format(doc_brief, 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):
 | 
						|
            Dashboard.StyleCommand(dashboard, self.prefix, self.instance, self.instance.attributes())
 | 
						|
 | 
						|
        def add_subcommands(self, dashboard):
 | 
						|
            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 -----------------------------------------------------------------
 | 
						|
 | 
						|
    # handler for the `dashboard` command itself
 | 
						|
    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 or save the dashboard configuration.
 | 
						|
 | 
						|
With an optional argument the configuration will be written to the specified
 | 
						|
file.
 | 
						|
 | 
						|
This command allows to configure the dashboard live then make the changes
 | 
						|
permanent, for example:
 | 
						|
 | 
						|
    dashboard -configuration ~/.gdbinit.d/init
 | 
						|
 | 
						|
At startup the `~/.gdbinit.d/` directory tree is walked and files are evaluated
 | 
						|
in alphabetical order but giving priority to Python files. This is where user
 | 
						|
configuration files must be placed.'''
 | 
						|
 | 
						|
        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 the whole dashboard or single 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 geometry will be used, otherwise it falls back to the geometry of the main
 | 
						|
GDB terminal.
 | 
						|
 | 
						|
When invoked without argument on the dashboard, the output/messages and modules
 | 
						|
which do not specify an output themselves will be printed on standard output
 | 
						|
(default).
 | 
						|
 | 
						|
When invoked without argument on a module, it will be printed where the
 | 
						|
dashboard will be printed.
 | 
						|
 | 
						|
An overview of all the outputs can be obtained with the `dashboard -layout`
 | 
						|
command.'''
 | 
						|
 | 
						|
        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)
 | 
						|
            # reset the terminal status
 | 
						|
            if self.obj.output:
 | 
						|
                try:
 | 
						|
                    with open(self.obj.output, 'w') as fs:
 | 
						|
                        fs.write(Dashboard.reset_terminal())
 | 
						|
                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.
 | 
						|
 | 
						|
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.
 | 
						|
 | 
						|
Passing `!` as a single argument resets the dashboard original layout.'''
 | 
						|
 | 
						|
        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:
 | 
						|
                # apply the layout
 | 
						|
                if directives == ['!']:
 | 
						|
                    self.reset()
 | 
						|
                else:
 | 
						|
                    if not self.layout(directives):
 | 
						|
                        return  # in case of errors
 | 
						|
                # redisplay or otherwise notify
 | 
						|
                if from_tty:
 | 
						|
                    if self.dashboard.is_running():
 | 
						|
                        self.dashboard.redisplay()
 | 
						|
                    else:
 | 
						|
                        self.show()
 | 
						|
            else:
 | 
						|
                self.show()
 | 
						|
 | 
						|
        def reset(self):
 | 
						|
            modules = self.dashboard.modules
 | 
						|
            modules.sort(key=lambda module: module.name)
 | 
						|
            for module in modules:
 | 
						|
                module.enabled = True
 | 
						|
 | 
						|
        def show(self):
 | 
						|
            global_str = 'Dashboard'
 | 
						|
            default = '(default TTY)'
 | 
						|
            max_name_len = max(len(module.name) for module in self.dashboard.modules)
 | 
						|
            max_name_len = max(max_name_len, len(global_str))
 | 
						|
            fmt = '{{}}{{:{}s}}{{}}'.format(max_name_len + 2)
 | 
						|
            print((fmt + '\n').format(' ', global_str, self.dashboard.output or default))
 | 
						|
            for module in self.dashboard.modules:
 | 
						|
                mark = ' ' if module.enabled else '!'
 | 
						|
                style = R.style_high if module.enabled else R.style_low
 | 
						|
                line = fmt.format(mark, module.name, module.output or default)
 | 
						|
                print(ansi(line, style))
 | 
						|
 | 
						|
        def layout(self, directives):
 | 
						|
            modules = self.dashboard.modules
 | 
						|
            # parse and check directives
 | 
						|
            parsed_directives = []
 | 
						|
            selected_modules = set()
 | 
						|
            for directive in directives:
 | 
						|
                enabled = (directive[0] != '!')
 | 
						|
                name = directive[not enabled:]
 | 
						|
                if name in selected_modules:
 | 
						|
                    Dashboard.err('Module "{}" already set'.format(name))
 | 
						|
                    return False
 | 
						|
                if next((False for module in modules if module.name == name), True):
 | 
						|
                    Dashboard.err('Cannot find module "{}"'.format(name))
 | 
						|
                    return False
 | 
						|
                parsed_directives.append((name, enabled))
 | 
						|
                selected_modules.add(name)
 | 
						|
            # reset visibility
 | 
						|
            for module in modules:
 | 
						|
                module.enabled = False
 | 
						|
            # move and enable the selected modules on top
 | 
						|
            last = 0
 | 
						|
            for name, enabled in parsed_directives:
 | 
						|
                todo = enumerate(modules[last:], start=last)
 | 
						|
                index = next(index for index, module in todo if name == module.name)
 | 
						|
                modules[index].enabled = enabled
 | 
						|
                modules.insert(last, modules.pop(index))
 | 
						|
                last += 1
 | 
						|
            return True
 | 
						|
 | 
						|
        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.
 | 
						|
 | 
						|
When only the name is specified show the current value.
 | 
						|
 | 
						|
With name and value set the stylable attribute. Values are parsed as Python
 | 
						|
literals and converted to the proper type. '''
 | 
						|
 | 
						|
        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():
 | 
						|
        '''Base class for GDB dashboard modules.
 | 
						|
 | 
						|
        Modules are instantiated once at initialization time and kept during the
 | 
						|
        whole the GDB session.
 | 
						|
 | 
						|
        The name of a module is automatically obtained by the class name.
 | 
						|
 | 
						|
        Optionally, a module may include a description which will appear in the
 | 
						|
        GDB help system by specifying a Python docstring for the class. By
 | 
						|
        convention the first line should contain a brief description.'''
 | 
						|
 | 
						|
        def label(self):
 | 
						|
            '''Return the module label which will appear in the divider.'''
 | 
						|
            pass
 | 
						|
 | 
						|
        def lines(self, term_width, term_height, style_changed):
 | 
						|
            '''Return a list of strings which will form the module content.
 | 
						|
 | 
						|
            When a module is temporarily unable to produce its content, it
 | 
						|
            should return an empty list; its divider will then use the styles
 | 
						|
            with the "off" qualifier.
 | 
						|
 | 
						|
            term_width and term_height are the dimension of the terminal where
 | 
						|
            this module will be displayed. If `style_changed` is `True` then
 | 
						|
            some attributes have changed since the last time so the
 | 
						|
            implementation may want to update its status.'''
 | 
						|
            pass
 | 
						|
 | 
						|
        def attributes(self):
 | 
						|
            '''Return the dictionary of available attributes.
 | 
						|
 | 
						|
            The key is the attribute name and the value is another dictionary
 | 
						|
            with items:
 | 
						|
 | 
						|
            - `default` is the initial value for this attribute;
 | 
						|
 | 
						|
            - `doc` is the optional documentation of this attribute which will
 | 
						|
              appear in the GDB help system;
 | 
						|
 | 
						|
            - `name` is the name of the attribute of the Python object (defaults
 | 
						|
              to the key value);
 | 
						|
 | 
						|
            - `type` is the Python type of this attribute defaulting to the
 | 
						|
              `str` type, it is used to coerce the value passed as an argument
 | 
						|
              to the proper type, or raise an exception;
 | 
						|
 | 
						|
            - `check` is an optional control callback which accept the coerced
 | 
						|
              value and returns `True` if the value satisfies the constraint and
 | 
						|
              `False` otherwise.
 | 
						|
 | 
						|
            Those attributes can be accessed from the implementation using
 | 
						|
            instance variables named `name`.'''
 | 
						|
            return {}
 | 
						|
 | 
						|
        def commands(self):
 | 
						|
            '''Return the dictionary of available commands.
 | 
						|
 | 
						|
            The key is the attribute name and the value is another dictionary
 | 
						|
            with items:
 | 
						|
 | 
						|
            - `action` is the callback to be executed which accepts the raw
 | 
						|
              input string from the GDB prompt, exceptions in these functions
 | 
						|
              will be shown automatically to the user;
 | 
						|
 | 
						|
            - `doc` is the documentation of this command which will appear in
 | 
						|
              the GDB help system;
 | 
						|
 | 
						|
            - `completion` is the optional completion policy, one of the
 | 
						|
              `gdb.COMPLETE_*` constants defined in the GDB reference manual
 | 
						|
              (https://sourceware.org/gdb/onlinedocs/gdb/Commands-In-Python.html).'''
 | 
						|
            return {}
 | 
						|
 | 
						|
# 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
 | 
						|
        self.offset = 0
 | 
						|
 | 
						|
    def label(self):
 | 
						|
        label = 'Source'
 | 
						|
        if self.show_path and self.file_name:
 | 
						|
            label += ': {}'.format(self.file_name)
 | 
						|
        return label
 | 
						|
 | 
						|
    def lines(self, term_width, term_height, 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:
 | 
						|
            self.file_name = None
 | 
						|
            return []
 | 
						|
        # try to lookup the source file
 | 
						|
        candidates = [
 | 
						|
            sal.symtab.fullname(),
 | 
						|
            sal.symtab.filename,
 | 
						|
            # XXX GDB also uses absolute filename but it is harder to implement
 | 
						|
            # properly and IMHO useless
 | 
						|
            os.path.basename(sal.symtab.filename)]
 | 
						|
        for candidate in candidates:
 | 
						|
            file_name = candidate
 | 
						|
            ts = None
 | 
						|
            try:
 | 
						|
                ts = os.path.getmtime(file_name)
 | 
						|
                break
 | 
						|
            except:
 | 
						|
                # try another or delay error check to open()
 | 
						|
                continue
 | 
						|
        # style changed, different file name or file modified in the meanwhile
 | 
						|
        if style_changed or file_name != self.file_name or ts and ts > self.ts:
 | 
						|
            try:
 | 
						|
                # reload the source file if changed
 | 
						|
                with io.open(file_name, errors='replace') as source_file:
 | 
						|
                    highlighter = Beautifier(file_name, self.tab_size)
 | 
						|
                    self.highlighted = highlighter.active
 | 
						|
                    source = highlighter.process(source_file.read())
 | 
						|
                    self.source_lines = source.split('\n')
 | 
						|
                # store file name and timestamp only if success to have
 | 
						|
                # persistent errors
 | 
						|
                self.file_name = file_name
 | 
						|
                self.ts = ts
 | 
						|
            except IOError as e:
 | 
						|
                msg = 'Cannot display "{}"'.format(file_name)
 | 
						|
                return [ansi(msg, R.style_error)]
 | 
						|
        # compute the line range
 | 
						|
        height = self.height or (term_height - 1)
 | 
						|
        start = current_line - 1 - int(height / 2) + self.offset
 | 
						|
        end = start + height
 | 
						|
        # extra at start
 | 
						|
        extra_start = 0
 | 
						|
        if start < 0:
 | 
						|
            extra_start = min(-start, height)
 | 
						|
            start = 0
 | 
						|
        # extra at end
 | 
						|
        extra_end = 0
 | 
						|
        if end > len(self.source_lines):
 | 
						|
            extra_end = min(end - len(self.source_lines), height)
 | 
						|
            end = len(self.source_lines)
 | 
						|
        else:
 | 
						|
            end = max(end, 0)
 | 
						|
        # return the source code listing
 | 
						|
        breakpoints = fetch_breakpoints()
 | 
						|
        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 and not self.highlight_line:
 | 
						|
                        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) + '  {}'
 | 
						|
            # check for breakpoint presence
 | 
						|
            enabled = None
 | 
						|
            for breakpoint in breakpoints:
 | 
						|
                addresses = breakpoint['addresses']
 | 
						|
                is_root_enabled = addresses[0]['enabled']
 | 
						|
                for address in addresses:
 | 
						|
                    # note, despite the lookup path always use the relative
 | 
						|
                    # (sal.symtab.filename) file name to match source files with
 | 
						|
                    # breakpoints
 | 
						|
                    if address['file_line'] == number and address['file_name'] == sal.symtab.filename:
 | 
						|
                        enabled = enabled or (address['enabled'] and is_root_enabled)
 | 
						|
            if enabled is None:
 | 
						|
                breakpoint = ' '
 | 
						|
            else:
 | 
						|
                breakpoint = ansi('!', R.style_critical) if enabled else ansi('-', R.style_low)
 | 
						|
            out.append(line_format.format(breakpoint, number, line.rstrip('\n')))
 | 
						|
        # return the output along with scroll indicators
 | 
						|
        if len(out) <= height:
 | 
						|
            extra = [ansi('~', R.style_low)]
 | 
						|
            return extra_start * extra + out + extra_end * extra
 | 
						|
        else:
 | 
						|
            return out
 | 
						|
 | 
						|
    def commands(self):
 | 
						|
        return {
 | 
						|
            'scroll': {
 | 
						|
                'action': self.scroll,
 | 
						|
                'doc': 'Scroll by relative steps or reset if invoked without argument.'
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
    def attributes(self):
 | 
						|
        return {
 | 
						|
            'height': {
 | 
						|
                'doc': '''Height of the module.
 | 
						|
 | 
						|
A value of 0 uses the whole height.''',
 | 
						|
                'default': 10,
 | 
						|
                '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
 | 
						|
            },
 | 
						|
            'path': {
 | 
						|
                'doc': 'Path visibility flag in the module label.',
 | 
						|
                'default': False,
 | 
						|
                'name': 'show_path',
 | 
						|
                'type': bool
 | 
						|
            },
 | 
						|
            'highlight-line': {
 | 
						|
                'doc': 'Decide whether the whole current line should be highlighted.',
 | 
						|
                'default': False,
 | 
						|
                'name': 'highlight_line',
 | 
						|
                'type': bool
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
    def scroll(self, arg):
 | 
						|
        if arg:
 | 
						|
            self.offset += int(arg)
 | 
						|
        else:
 | 
						|
            self.offset = 0
 | 
						|
 | 
						|
class Assembly(Dashboard.Module):
 | 
						|
    '''Show the disassembled code surrounding the program counter.
 | 
						|
 | 
						|
The instructions constituting the current statement are marked, if available.'''
 | 
						|
 | 
						|
    def __init__(self):
 | 
						|
        self.offset = 0
 | 
						|
        self.cache_key = None
 | 
						|
        self.cache_asm = None
 | 
						|
 | 
						|
    def label(self):
 | 
						|
        return 'Assembly'
 | 
						|
 | 
						|
    def lines(self, term_width, term_height, style_changed):
 | 
						|
        # skip if the current thread is not stopped
 | 
						|
        if not gdb.selected_thread().is_stopped():
 | 
						|
            return []
 | 
						|
        # flush the cache if the style is changed
 | 
						|
        if style_changed:
 | 
						|
            self.cache_key = None
 | 
						|
        # prepare the highlighter
 | 
						|
        try:
 | 
						|
            flavor = gdb.parameter('disassembly-flavor')
 | 
						|
        except:
 | 
						|
            flavor = 'att'  # not always defined (see #36)
 | 
						|
        highlighter = Beautifier(flavor, tab_size=None)
 | 
						|
        # fetch the assembly code
 | 
						|
        line_info = None
 | 
						|
        frame = gdb.selected_frame()  # PC is here
 | 
						|
        height = self.height or (term_height - 1)
 | 
						|
        try:
 | 
						|
            # disassemble the current block
 | 
						|
            asm_start, asm_end = self.fetch_function_boundaries()
 | 
						|
            asm = self.fetch_asm(asm_start, asm_end, False, highlighter)
 | 
						|
            # find the location of the PC
 | 
						|
            pc_index = next(index for index, instr in enumerate(asm)
 | 
						|
                            if instr['addr'] == frame.pc())
 | 
						|
            # compute the instruction range
 | 
						|
            start = pc_index - int(height / 2) + self.offset
 | 
						|
            end = start + height
 | 
						|
            # extra at start
 | 
						|
            extra_start = 0
 | 
						|
            if start < 0:
 | 
						|
                extra_start = min(-start, height)
 | 
						|
                start = 0
 | 
						|
            # extra at end
 | 
						|
            extra_end = 0
 | 
						|
            if end > len(asm):
 | 
						|
                extra_end = min(end - len(asm), height)
 | 
						|
                end = len(asm)
 | 
						|
            else:
 | 
						|
                end = max(end, 0)
 | 
						|
            # fetch actual interval
 | 
						|
            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, RuntimeError, 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
 | 
						|
            try:
 | 
						|
                extra_start = 0
 | 
						|
                extra_end = 0
 | 
						|
                # allow to scroll down nevertheless
 | 
						|
                clamped_offset = min(self.offset, 0)
 | 
						|
                asm = self.fetch_asm(frame.pc(), height - clamped_offset, True, highlighter)
 | 
						|
                asm = asm[-clamped_offset:]
 | 
						|
            except gdb.error as e:
 | 
						|
                msg = '{}'.format(e)
 | 
						|
                return [ansi(msg, R.style_error)]
 | 
						|
        # fetch function start if available (e.g., not with @plt)
 | 
						|
        func_start = None
 | 
						|
        if self.show_function and frame.function():
 | 
						|
            func_start = to_unsigned(frame.function().value())
 | 
						|
        # compute the maximum offset size
 | 
						|
        if asm and 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
 | 
						|
        breakpoints = fetch_breakpoints()
 | 
						|
        max_length = max(instr['length'] for instr in asm) if asm else 0
 | 
						|
        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.function(), offset)
 | 
						|
                else:
 | 
						|
                    func_info = '?'
 | 
						|
            else:
 | 
						|
                func_info = ''
 | 
						|
            format_string = '{}{}{}{}{}{}'
 | 
						|
            indicator = '  '
 | 
						|
            text = ' ' + 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 or self.highlight_line:
 | 
						|
                    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 or self.highlight_line:
 | 
						|
                    text = ansi(text, R.style_selected_2)
 | 
						|
            else:
 | 
						|
                addr_str = ansi(addr_str, R.style_low)
 | 
						|
                func_info = ansi(func_info, R.style_low)
 | 
						|
            # check for breakpoint presence
 | 
						|
            enabled = None
 | 
						|
            for breakpoint in breakpoints:
 | 
						|
                addresses = breakpoint['addresses']
 | 
						|
                is_root_enabled = addresses[0]['enabled']
 | 
						|
                for address in addresses:
 | 
						|
                    if address['address'] == addr:
 | 
						|
                        enabled = enabled or (address['enabled'] and is_root_enabled)
 | 
						|
            if enabled is None:
 | 
						|
                breakpoint = ' '
 | 
						|
            else:
 | 
						|
                breakpoint = ansi('!', R.style_critical) if enabled else ansi('-', R.style_low)
 | 
						|
            out.append(format_string.format(breakpoint, addr_str, indicator, opcodes, func_info, text))
 | 
						|
        # return the output along with scroll indicators
 | 
						|
        if len(out) <= height:
 | 
						|
            extra = [ansi('~', R.style_low)]
 | 
						|
            return extra_start * extra + out + extra_end * extra
 | 
						|
        else:
 | 
						|
            return out
 | 
						|
 | 
						|
    def commands(self):
 | 
						|
        return {
 | 
						|
            'scroll': {
 | 
						|
                'action': self.scroll,
 | 
						|
                'doc': 'Scroll by relative steps or reset if invoked without argument.'
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
    def attributes(self):
 | 
						|
        return {
 | 
						|
            'height': {
 | 
						|
                'doc': '''Height of the module.
 | 
						|
 | 
						|
A value of 0 uses the whole height.''',
 | 
						|
                'default': 10,
 | 
						|
                '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
 | 
						|
            },
 | 
						|
            'highlight-line': {
 | 
						|
                'doc': 'Decide whether the whole current line should be highlighted.',
 | 
						|
                'default': False,
 | 
						|
                'name': 'highlight_line',
 | 
						|
                'type': bool
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
    def scroll(self, arg):
 | 
						|
        if arg:
 | 
						|
            self.offset += int(arg)
 | 
						|
        else:
 | 
						|
            self.offset = 0
 | 
						|
 | 
						|
    def fetch_function_boundaries(self):
 | 
						|
        frame = gdb.selected_frame()
 | 
						|
        # parse the output of the disassemble GDB command to find the function
 | 
						|
        # boundaries, this should handle cases in which a function spans
 | 
						|
        # multiple discontinuous blocks
 | 
						|
        disassemble = run('disassemble')
 | 
						|
        for block_start, block_end in re.findall(r'Address range 0x([0-9a-f]+) to 0x([0-9a-f]+):', disassemble):
 | 
						|
            block_start = int(block_start, 16)
 | 
						|
            block_end = int(block_end, 16)
 | 
						|
            if block_start <= frame.pc() < block_end:
 | 
						|
                return block_start, block_end - 1 # need to be inclusive
 | 
						|
        # if function information is available then try to obtain the
 | 
						|
        # boundaries by looking at the superblocks
 | 
						|
        block = frame.block()
 | 
						|
        if frame.function():
 | 
						|
            while block and (not block.function or block.function.name != frame.function().name):
 | 
						|
                block = block.superblock
 | 
						|
            block = block or frame.block()
 | 
						|
        return block.start, block.end - 1
 | 
						|
 | 
						|
    def fetch_asm(self, start, end_or_count, relative, highlighter):
 | 
						|
        # fetch asm from cache or disassemble
 | 
						|
        if self.cache_key == (start, end_or_count):
 | 
						|
            asm = self.cache_asm
 | 
						|
        else:
 | 
						|
            kwargs = {
 | 
						|
                'start_pc': start,
 | 
						|
                'count' if relative else 'end_pc': end_or_count
 | 
						|
            }
 | 
						|
            asm = gdb.selected_frame().architecture().disassemble(**kwargs)
 | 
						|
            self.cache_key = (start, end_or_count)
 | 
						|
            self.cache_asm = asm
 | 
						|
            # syntax highlight the cached entry
 | 
						|
            for instr in asm:
 | 
						|
                instr['asm'] = highlighter.process(instr['asm'])
 | 
						|
        return asm
 | 
						|
 | 
						|
class Variables(Dashboard.Module):
 | 
						|
    '''Show arguments and locals of the selected frame.'''
 | 
						|
 | 
						|
    def label(self):
 | 
						|
        return 'Variables'
 | 
						|
 | 
						|
    def lines(self, term_width, term_height, style_changed):
 | 
						|
        return Variables.format_frame(
 | 
						|
            gdb.selected_frame(), self.show_arguments, self.show_locals, self.compact, self.align, self.sort)
 | 
						|
 | 
						|
    def attributes(self):
 | 
						|
        return {
 | 
						|
            'arguments': {
 | 
						|
                'doc': 'Frame arguments visibility flag.',
 | 
						|
                'default': True,
 | 
						|
                'name': 'show_arguments',
 | 
						|
                'type': bool
 | 
						|
            },
 | 
						|
            'locals': {
 | 
						|
                'doc': 'Frame locals visibility flag.',
 | 
						|
                'default': True,
 | 
						|
                'name': 'show_locals',
 | 
						|
                'type': bool
 | 
						|
            },
 | 
						|
            'compact': {
 | 
						|
                'doc': 'Single-line display flag.',
 | 
						|
                'default': True,
 | 
						|
                'type': bool
 | 
						|
            },
 | 
						|
            'align': {
 | 
						|
                'doc': 'Align variables in column flag (only if not compact).',
 | 
						|
                'default': False,
 | 
						|
                'type': bool
 | 
						|
            },
 | 
						|
            'sort': {
 | 
						|
                'doc': 'Sort variables by name.',
 | 
						|
                'default': False,
 | 
						|
                'type': bool
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def format_frame(frame, show_arguments, show_locals, compact, align, sort):
 | 
						|
        out = []
 | 
						|
        # fetch frame arguments and locals
 | 
						|
        decorator = gdb.FrameDecorator.FrameDecorator(frame)
 | 
						|
        separator = ansi(', ', R.style_low)
 | 
						|
        if show_arguments:
 | 
						|
            def prefix(line):
 | 
						|
                return Stack.format_line('arg', line)
 | 
						|
            frame_args = decorator.frame_args()
 | 
						|
            args_lines = Variables.fetch(frame, frame_args, compact, align, sort)
 | 
						|
            if args_lines:
 | 
						|
                if compact:
 | 
						|
                    args_line = separator.join(args_lines)
 | 
						|
                    single_line = prefix(args_line)
 | 
						|
                    out.append(single_line)
 | 
						|
                else:
 | 
						|
                    out.extend(map(prefix, args_lines))
 | 
						|
        if show_locals:
 | 
						|
            def prefix(line):
 | 
						|
                return Stack.format_line('loc', line)
 | 
						|
            frame_locals = decorator.frame_locals()
 | 
						|
            locals_lines = Variables.fetch(frame, frame_locals, compact, align, sort)
 | 
						|
            if locals_lines:
 | 
						|
                if compact:
 | 
						|
                    locals_line = separator.join(locals_lines)
 | 
						|
                    single_line = prefix(locals_line)
 | 
						|
                    out.append(single_line)
 | 
						|
                else:
 | 
						|
                    out.extend(map(prefix, locals_lines))
 | 
						|
        return out
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def fetch(frame, data, compact, align, sort):
 | 
						|
        lines = []
 | 
						|
        name_width = 0
 | 
						|
        if align and not compact:
 | 
						|
            name_width = max(len(str(elem.sym)) for elem in data) if data else 0
 | 
						|
        for elem in data or []:
 | 
						|
            name = ansi(elem.sym, R.style_high) + ' ' * (name_width - len(str(elem.sym)))
 | 
						|
            equal = ansi('=', R.style_low)
 | 
						|
            value = format_value(elem.sym.value(frame), compact)
 | 
						|
            lines.append('{} {} {}'.format(name, equal, value))
 | 
						|
        if sort:
 | 
						|
            lines.sort()
 | 
						|
        return lines
 | 
						|
 | 
						|
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, term_height, style_changed):
 | 
						|
        # skip if the current thread is not stopped
 | 
						|
        if not gdb.selected_thread().is_stopped():
 | 
						|
            return []
 | 
						|
        # find the selected frame level (XXX Frame.level() is a recent addition)
 | 
						|
        start_level = 0
 | 
						|
        frame = gdb.newest_frame()
 | 
						|
        while frame:
 | 
						|
            if frame == gdb.selected_frame():
 | 
						|
                break
 | 
						|
            frame = frame.older()
 | 
						|
            start_level += 1
 | 
						|
        # gather the frames
 | 
						|
        more = False
 | 
						|
        frames = [gdb.selected_frame()]
 | 
						|
        going_down = True
 | 
						|
        while True:
 | 
						|
            # stack frames limit reached
 | 
						|
            if len(frames) == self.limit:
 | 
						|
                more = True
 | 
						|
                break
 | 
						|
            # zigzag the frames starting from the selected one
 | 
						|
            if going_down:
 | 
						|
                frame = frames[-1].older()
 | 
						|
                if frame:
 | 
						|
                    frames.append(frame)
 | 
						|
                else:
 | 
						|
                    frame = frames[0].newer()
 | 
						|
                    if frame:
 | 
						|
                        frames.insert(0, frame)
 | 
						|
                        start_level -= 1
 | 
						|
                    else:
 | 
						|
                        break
 | 
						|
            else:
 | 
						|
                frame = frames[0].newer()
 | 
						|
                if frame:
 | 
						|
                    frames.insert(0, frame)
 | 
						|
                    start_level -= 1
 | 
						|
                else:
 | 
						|
                    frame = frames[-1].older()
 | 
						|
                    if frame:
 | 
						|
                        frames.append(frame)
 | 
						|
                    else:
 | 
						|
                        break
 | 
						|
            # switch direction
 | 
						|
            going_down = not going_down
 | 
						|
        # format the output
 | 
						|
        lines = []
 | 
						|
        for number, frame in enumerate(frames, start=start_level):
 | 
						|
            selected = frame == gdb.selected_frame()
 | 
						|
            lines.extend(self.get_frame_lines(number, frame, selected))
 | 
						|
        # add the placeholder
 | 
						|
        if more:
 | 
						|
            lines.append('[{}]'.format(ansi('+', R.style_selected_2)))
 | 
						|
        return lines
 | 
						|
 | 
						|
    def attributes(self):
 | 
						|
        return {
 | 
						|
            'limit': {
 | 
						|
                'doc': 'Maximum number of displayed frames (0 means no limit).',
 | 
						|
                'default': 10,
 | 
						|
                'type': int,
 | 
						|
                'check': check_ge_zero
 | 
						|
            },
 | 
						|
            'arguments': {
 | 
						|
                'doc': 'Frame arguments visibility flag.',
 | 
						|
                'default': False,
 | 
						|
                '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
 | 
						|
            },
 | 
						|
            'align': {
 | 
						|
                'doc': 'Align variables in column flag (only if not compact).',
 | 
						|
                'default': False,
 | 
						|
                'type': bool
 | 
						|
            },
 | 
						|
            'sort': {
 | 
						|
                'doc': 'Sort variables by name.',
 | 
						|
                'default': False,
 | 
						|
                'type': bool
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
    def get_frame_lines(self, number, frame, selected=False):
 | 
						|
        # 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))
 | 
						|
        # add frame arguments and locals
 | 
						|
        variables = Variables.format_frame(
 | 
						|
            frame, self.show_arguments, self.show_locals, self.compact, self.align, self.sort)
 | 
						|
        frame_lines.extend(variables)
 | 
						|
        return frame_lines
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def format_line(prefix, line):
 | 
						|
        prefix = ansi(prefix, R.style_low)
 | 
						|
        return '{} {}'.format(prefix, line)
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def get_pc_line(frame, style):
 | 
						|
        frame_pc = ansi(format_address(frame.pc()), style)
 | 
						|
        info = 'from {}'.format(frame_pc)
 | 
						|
        # if a frame function symbol is available then use it to fetch the
 | 
						|
        # current function name and address, otherwise fall back relying on the
 | 
						|
        # frame name
 | 
						|
        if frame.function():
 | 
						|
            name = ansi(frame.function(), style)
 | 
						|
            func_start = to_unsigned(frame.function().value())
 | 
						|
            offset = ansi(str(frame.pc() - func_start), style)
 | 
						|
            info += ' in {}+{}'.format(name, offset)
 | 
						|
        elif frame.name():
 | 
						|
            name = ansi(frame.name(), style)
 | 
						|
            info += ' in {}'.format(name)
 | 
						|
        sal = frame.find_sal()
 | 
						|
        if sal and 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
 | 
						|
 | 
						|
class History(Dashboard.Module):
 | 
						|
    '''List the last entries of the value history.'''
 | 
						|
 | 
						|
    def label(self):
 | 
						|
        return 'History'
 | 
						|
 | 
						|
    def lines(self, term_width, term_height, 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_high).format(abs(i))
 | 
						|
                equal = ansi('=', R.style_low)
 | 
						|
                line = '{} {} {}'.format(value_id, equal, 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.'''
 | 
						|
 | 
						|
    DEFAULT_LENGTH = 16
 | 
						|
 | 
						|
    class Region():
 | 
						|
        def __init__(self, expression, length, module):
 | 
						|
            self.expression = expression
 | 
						|
            self.length = length
 | 
						|
            self.module = module
 | 
						|
            self.original = None
 | 
						|
            self.latest = None
 | 
						|
 | 
						|
        def reset(self):
 | 
						|
            self.original = None
 | 
						|
            self.latest = None
 | 
						|
 | 
						|
        def format(self, per_line):
 | 
						|
            # fetch the memory content
 | 
						|
            try:
 | 
						|
                address = Memory.parse_as_address(self.expression)
 | 
						|
                inferior = gdb.selected_inferior()
 | 
						|
                memory = inferior.read_memory(address, self.length)
 | 
						|
                # set the original memory snapshot if needed
 | 
						|
                if not self.original:
 | 
						|
                    self.original = memory
 | 
						|
            except gdb.error as e:
 | 
						|
                msg = 'Cannot access {} bytes starting at {}: {}'
 | 
						|
                msg = msg.format(self.length, self.expression, e)
 | 
						|
                return [ansi(msg, R.style_error)]
 | 
						|
            # format the memory content
 | 
						|
            out = []
 | 
						|
            for i in range(0, len(memory), per_line):
 | 
						|
                region = memory[i:i + per_line]
 | 
						|
                pad = per_line - len(region)
 | 
						|
                address_str = format_address(address + i)
 | 
						|
                # compute changes
 | 
						|
                hexa = []
 | 
						|
                text = []
 | 
						|
                for j in range(len(region)):
 | 
						|
                    rel = i + j
 | 
						|
                    byte = memory[rel]
 | 
						|
                    hexa_byte = '{:02x}'.format(ord(byte))
 | 
						|
                    text_byte = self.module.format_byte(byte)
 | 
						|
                    # differences against the latest have the highest priority
 | 
						|
                    if self.latest and memory[rel] != self.latest[rel]:
 | 
						|
                        hexa_byte = ansi(hexa_byte, R.style_selected_1)
 | 
						|
                        text_byte = ansi(text_byte, R.style_selected_1)
 | 
						|
                    # cumulative changes if enabled
 | 
						|
                    elif self.module.cumulative and memory[rel] != self.original[rel]:
 | 
						|
                        hexa_byte = ansi(hexa_byte, R.style_selected_2)
 | 
						|
                        text_byte = ansi(text_byte, R.style_selected_2)
 | 
						|
                    # format the text differently for clarity
 | 
						|
                    else:
 | 
						|
                        text_byte = ansi(text_byte, R.style_high)
 | 
						|
                    hexa.append(hexa_byte)
 | 
						|
                    text.append(text_byte)
 | 
						|
                # output the formatted line
 | 
						|
                hexa_placeholder = ' {}'.format(self.module.placeholder[0] * 2)
 | 
						|
                text_placeholder = self.module.placeholder[0]
 | 
						|
                out.append('{}  {}{}  {}{}'.format(
 | 
						|
                    ansi(address_str, R.style_low),
 | 
						|
                    ' '.join(hexa), ansi(pad * hexa_placeholder, R.style_low),
 | 
						|
                    ''.join(text), ansi(pad * text_placeholder, R.style_low)))
 | 
						|
            # update the latest memory snapshot
 | 
						|
            self.latest = memory
 | 
						|
            return out
 | 
						|
 | 
						|
    def __init__(self):
 | 
						|
        self.table = {}
 | 
						|
 | 
						|
    def label(self):
 | 
						|
        return 'Memory'
 | 
						|
 | 
						|
    def lines(self, term_width, term_height, style_changed):
 | 
						|
        out = []
 | 
						|
        for expression, region in self.table.items():
 | 
						|
            out.append(divider(term_width, expression))
 | 
						|
            out.extend(region.format(self.get_per_line(term_width)))
 | 
						|
        return out
 | 
						|
 | 
						|
    def commands(self):
 | 
						|
        return {
 | 
						|
            'watch': {
 | 
						|
                'action': self.watch,
 | 
						|
                'doc': '''Watch a memory region by expression and length.
 | 
						|
 | 
						|
The length defaults to 16 bytes.''',
 | 
						|
                'complete': gdb.COMPLETE_EXPRESSION
 | 
						|
            },
 | 
						|
            'unwatch': {
 | 
						|
                'action': self.unwatch,
 | 
						|
                'doc': 'Stop watching a memory region by expression.',
 | 
						|
                'complete': gdb.COMPLETE_EXPRESSION
 | 
						|
            },
 | 
						|
            'clear': {
 | 
						|
                'action': self.clear,
 | 
						|
                'doc': 'Clear all the watched regions.'
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
    def attributes(self):
 | 
						|
        return {
 | 
						|
            'cumulative': {
 | 
						|
                'doc': 'Highlight changes cumulatively, watch again to reset.',
 | 
						|
                'default': False,
 | 
						|
                'type': bool
 | 
						|
            },
 | 
						|
            'full': {
 | 
						|
                'doc': 'Take the whole horizontal space.',
 | 
						|
                'default': False,
 | 
						|
                'type': bool
 | 
						|
            },
 | 
						|
            'placeholder': {
 | 
						|
                'doc': 'Placeholder used for missing items and unprintable characters.',
 | 
						|
                'default': '·'
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
    def watch(self, arg):
 | 
						|
        if arg:
 | 
						|
            expression, _, length_str = arg.partition(' ')
 | 
						|
            length = Memory.parse_as_address(length_str) if length_str else Memory.DEFAULT_LENGTH
 | 
						|
            # keep the length when the memory is watched to reset the changes
 | 
						|
            region = self.table.get(expression)
 | 
						|
            if region and not length_str:
 | 
						|
                region.reset()
 | 
						|
            else:
 | 
						|
                self.table[expression] = Memory.Region(expression, length, self)
 | 
						|
        else:
 | 
						|
            raise Exception('Specify a memory location')
 | 
						|
 | 
						|
    def unwatch(self, arg):
 | 
						|
        if arg:
 | 
						|
            try:
 | 
						|
                del self.table[arg]
 | 
						|
            except KeyError:
 | 
						|
                raise Exception('Memory expression not watched')
 | 
						|
        else:
 | 
						|
            raise Exception('Specify a matched memory expression')
 | 
						|
 | 
						|
    def clear(self, arg):
 | 
						|
        self.table.clear()
 | 
						|
 | 
						|
    def format_byte(self, byte):
 | 
						|
        # `type(byte) is bytes` in Python 3
 | 
						|
        if 0x20 < ord(byte) < 0x7f:
 | 
						|
            return chr(ord(byte))
 | 
						|
        else:
 | 
						|
            return self.placeholder[0]
 | 
						|
 | 
						|
    def get_per_line(self, term_width):
 | 
						|
        if self.full:
 | 
						|
            padding = 3  # two double spaces separator (one is part of below)
 | 
						|
            elem_size = 4 # HH + 1 space + T
 | 
						|
            address_length = gdb.parse_and_eval('$pc').type.sizeof * 2 + 2  # 0x
 | 
						|
            return max(int((term_width - address_length - padding) / elem_size), 1)
 | 
						|
        else:
 | 
						|
            return Memory.DEFAULT_LENGTH
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def parse_as_address(expression):
 | 
						|
        value = gdb.parse_and_eval(expression)
 | 
						|
        return to_unsigned(value)
 | 
						|
 | 
						|
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, term_height, style_changed):
 | 
						|
        # skip if the current thread is not stopped
 | 
						|
        if not gdb.selected_thread().is_stopped():
 | 
						|
            return []
 | 
						|
        # obtain the registers to display
 | 
						|
        if style_changed:
 | 
						|
            self.table = {}
 | 
						|
        if self.register_list:
 | 
						|
            register_list = self.register_list.split()
 | 
						|
        else:
 | 
						|
            register_list = Registers.fetch_register_list()
 | 
						|
        # fetch registers status
 | 
						|
        registers = []
 | 
						|
        for name in register_list:
 | 
						|
            # 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)
 | 
						|
            # exclude unavailable registers (see #255)
 | 
						|
            if string_value == '<unavailable>':
 | 
						|
                continue
 | 
						|
            changed = self.table and (self.table.get(name, '') != string_value)
 | 
						|
            self.table[name] = string_value
 | 
						|
            registers.append((name, string_value, changed))
 | 
						|
        # handle the empty register list
 | 
						|
        if not registers:
 | 
						|
            msg = 'No registers to show (check the "dashboard registers -style list" attribute)'
 | 
						|
            return [ansi(msg, R.style_error)]
 | 
						|
        # compute lengths considering an extra space between and around the
 | 
						|
        # entries (hence the +2 and 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
 | 
						|
        columns = min(int((term_width - 1) / max_width) or 1, len(registers))
 | 
						|
        rows = int(math.ceil(float(len(registers)) / columns))
 | 
						|
        # build the registers matrix
 | 
						|
        if self.column_major:
 | 
						|
            matrix = list(registers[i:i + rows] for i in range(0, len(registers), rows))
 | 
						|
        else:
 | 
						|
            matrix = list(registers[i::columns] for i in range(columns))
 | 
						|
        # compute the lengths column wise
 | 
						|
        max_names_column = list(max(len(name) for name, _, _ in column) for column in matrix)
 | 
						|
        max_values_column = list(max(len(value) for _, value, _ in column) for column in matrix)
 | 
						|
        line_length = sum(max_names_column) + columns + sum(max_values_column)
 | 
						|
        extra = term_width - line_length
 | 
						|
        # compute padding as if there were one more column
 | 
						|
        base_padding = int(extra / (columns + 1))
 | 
						|
        padding_column = [base_padding] * columns
 | 
						|
        # distribute the remainder among columns giving the precedence to
 | 
						|
        # internal padding
 | 
						|
        rest = extra % (columns + 1)
 | 
						|
        while rest:
 | 
						|
            padding_column[rest % columns] += 1
 | 
						|
            rest -= 1
 | 
						|
        # format the registers
 | 
						|
        out = [''] * rows
 | 
						|
        for i, column in enumerate(matrix):
 | 
						|
            max_name = max_names_column[i]
 | 
						|
            max_value = max_values_column[i]
 | 
						|
            for j, (name, value, changed) in enumerate(column):
 | 
						|
                name = ' ' * (max_name - len(name)) + ansi(name, R.style_low)
 | 
						|
                style = R.style_selected_1 if changed else ''
 | 
						|
                value = ansi(value, style) + ' ' * (max_value - len(value))
 | 
						|
                padding = ' ' * padding_column[i]
 | 
						|
                item = '{}{} {}'.format(padding, name, value)
 | 
						|
                out[j] += item
 | 
						|
        return out
 | 
						|
 | 
						|
    def attributes(self):
 | 
						|
        return {
 | 
						|
            'column-major': {
 | 
						|
                'doc': 'Show registers in columns instead of rows.',
 | 
						|
                'default': False,
 | 
						|
                'name': 'column_major',
 | 
						|
                'type': bool
 | 
						|
            },
 | 
						|
            'list': {
 | 
						|
                'doc': '''String of space-separated register names to display.
 | 
						|
 | 
						|
The empty list (default) causes to show all the available registers. For
 | 
						|
architectures different from x86 setting this attribute might be mandatory.''',
 | 
						|
                'default': '',
 | 
						|
                'name': 'register_list',
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
    @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)
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def fetch_register_list(*match_groups):
 | 
						|
        names = []
 | 
						|
        for line in run('maintenance print register-groups').split('\n'):
 | 
						|
            fields = line.split()
 | 
						|
            if len(fields) != 7:
 | 
						|
                continue
 | 
						|
            name, _, _, _, _, _, groups = fields
 | 
						|
            if not re.match(r'\w', name):
 | 
						|
                continue
 | 
						|
            for group in groups.split(','):
 | 
						|
                if group in (match_groups or ('general',)):
 | 
						|
                    names.append(name)
 | 
						|
                    break
 | 
						|
        return names
 | 
						|
 | 
						|
class Threads(Dashboard.Module):
 | 
						|
    '''List the currently available threads.'''
 | 
						|
 | 
						|
    def label(self):
 | 
						|
        return 'Threads'
 | 
						|
 | 
						|
    def lines(self, term_width, term_height, 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()
 | 
						|
        # fetch the thread list
 | 
						|
        threads = []
 | 
						|
        for inferior in gdb.inferiors():
 | 
						|
            if self.all_inferiors or inferior == gdb.selected_inferior():
 | 
						|
                threads += gdb.Inferior.threads(inferior)
 | 
						|
        for thread in threads:
 | 
						|
            # 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
 | 
						|
            if self.all_inferiors:
 | 
						|
                number = '{}.{}'.format(thread.inferior.num, thread.num)
 | 
						|
            else:
 | 
						|
                number = str(thread.num)
 | 
						|
            number = ansi(number, 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
 | 
						|
            },
 | 
						|
            'all-inferiors': {
 | 
						|
                'doc': 'Show threads from all inferiors.',
 | 
						|
                'default': False,
 | 
						|
                'name': 'all_inferiors',
 | 
						|
                'type': bool
 | 
						|
            },
 | 
						|
        }
 | 
						|
 | 
						|
class Expressions(Dashboard.Module):
 | 
						|
    '''Watch user expressions.'''
 | 
						|
 | 
						|
    def __init__(self):
 | 
						|
        self.table = []
 | 
						|
 | 
						|
    def label(self):
 | 
						|
        return 'Expressions'
 | 
						|
 | 
						|
    def lines(self, term_width, term_height, style_changed):
 | 
						|
        out = []
 | 
						|
        label_width = 0
 | 
						|
        if self.align:
 | 
						|
            label_width = max(len(expression) for expression in self.table) if self.table else 0
 | 
						|
        default_radix = Expressions.get_default_radix()
 | 
						|
        for number, expression in enumerate(self.table, start=1):
 | 
						|
            label = expression
 | 
						|
            match = re.match(r'^/(\d+) +(.+)$', expression)
 | 
						|
            try:
 | 
						|
                if match:
 | 
						|
                    radix, expression = match.groups()
 | 
						|
                    run('set output-radix {}'.format(radix))
 | 
						|
                value = format_value(gdb.parse_and_eval(expression))
 | 
						|
            except gdb.error as e:
 | 
						|
                value = ansi(e, R.style_error)
 | 
						|
            finally:
 | 
						|
                if match:
 | 
						|
                    run('set output-radix {}'.format(default_radix))
 | 
						|
            number = ansi(str(number), R.style_selected_2)
 | 
						|
            label = ansi(expression, R.style_high) + ' ' * (label_width - len(expression))
 | 
						|
            equal = ansi('=', R.style_low)
 | 
						|
            out.append('[{}] {} {} {}'.format(number, label, equal, value))
 | 
						|
        return out
 | 
						|
 | 
						|
    def commands(self):
 | 
						|
        return {
 | 
						|
            'watch': {
 | 
						|
                'action': self.watch,
 | 
						|
                'doc': 'Watch an expression using the format `[/<radix>] <expression>`.',
 | 
						|
                'complete': gdb.COMPLETE_EXPRESSION
 | 
						|
            },
 | 
						|
            'unwatch': {
 | 
						|
                'action': self.unwatch,
 | 
						|
                'doc': 'Stop watching an expression by index.'
 | 
						|
            },
 | 
						|
            'clear': {
 | 
						|
                'action': self.clear,
 | 
						|
                'doc': 'Clear all the watched expressions.'
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
    def attributes(self):
 | 
						|
        return {
 | 
						|
            'align': {
 | 
						|
                'doc': 'Align variables in column flag.',
 | 
						|
                'default': False,
 | 
						|
                'type': bool
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
    def watch(self, arg):
 | 
						|
        if arg:
 | 
						|
            if arg not in self.table:
 | 
						|
                self.table.append(arg)
 | 
						|
            else:
 | 
						|
                raise Exception('Expression already watched')
 | 
						|
        else:
 | 
						|
            raise Exception('Specify an expression')
 | 
						|
 | 
						|
    def unwatch(self, arg):
 | 
						|
        if arg:
 | 
						|
            try:
 | 
						|
                number = int(arg) - 1
 | 
						|
            except:
 | 
						|
                number = -1
 | 
						|
            if 0 <= number < len(self.table):
 | 
						|
                self.table.pop(number)
 | 
						|
            else:
 | 
						|
                raise Exception('Expression not watched')
 | 
						|
        else:
 | 
						|
            raise Exception('Specify an expression')
 | 
						|
 | 
						|
    def clear(self, arg):
 | 
						|
        self.table.clear()
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def get_default_radix():
 | 
						|
        try:
 | 
						|
            return gdb.parameter('output-radix')
 | 
						|
        except RuntimeError:
 | 
						|
            # XXX this is a fix for GDB <8.1.x see #161
 | 
						|
            message = run('show output-radix')
 | 
						|
            match = re.match(r'^Default output radix for printing of values is (\d+)\.$', message)
 | 
						|
            return match.groups()[0] if match else 10  # fallback
 | 
						|
 | 
						|
# XXX workaround to support BP_BREAKPOINT in older GDB versions
 | 
						|
setattr(gdb, 'BP_CATCHPOINT', getattr(gdb, 'BP_CATCHPOINT', 26))
 | 
						|
 | 
						|
class Breakpoints(Dashboard.Module):
 | 
						|
    '''Display the breakpoints list.'''
 | 
						|
 | 
						|
    NAMES = {
 | 
						|
        gdb.BP_BREAKPOINT: 'break',
 | 
						|
        gdb.BP_WATCHPOINT: 'watch',
 | 
						|
        gdb.BP_HARDWARE_WATCHPOINT: 'write watch',
 | 
						|
        gdb.BP_READ_WATCHPOINT: 'read watch',
 | 
						|
        gdb.BP_ACCESS_WATCHPOINT: 'access watch',
 | 
						|
        gdb.BP_CATCHPOINT: 'catch'
 | 
						|
    }
 | 
						|
 | 
						|
    def label(self):
 | 
						|
        return 'Breakpoints'
 | 
						|
 | 
						|
    def lines(self, term_width, term_height, style_changed):
 | 
						|
        out = []
 | 
						|
        breakpoints = fetch_breakpoints(watchpoints=True, pending=self.show_pending)
 | 
						|
        for breakpoint in breakpoints:
 | 
						|
            sub_lines = []
 | 
						|
            # format common information
 | 
						|
            style = R.style_selected_1 if breakpoint['enabled'] else R.style_selected_2
 | 
						|
            number = ansi(breakpoint['number'], style)
 | 
						|
            bp_type = ansi(Breakpoints.NAMES[breakpoint['type']], style)
 | 
						|
            if breakpoint['temporary']:
 | 
						|
                bp_type = bp_type + ' {}'.format(ansi('once', style))
 | 
						|
            if not R.ansi and breakpoint['enabled']:
 | 
						|
                bp_type = 'disabled ' + bp_type
 | 
						|
            line = '[{}] {}'.format(number, bp_type)
 | 
						|
            if breakpoint['type'] == gdb.BP_BREAKPOINT:
 | 
						|
                for i, address in enumerate(breakpoint['addresses']):
 | 
						|
                    addr = address['address']
 | 
						|
                    if i == 0 and addr:
 | 
						|
                        # this is a regular breakpoint
 | 
						|
                        line += ' at {}'.format(ansi(format_address(addr), style))
 | 
						|
                        # format source information
 | 
						|
                        file_name = address.get('file_name')
 | 
						|
                        file_line = address.get('file_line')
 | 
						|
                        if file_name and file_line:
 | 
						|
                            file_name = ansi(file_name, style)
 | 
						|
                            file_line = ansi(file_line, style)
 | 
						|
                            line += ' in {}:{}'.format(file_name, file_line)
 | 
						|
                    elif i > 0:
 | 
						|
                        # this is a sub breakpoint
 | 
						|
                        sub_style = R.style_selected_1 if address['enabled'] else R.style_selected_2
 | 
						|
                        sub_number = ansi('{}.{}'.format(breakpoint['number'], i), sub_style)
 | 
						|
                        sub_line = '[{}]'.format(sub_number)
 | 
						|
                        sub_line += ' at {}'.format(ansi(format_address(addr), sub_style))
 | 
						|
                        # format source information
 | 
						|
                        file_name = address.get('file_name')
 | 
						|
                        file_line = address.get('file_line')
 | 
						|
                        if file_name and file_line:
 | 
						|
                            file_name = ansi(file_name, sub_style)
 | 
						|
                            file_line = ansi(file_line, sub_style)
 | 
						|
                            sub_line += ' in {}:{}'.format(file_name, file_line)
 | 
						|
                        sub_lines += [sub_line]
 | 
						|
                # format user location
 | 
						|
                location = breakpoint['location']
 | 
						|
                line += ' for {}'.format(ansi(location, style))
 | 
						|
            elif breakpoint['type'] == gdb.BP_CATCHPOINT:
 | 
						|
                what = breakpoint['what']
 | 
						|
                line += ' {}'.format(ansi(what, style))
 | 
						|
            else:
 | 
						|
                # format user expression
 | 
						|
                expression = breakpoint['expression']
 | 
						|
                line += ' for {}'.format(ansi(expression, style))
 | 
						|
            # format condition
 | 
						|
            condition = breakpoint['condition']
 | 
						|
            if condition:
 | 
						|
                line += ' if {}'.format(ansi(condition, style))
 | 
						|
            # format hit count
 | 
						|
            hit_count = breakpoint['hit_count']
 | 
						|
            if hit_count:
 | 
						|
                word = 'time{}'.format('s' if hit_count > 1 else '')
 | 
						|
                line += ' hit {} {}'.format(ansi(breakpoint['hit_count'], style), word)
 | 
						|
            # append the main line and possibly sub breakpoints
 | 
						|
            out.append(line)
 | 
						|
            out.extend(sub_lines)
 | 
						|
        return out
 | 
						|
 | 
						|
    def attributes(self):
 | 
						|
        return {
 | 
						|
            'pending': {
 | 
						|
                'doc': 'Also show pending breakpoints.',
 | 
						|
                'default': True,
 | 
						|
                'name': 'show_pending',
 | 
						|
                'type': bool
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
# 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()
 | 
						|
 | 
						|
# File variables ---------------------------------------------------------------
 | 
						|
 | 
						|
# vim: filetype=python
 | 
						|
# Local Variables:
 | 
						|
# mode: python
 | 
						|
# End:
 |