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