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