diff --git a/.gdbinit b/.gdbinit index 5e6c6cb..face706 100644 --- a/.gdbinit +++ b/.gdbinit @@ -1819,14 +1819,22 @@ class History(Dashboard.Module): } class Memory(Dashboard.Module): - '''Allow to inspect memory regions.''' + '''Allow to inspect memory regions with GDB's "x" command.''' - DEFAULT_LENGTH = 16 + DEFAULT_COUNT = 16 + DEFAULT_FORMAT = 'x' + DEFAULT_SIZE = 'b' + DEFAULT_PER_LINE = 4 + SEPARATION_SPACE_RAW = 2 + SEPARATION_SPACE_TEXT = 1 class Region(): - def __init__(self, expression, length, module): - self.expression = expression - self.length = length + def __init__(self, xcmd, object_base, object_size, per_line, module): + self.xcmd = xcmd + self.object_base = object_base + self.object_size = object_size + self.address = 0 + self.per_line = per_line self.module = module self.original = None self.latest = None @@ -1835,53 +1843,139 @@ class Memory(Dashboard.Module): self.original = None self.latest = None - def format(self, per_line): + def fetch_memory(self): + memory_raw = gdb.execute(self.xcmd, to_string=True) + if memory_raw.startswith("warning"): + raise gdb.error('GDB threw warning, try command directly for more info') + # get start address from GDB's output rather than input, so GDB can + # use default if no address input is provided and to account for + # negative range + self.address = memory_raw[:memory_raw.index(':')] + # remove current position indicator if present and parse + self.address = re.sub(r'^=> ', '', self.address) + self.address = Memory.parse_as_address(self.address.split()[0]) + # split output string into items and strip addresses + memory = [] + for l in memory_raw.split('\n'): + if l: + address_separator = re.search(r':\s+', l) + if not address_separator: + raise gdb.error('Unexpected output from GDB') + if self.object_base == 'i' or self.object_base == 's': + # one instruction/string per line with variable size, so + # keep address + l_elements = [l] + elif self.object_base == 'c': + # always in pairs, separated by tabs + l_elements = l[address_separator.end():].split('\t') + else: + l_elements = l[address_separator.end():].split() + # with string format GDB doesn't throw an error directly + if self.object_base == 's' and ' max_per_line: # use maximum horizontal space + per_line = max_per_line + elif choice > 0: + per_line = choice else: - return Memory.DEFAULT_LENGTH + per_line = 1 + return per_line + + def parse_arg(self, arg): + msg_invalid = 'Invalid command, see "help dashboard memory watch"' + # values for GDB's x command (every letter is unique) + x_vals_size = { 'b': 1, 'h': 2, 'w': 4, 'g': 8} # object size + x_vals_format = { + 'x': 16, 'z': 16, 'o': 8, 'd': 10, 'u': 10, 't': 2, # object base + 'i': 'i', 'c': 'c', 's': 's', 'f': 'other', 'a': 'other' # string objects + } + cmd_options = {'address': '', 'count': 0, + 'base': ('', 0), 'size': ('', 0), 'per_line': 0} + # argument can be either "x[/[COUNT][FORMAT][SIZE]] [PER_LINE]" or + # "ADDRESS [COUNT] [FORMAT] [SIZE] [PER_LINE]"; + # bring argument into common format + arg_parts = arg.split() + # regardless of format, per_line arg must be at least the 3rd argument + # and it must be numeric + if len(arg_parts) > 2 and arg_parts[-1].isdigit(): + cmd_options['per_line'] = self.parse_as_address(arg_parts[-1]) + del(arg_parts[-1]) + if arg.startswith('x'): # format of GDB's "x" command + if len(arg_parts) > 1: + cmd_options['address'] = arg_parts[1] + x_options = arg_parts[0][2:] # formatted as "x/FMT"; go past slash + else: # gdb-dashboard format + cmd_options['address'] = arg_parts[0] + x_options = ''.join(arg_parts[1:]) + # parse command + if len(x_options): + count_pos = re.search(r'^-?\d*', x_options) + if count_pos and count_pos.start() != count_pos.end(): + cmd_options['count'] = int(x_options[:count_pos.end()]) + x_options = x_options[count_pos.end():] + if len(x_options) > 2: + raise Exception(msg_invalid) + elif len(x_options): + for opt in x_options: + if opt in x_vals_format: + cmd_options['base'] = (opt, x_vals_format[opt]) + elif opt in x_vals_size: + cmd_options['size'] = (opt, x_vals_size[opt]) + else: + raise Exception(msg_invalid) + # set defaults where needed + if not cmd_options['count']: + cmd_options['count'] = self.DEFAULT_COUNT + if not cmd_options['base'][0]: + cmd_options['base'] = (self.DEFAULT_FORMAT, x_vals_format[self.DEFAULT_FORMAT]) + if not cmd_options['size'][0]: + cmd_options['size'] = (self.DEFAULT_SIZE, x_vals_size[self.DEFAULT_SIZE]) + if not cmd_options['per_line']: + cmd_options['per_line'] = self.DEFAULT_PER_LINE + if cmd_options['per_line'] > abs(cmd_options['count']): + # avoid unnecessary placeholders + cmd_options['per_line'] = abs(cmd_options['count']) + return cmd_options @staticmethod def parse_as_address(expression):