├── example ├── breakpoint_hook.py ├── command.py ├── function.py ├── info.py └── pprint.py ├── .gitignore ├── LICENSE ├── README.md ├── API.md └── gdb_utils.py /example/breakpoint_hook.py: -------------------------------------------------------------------------------- 1 | # Add commands to the last breakpoint 2 | # (gdb) comm 3 | # info locals 4 | # info args 5 | # end 6 | # (gdb) c 7 | 8 | # Do the same with python script 9 | # (gdb) so breakpoint_hook.py 10 | 11 | # Before 12 | import gdb 13 | 14 | 15 | last_breakpoint_num = gdb.breakpoints()[-1].number 16 | 17 | def commands(event): 18 | if isinstance(event, gdb.SignalEvent): 19 | return 20 | if last_breakpoint_num in (bp.number for bp in event.breakpoints): 21 | gdb.execute('info locals') 22 | gdb.execute('info args') 23 | 24 | gdb.events.stop.connect(commands) 25 | 26 | # After 27 | import gdb_utils 28 | 29 | def info_all(): 30 | gdb.execute('info locals') 31 | gdb.execute('info args') 32 | 33 | gdb_utils.commands(info_all) 34 | # Or 35 | gdb_utils.stop(info_all, gdb.breakpoints()[-1]) 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | 58 | # PyBuilder 59 | target/ 60 | 61 | #Ipython Notebook 62 | .ipynb_checkpoints 63 | 64 | *.c 65 | *.cpp 66 | try_*.py 67 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 罗泽轩 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Utilities for extending gdb with python 2 | 3 | ## Usage 4 | 5 | 1. Download `debugger-utils` to directory `X` 6 | 2. Put these to your `~/.gdbinit`: 7 | ``` 8 | python import sys; sys.path.append('$X/debugger-utils') 9 | ``` 10 | `$X` is where `debugger-utils` lies in. 11 | 3. `import gdb_utils` in your python script. 12 | 13 | ## Example 14 | 15 | ```python 16 | # Create a gdb commands in hello.py 17 | import gdb_utils 18 | 19 | 20 | def hello(who, *args): 21 | """ 22 | Say hello. 23 | """ 24 | print('hello %s', who) 25 | gdb_utils.define(hello) 26 | #(gdb) so hello.py 27 | #(gdb) hello 'world' 28 | ``` 29 | 30 | For more examples, see [example](./example). 31 | 32 | ## API 33 | 34 | GDB commands: 35 | 36 | - [x] break 37 | - [x] clear 38 | - [x] commands 39 | - [x] delete 40 | - [x] disable 41 | - [x] define 42 | - [x] enable 43 | - [x] info 44 | - [x] thread 45 | - [x] thread_name 46 | - [x] watch 47 | 48 | Other API: 49 | 50 | - [x] get_breakpoint 51 | - [x] function 52 | - [x] stop 53 | - [x] register_pprinter 54 | - [x] build_pprinter 55 | - [x] globval 56 | - [x] ty 57 | 58 | For more info, see [API.md](./API.md). 59 | -------------------------------------------------------------------------------- /example/command.py: -------------------------------------------------------------------------------- 1 | # (gdb) so command.py 2 | # (gdb) help mv 3 | # Move breakpoint. 4 | # Usage: mv old_breakpoint_num new_breakpoint 5 | # Example: 6 | # (gdb) mv 1 binary_search -- move breakpoint 1 to `b binary_search` 7 | # 8 | # (gdb) mv 1 binary_search 9 | 10 | # Before 11 | import gdb 12 | 13 | 14 | class Move(gdb.Command): 15 | """Move breakpoint 16 | Usage: mv old_breakpoint_num new_breakpoint 17 | Example: 18 | (gdb) mv 1 binary_search -- move breakpoint 1 to `b binary_search` 19 | """ 20 | def __init__(self): 21 | super(self.__class__, self).__init__("mv", gdb.COMMAND_USER) 22 | 23 | def invoke(self, args, from_tty): 24 | argv = gdb.string_to_argv(args) 25 | if len(argv) != 2: 26 | raise gdb.GdbError('输入参数数目不对,help mv以获得用法') 27 | gdb.execute('delete ' + argv[0]) 28 | gdb.execute('break ' + argv[1]) 29 | 30 | Move() 31 | 32 | # After 33 | import gdb_utils 34 | 35 | def move(argv, from_tty): 36 | if len(argv) != 2: 37 | raise gdb.GdbError('输入参数数目不对,help mv以获得用法') 38 | gdb_utils.delete(argv[0]) 39 | gdb_utils.br(argv[1]) 40 | 41 | gdb_utils.define(move) 42 | -------------------------------------------------------------------------------- /example/function.py: -------------------------------------------------------------------------------- 1 | # typedef struct { 2 | # void *data; 3 | # unsigned int size; 4 | # } Array; 5 | # (gdb) so function.py 6 | # (gdb) show convenience 7 | # $dataType = 8 | # 9 | # (gdb) help function 10 | # function dataType -- Print data according to type 11 | # 12 | # (gdb) p $dataType(array, 'char') 13 | # $1 = "{ 65 'A' 66 'B' 67 'C' 68 'D' 69 'E' 70 'F' 71 'G' 72 'H' 73 'I' 74 'J'}" 14 | # (gdb) p $dataType(array, "short") 15 | # $2 = "{ 16961 17475 17989 18503 19017 0 0 0 0 0}" 16 | # Before 17 | import gdb 18 | 19 | 20 | class DataType(gdb.Function): 21 | 'Print data according to type' 22 | def __init__(self): 23 | super(self.__class__, self).__init__('dataType') 24 | 25 | def invoke(self, target, typename): 26 | pointer = target['data'].cast(gdb.lookup_type(typename.string()).pointer()) 27 | data = [] 28 | for i in range(int(target['size'])): 29 | elem = pointer.dereference() 30 | data.append(str(elem)) 31 | pointer = pointer + 1 32 | return '{ ' + ' '.join(data) + '}' 33 | 34 | DataType() 35 | 36 | # After 37 | import gdb_utils 38 | 39 | 40 | def dataType(target, typename): 41 | 'Print data according to type' 42 | pointer = target['data'].cast(gdb.lookup_type(typename.string()).pointer()) 43 | data = [] 44 | for i in range(int(target['size'])): 45 | elem = pointer.dereference() 46 | data.append(str(elem)) 47 | pointer = pointer + 1 48 | return '{ ' + ' '.join(data) + '}' 49 | 50 | gdb_utils.function(dataType) 51 | -------------------------------------------------------------------------------- /example/info.py: -------------------------------------------------------------------------------- 1 | # (gdb) info args 2 | # i = 2 3 | # j = 97 'a' 4 | # (gdb) info locals 5 | # k = 0 6 | # l = 108 'l' 7 | # (gdb) info breakpoints 8 | # Num Type Disp Enb Address What 9 | # 1 breakpoint keep y 0x0000000000400505 in func at gdb.c:9 10 | # breakpoint already hit 1 time 11 | # (gdb) so info.py 12 | # i is 2 13 | # k is 0 14 | # breakpoint 1 type: 1 enable: True temp: False 15 | # Where: gdb.c:9 16 | import os 17 | 18 | 19 | def print_breakpoint(bp): 20 | print("breakpoint %d type: %s enable: %s temp: %s" % ( 21 | bp.number, bp.type, bp.enabled, bp.temporary)) 22 | 23 | if bp.location is None: 24 | what = bp.expression 25 | else: 26 | what = os.path.relpath(bp.location, os.getcwd()) 27 | print("Where: " + what) 28 | 29 | if bp.condition is not None: 30 | print("When: " + bp.condition) 31 | 32 | # Before 33 | import gdb 34 | 35 | def convert_variable_info(text): 36 | group = {} 37 | for line in text.splitlines(): 38 | variable, _, value = line.partition('=') 39 | group[variable.rstrip()] = value.lstrip() 40 | return group 41 | 42 | args = convert_variable_info(gdb.execute('info args', to_string=True)) 43 | locals = convert_variable_info(gdb.execute('info locals', to_string=True)) 44 | breakpoints = gdb.breakpoints() 45 | print('i is %s' % args['i']) 46 | print('k is %s' % locals['k']) 47 | print_breakpoint(breakpoints[0]) 48 | 49 | # After 50 | import gdb_utils 51 | 52 | args = gdb_utils.info('args') 53 | locals = gdb_utils.info('locals') 54 | bps = gdb_utils.info('br') 55 | print('i is %s' % args['i']) 56 | print('k is %s' % locals['k']) 57 | print_breakpoint(breakpoints[0]) 58 | -------------------------------------------------------------------------------- /example/pprint.py: -------------------------------------------------------------------------------- 1 | # typedef struct { 2 | # int *data; 3 | # unsigned int used; 4 | # unsigned int free; 5 | # } Buffer; 6 | # (gdb) so pprint.py 7 | # (gdb) p buf 8 | # $2 = used: 10 9 | # free: 0 10 | # = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} 11 | # Before 12 | import gdb 13 | 14 | 15 | class BufferPrinter: 16 | """Print Buffer""" 17 | def __init__(self, val): 18 | self.val = val 19 | 20 | def _iterate(self, pointer, size): 21 | for i in range(size): 22 | elem = pointer.dereference() 23 | pointer = pointer + 1 24 | yield ('[%d]' % i, elem) 25 | 26 | def children(self): 27 | """Iterate children member""" 28 | return self._iterate(self.val['data'], int(self.val['used'])) 29 | 30 | def to_string(self): 31 | """The main display data""" 32 | return "used: %d\nfree: %d\n" % (self.val['used'], self.val['free']) 33 | 34 | def display_hint(self): 35 | """Print like an array""" 36 | return 'array' 37 | 38 | 39 | def lookup_buffer(val): 40 | if str(val.type) == 'Buffer': 41 | return BufferPrinter(val) 42 | return None 43 | 44 | gdb.pretty_printers.append(lookup_buffer) 45 | 46 | # After 47 | import gdb_utils 48 | 49 | 50 | def _iterate(pointer, size): 51 | for i in range(size): 52 | elem = pointer.dereference() 53 | pointer = pointer + 1 54 | yield ('[%d]' % i, elem) 55 | 56 | def iter_data(val): 57 | return _iterate(val['data'], int(val['used'])) 58 | 59 | def to_string(val): 60 | return "used: %d\nfree: %d\n" % (val['used'], val['free']) 61 | 62 | pp = gdb_utils.build_pprinter(to_string, display_hint='array', 63 | children=iter_data) 64 | gdb_utils.register_pprinter(pp, pattern='^Buffer$') 65 | -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | ## br(location, threadnum='', condition='', commands=None, temporary=False, probe_modifier='') 2 | ``` 3 | Return a gdb.Breakpoint object 4 | br('main.cpp:2') # break main.cpp:2 5 | # break 2 thread 2 if result != NULL 6 | br('2', threadnum=2, condition='result != NULL') 7 | # threadnum can be the name of specific thread. 8 | # We only set breakpoint on single thread, so make sure this name is unique. 9 | br('2', threadnum='foo') 10 | 11 | # break 2 and run the given function `callback` 12 | br('2', commands=callback) 13 | 14 | # temporary break 15 | br('2', temporary=True) 16 | ``` 17 | 18 | ## clear() 19 | ``` 20 | clear('main.cpp:11') 21 | ``` 22 | 23 | ## commands(callback, breakpoint_num=None, remove=False) 24 | ``` 25 | If `breakpoint_num` is not given, add callback to last breakpoint, 26 | else add to specific breakpoint. 27 | If`remove` is True, remove the callback instead of adding it. 28 | ``` 29 | 30 | ## define(cmd) 31 | ``` 32 | Define an user command with given function. We will forward two arguments 33 | to this function, first is argv(list of parameters), second is `from_tty`. 34 | 35 | # move.py 36 | import gdb_utils 37 | def move(argv, tty): 38 | "Move a breakpoint to other location." 39 | if len(argv) != 2: 40 | raise gdb.GdbError('Expect two arguments, %d given' % len(argv)) 41 | gdb.execute('delete ' + argv[0]) 42 | gdb.execute('break ' + argv[1]) 43 | gdb_utils.define(move) 44 | 45 | (gdb) so move.py 46 | (gdb) help move 47 | Move a breakpoint to other location. 48 | (gdb) move 100 main.cpp:22 49 | ``` 50 | 51 | ## delete() 52 | ``` 53 | delete() # delete all breakpoints 54 | delete('1') # delete breakpoint 1 55 | delete('bookmark', 1) # delete bookmark 1 56 | delete(gdb.Breakpoint) # delete given Breakpoint object 57 | ``` 58 | 59 | ## disable() 60 | ``` 61 | Similar to gdb command 'disable'. 62 | ``` 63 | 64 | ## enable() 65 | ``` 66 | Similar to gdb command 'enable'. 67 | ``` 68 | 69 | ## info(entry) 70 | ``` 71 | In most cases, simply execute given arguments 72 | and return results as string. 73 | When `entry` is: 74 | * locals/args: Return dict{variable=value} 75 | * breakpoints: Return a list of gdb.Breakpoint 76 | * threads: Return a list of (is_current_thread, num, ptid, name, frame) 77 | ``` 78 | 79 | ## thread() 80 | ``` 81 | thread(1) # switch to thread 1 82 | # switch to the thread which has name a.out and with the highest id 83 | thread('a.out') 84 | ``` 85 | 86 | ## thread_name(name, threadnum=None) 87 | ``` 88 | Set name to thread `threadnum`. 89 | If threadnum is not given, set name to current thread. 90 | For example: threadnum('foo', 2) will set thread 2's name to 'foo'. 91 | ``` 92 | 93 | ## watch(expression, condition='', commands=None) 94 | ``` 95 | # watch array.len if size > 20 96 | watch('array.len', condition='size > 20') 97 | ``` 98 | 99 | ## get_breakpoint(location=None, expression=None, condition=None, number=None) 100 | ``` 101 | Return last breakpoint if not arguments given, 102 | or the breakpoint with given number if `number` given, 103 | or the first breakpoint matched given 104 | `location`(for `breakpoint`)/`expression`(for watchpoint) and `condition`. 105 | 106 | If there is no any breakpoint, 107 | raise gdb.GdbError('No breakpoints or watchpoints.') 108 | If there is no matched breakpoint, return None. 109 | ``` 110 | 111 | ## function(func) 112 | ``` 113 | Define a gdb convenience function with user specific function. 114 | 115 | # greet.py 116 | import gdb_utils 117 | def greet(name): 118 | "The `name` argument will be a gdb.Value" 119 | return "Hello, %s" % name.string() 120 | gdb_utils.function(func) 121 | 122 | (gdb) so greet.py 123 | (gdb) p $greet("World") 124 | $1 = "Hello World" 125 | ``` 126 | 127 | ## stop(callback, breakpoint=None, remove=False) 128 | ``` 129 | Run callback while gdb stops on breakpoints. 130 | If `breakpoint` is given, run it while specific breakpoint is hit. 131 | If `remove` is True, remove callback instead of adding it. 132 | ``` 133 | 134 | ## ty(typename) 135 | ``` 136 | Return a gdb.Type object represents given `typename`. 137 | For example, x.cast(ty('Buffer')) 138 | ``` 139 | 140 | ## globval(var) 141 | ``` 142 | Get global `var`'s value 143 | ``` 144 | 145 | ## register_pprinter(pprinter, pattern) 146 | ``` 147 | Register given pprinter to class matched given pattern. 148 | ``` 149 | 150 | ## build_pprinter(to_string, display_hint=None, children=None) 151 | ``` 152 | Build a pretty printer. 153 | For example: 154 | def buffer_pretty_printer(val): 155 | return "size: %d\n" % self.val['size'] 156 | def children(val): 157 | return [] 158 | pp = build_pprinter(buffer_pretty_printer, display_hint='array', 159 | children=children) 160 | 161 | ... is equal to: 162 | 163 | class BufferPrettyPrinter: 164 | def __init__(self, val): 165 | self.val = val 166 | def to_string(self): 167 | return "size: %d\n" % self.val['size'] 168 | def children(self): 169 | return [] 170 | def display_hint(self): 171 | return 'array' 172 | pp = BufferPrettyPrinter 173 | 174 | ``` 175 | 176 | -------------------------------------------------------------------------------- /gdb_utils.py: -------------------------------------------------------------------------------- 1 | """This module wraps gdb and offers API with the same name of gdb commands.""" 2 | from collections import defaultdict 3 | import re 4 | import sys 5 | import gdb 6 | 7 | __all__ = [ 8 | 'br', 9 | 'clear', 10 | 'commands', 11 | 'define', 12 | 'delete', 13 | 'disable', 14 | 'enable', 15 | 'info', 16 | 'thread', 17 | 'thread_name', 18 | 'watch', 19 | 'get_breakpoint', 20 | 'globval', 21 | 'register_pprinter', 22 | 'build_pprinter', 23 | 'function', 24 | 'stop', 25 | 'ty'] 26 | 27 | 28 | # GDB commands 29 | def br(location, threadnum='', condition='', commands=None, 30 | temporary=False, probe_modifier=''): 31 | """Return a gdb.Breakpoint object 32 | br('main.cpp:2') # break main.cpp:2 33 | # break 2 thread 2 if result != NULL 34 | br('2', threadnum=2, condition='result != NULL') 35 | # threadnum can be the name of specific thread. 36 | # We only set breakpoint on single thread, so make sure this name is unique. 37 | br('2', threadnum='foo') 38 | 39 | # break 2 and run the given function `callback` 40 | br('2', commands=callback) 41 | 42 | # temporary break 43 | br('2', temporary=True) 44 | """ 45 | if commands is not None: 46 | if not hasattr(commands, '__call__'): 47 | raise TypeError('commands argument should be a function') 48 | if threadnum != '': 49 | if isinstance(threadnum, str): 50 | thread_name = find_first_threadnum_with_name(threadnum) 51 | if thread_name is None: 52 | raise gdb.GdbError('Given thread name is not found') 53 | threadnum = 'thread %d' % threadnum 54 | if condition != '': 55 | condition = 'if ' + condition 56 | if temporary: 57 | gdb.execute('tbreak ' + args_to_string( 58 | probe_modifier, location, threadnum, condition)) 59 | else: 60 | gdb.execute('break ' + args_to_string( 61 | probe_modifier, location, threadnum, condition)) 62 | if commands is not None: 63 | bp = get_last_breakpoint() 64 | register_callback_to_breakpoint_num(bp.number, commands) 65 | return bp 66 | return get_last_breakpoint() 67 | 68 | def clear(*args): 69 | "clear('main.cpp:11')" 70 | gdb.execute('clear %s' % args_to_string(*args)) 71 | 72 | def commands(callback, breakpoint_num=None, remove=False): 73 | """If `breakpoint_num` is not given, add callback to last breakpoint, 74 | else add to specific breakpoint. 75 | If`remove` is True, remove the callback instead of adding it.""" 76 | if remove is False: 77 | if breakpoint_num is None: 78 | bp = get_last_breakpoint() 79 | if bp is None: 80 | raise gdb.GdbError('No breakpoints specified') 81 | breakpoint_num = bp.number 82 | register_callback_to_breakpoint_num(breakpoint_num, callback) 83 | else: 84 | if breakpoint_num is None: 85 | bp = get_last_breakpoint() 86 | if bp is None: 87 | raise gdb.GdbError('No breakpoints specified') 88 | breakpoint_num = bp.number 89 | remove_callback_to_breakpoint_num(breakpoint_num, callback) 90 | 91 | 92 | _define_template = """\ 93 | import gdb 94 | class {classname}(gdb.Command): 95 | \"\"\"{docstring}\"\"\" 96 | def __init__(self): 97 | super(self.__class__, self).__init__("{cmdname}", gdb.COMMAND_USER) 98 | 99 | @classmethod 100 | def cmd(argv, from_tty): 101 | raise NotImplementError('cmd') 102 | 103 | def invoke(self, args, from_tty): 104 | argv = gdb.string_to_argv(args) 105 | {classname}.cmd(argv, from_tty) 106 | {classname}() 107 | """ 108 | def define(cmd): 109 | """ 110 | Define an user command with given function. We will forward two arguments 111 | to this function, first is argv(list of parameters), second is `from_tty`. 112 | 113 | # move.py 114 | import gdb_utils 115 | def move(argv, tty): 116 | "Move a breakpoint to other location." 117 | if len(argv) != 2: 118 | raise gdb.GdbError('Expect two arguments, %d given' % len(argv)) 119 | gdb.execute('delete ' + argv[0]) 120 | gdb.execute('break ' + argv[1]) 121 | gdb_utils.define(move) 122 | 123 | (gdb) so move.py 124 | (gdb) help move 125 | Move a breakpoint to other location. 126 | (gdb) move 100 main.cpp:22 127 | """ 128 | return eval_template(_define_template, cmd) 129 | 130 | 131 | def delete(*args): 132 | """ 133 | delete() # delete all breakpoints 134 | delete('1') # delete breakpoint 1 135 | delete('bookmark', 1) # delete bookmark 1 136 | delete(gdb.Breakpoint) # delete given Breakpoint object 137 | """ 138 | if len(args) == 0: 139 | gdb.execute('delete') 140 | elif isinstance(args[0], gdb.Breakpoint): 141 | args[0].delete() 142 | else: 143 | gdb.execute('delete ' + args_to_string(*filter(None, args))) 144 | 145 | def disable(*args): 146 | "Similar to gdb command 'disable'." 147 | gdb.execute('disable ' + args_to_string(*args)) 148 | 149 | def enable(*args): 150 | "Similar to gdb command 'enable'." 151 | gdb.execute('enable ' + args_to_string(*args)) 152 | 153 | def info(entry, *args): 154 | """In most cases, simply execute given arguments 155 | and return results as string. 156 | When `entry` is: 157 | * locals/args: Return dict{variable=value} 158 | * breakpoints: Return a list of gdb.Breakpoint 159 | * threads: Return a list of (is_current_thread, num, ptid, name, frame) 160 | """ 161 | if entry.startswith(('ar', 'lo')): 162 | info = gdb.execute('info ' + entry, to_string=True).splitlines() 163 | # No arguments or No locals 164 | if len(info) == 1 and info[0].startswith('No '): 165 | return {} 166 | group = {} 167 | for line in info: 168 | variable, _, value = line.partition('=') 169 | group[variable.rstrip()] = value.lstrip() 170 | return group 171 | elif entry.startswith('b'): 172 | return gdb.breakpoints() 173 | elif entry.startswith('th'): 174 | return info_threads(*args) 175 | return gdb.execute( 176 | 'info %s %s' % (entry, args_to_string(*args)), to_string=True) 177 | 178 | def thread(*args): 179 | """ 180 | thread(1) # switch to thread 1 181 | # switch to the thread which has name a.out and with the highest id 182 | thread('a.out') 183 | """ 184 | if isinstance(args[0], str) and args[0] not in ('apply', 'find', 'name'): 185 | threadnum = find_first_threadnum_with_name(args[0]) 186 | if threadnum is not None: 187 | gdb.execute('thread %d' % threadnum) 188 | else: 189 | gdb.execute('thread %s' % args_to_string(*args)) 190 | 191 | def thread_name(name, threadnum=None): 192 | """Set name to thread `threadnum`. 193 | If threadnum is not given, set name to current thread. 194 | For example: threadnum('foo', 2) will set thread 2's name to 'foo'.""" 195 | if threadnum is not None: 196 | threads = info_threads() 197 | for th in threads: 198 | if th[1] == threadnum: 199 | original_thread_num = gdb.selected_thread().num 200 | gdb.execute('thread %d' % th[1], to_string=True) 201 | current_thread = gdb.selected_thread() 202 | current_thread.name = name 203 | gdb.execute('thread %d' % original_thread_num, to_string=True) 204 | else: 205 | gdb.execute('thread name %s' % name) 206 | 207 | def watch(expression, condition='', commands=None): 208 | """ 209 | # watch array.len if size > 20 210 | watch('array.len', condition='size > 20')""" 211 | if commands is not None: 212 | if not hasattr(commands, '__call__'): 213 | raise TypeError('commands argument should be a function') 214 | if condition != '': 215 | condition = 'if ' + condition 216 | gdb.execute('watch %s %s' % (expression, condition)) 217 | if commands is not None: 218 | bp = get_last_breakpoint() 219 | register_callback_to_breakpoint_num(bp.number, commands) 220 | return bp 221 | return get_last_breakpoint() 222 | 223 | 224 | # Other API 225 | def get_breakpoint(location=None, expression=None, condition=None, number=None): 226 | """Return last breakpoint if not arguments given, 227 | or the breakpoint with given number if `number` given, 228 | or the first breakpoint matched given 229 | `location`(for `breakpoint`)/`expression`(for watchpoint) and `condition`. 230 | 231 | If there is no any breakpoint, 232 | raise gdb.GdbError('No breakpoints or watchpoints.') 233 | If there is no matched breakpoint, return None. 234 | """ 235 | if location is None and expression is None and number is None: 236 | return get_last_breakpoint() 237 | bps = gdb.breakpoints() 238 | if bps is None: raise gdb.GdbError('No breakpoints or watchpoints.') 239 | 240 | if number is not None: 241 | for bp in bps: 242 | if bp.number == number: 243 | return bp 244 | else: 245 | for bp in bps: 246 | if (bp.location == location and bp.expression == expression and 247 | bp.condition == condition): 248 | return bp 249 | return None 250 | 251 | _function_template = """\ 252 | import gdb 253 | class {classname}(gdb.Function): 254 | \"\"\"{docstring}\"\"\" 255 | def __init__(self): 256 | super(self.__class__, self).__init__("{cmdname}") 257 | 258 | @classmethod 259 | def cmd(*args): 260 | raise NotImplementError('func') 261 | 262 | def invoke(self, *args): 263 | return {classname}.cmd(*args) 264 | {classname}() 265 | """ 266 | def function(func): 267 | """Define a gdb convenience function with user specific function. 268 | 269 | # greet.py 270 | import gdb_utils 271 | def greet(name): 272 | "The `name` argument will be a gdb.Value" 273 | return "Hello, %s" % name.string() 274 | gdb_utils.function(func) 275 | 276 | (gdb) so greet.py 277 | (gdb) p $greet("World") 278 | $1 = "Hello World" 279 | """ 280 | return eval_template(_function_template, func) 281 | 282 | 283 | def stop(callback, breakpoint=None, remove=False): 284 | """Run callback while gdb stops on breakpoints. 285 | If `breakpoint` is given, run it while specific breakpoint is hit. 286 | If `remove` is True, remove callback instead of adding it. 287 | """ 288 | if not remove: 289 | if isinstance(breakpoint, gdb.Breakpoint): 290 | register_callback_to_breakpoint_num(breakpoint.number, callback) 291 | else: 292 | gdb.events.stop.connect(callback) 293 | else: 294 | if isinstance(breakpoint, gdb.Breakpoint): 295 | remove_callback_to_breakpoint_num(breakpoint.number, callback) 296 | else: 297 | gdb.events.stop.disconnect(callback) 298 | 299 | 300 | TYPE_CACHE = {} 301 | def ty(typename): 302 | """Return a gdb.Type object represents given `typename`. 303 | For example, x.cast(ty('Buffer'))""" 304 | if typename in TYPE_CACHE: 305 | return TYPE_CACHE[typename] 306 | 307 | m = re.match(r"^(\S*)\s*[*|&]$", typename) 308 | if m is None: 309 | tp = gdb.lookup_type(typename) 310 | else: 311 | if m.group(1).endswith('*'): 312 | tp = gdb.lookup_type().pointer() 313 | else: 314 | tp = gdb.lookup_type().reference() 315 | TYPE_CACHE[typename] = tp 316 | return tp 317 | 318 | def globval(var): 319 | """Get global `var`'s value""" 320 | return gdb.lookup_global_symbol(var).value() 321 | 322 | def register_pprinter(pprinter, pattern): 323 | """Register given pprinter to class matched given pattern.""" 324 | if not hasattr(pprinter, 'to_string'): 325 | raise gdb.GdbError( 326 | 'A pretty printer should implement `to_string` method.') 327 | pp = (lambda val: pprinter(val) 328 | if re.match(pattern, str(val.type)) else None) 329 | # Set a name so that we can enable/disable with its name 330 | pp.__name__ = pprinter.__name__ 331 | gdb.pretty_printers.append(pp) 332 | 333 | 334 | # Helpers 335 | def str_except_none(arg): 336 | if arg is None: 337 | return '' 338 | return str(arg) 339 | 340 | def args_to_string(*args): 341 | return ' '.join(map(str_except_none, args)) 342 | 343 | def get_last_breakpoint(): 344 | bps = gdb.breakpoints() 345 | if bps is None: raise gdb.GdbError('No breakpoints or watchpoints.') 346 | return bps[-1] 347 | 348 | STOP_EVENT_REGISTER = defaultdict(set) 349 | def register_callback_to_breakpoint_num(breakpoint_num, callback): 350 | STOP_EVENT_REGISTER[breakpoint_num].add(callback) 351 | 352 | def remove_callback_to_breakpoint_num(breakpoint_num, callback): 353 | if callback in STOP_EVENT_REGISTER[breakpoint_num]: 354 | STOP_EVENT_REGISTER[breakpoint_num].remove(callback) 355 | 356 | def trigger_registered_callback(num): 357 | if num in STOP_EVENT_REGISTER: 358 | for cb in STOP_EVENT_REGISTER[num]: 359 | cb() 360 | 361 | def stop_handler(event): 362 | if isinstance(event, gdb.BreakpointEvent): 363 | for bp in event.breakpoints: 364 | trigger_registered_callback(bp.number) 365 | 366 | gdb.events.stop.connect(stop_handler) 367 | 368 | def info_threads(*args): 369 | info = gdb.execute('info threads %s' % args_to_string(*args), 370 | to_string=True).splitlines() 371 | if len(info) == 1 and info[0].startswith('No '): 372 | return [] 373 | group = [] 374 | for line in info[1:]: 375 | is_current_thread = line[0] == '*' 376 | ids, _, others = line[2:].partition('"') 377 | idlist = ids.split() 378 | num = int(idlist[0]) 379 | ptid = " ".join(idlist[1:]).strip() 380 | name, _, frame = others.partition('"') 381 | group.append((is_current_thread, num, ptid, name, frame.strip())) 382 | return group 383 | 384 | def find_first_threadnum_with_name(name): 385 | threads = info_threads() 386 | for th in threads: 387 | if th[3] == name: 388 | return th[1] 389 | return None 390 | 391 | def eval_template(template, cmd): 392 | cmdname = cmd.__name__ 393 | classname = to_classname(cmd.__name__) 394 | # Use the same technic as namedtuple 395 | class_definition = template.format( 396 | cmdname = cmdname, 397 | docstring = cmd.__doc__ if cmd.__doc__ is not None else '', 398 | classname = classname 399 | ) 400 | namespace = dict(__name__='template_%s' % cmdname) 401 | exec(class_definition, namespace) 402 | result = namespace[classname] 403 | result.cmd = cmd 404 | result._source = class_definition 405 | try: 406 | result.__module__ = sys._getframe(1).f_globals.get( 407 | '__name__', '__main__') 408 | except (AttributeError, ValueError): 409 | pass 410 | return result 411 | 412 | 413 | _pp_template = """\ 414 | class {classname}: 415 | def __init__(self, val): 416 | self.val = val 417 | 418 | @classmethod 419 | def _to_string(val): 420 | raise NotImplementError('to_string') 421 | 422 | def to_string(self): 423 | return {classname}._to_string(self.val) 424 | """ 425 | _display_hint_snippet = """ 426 | def display_hint(self): 427 | raise NotImplementError('display_hint') 428 | """ 429 | _children_snippet = """ 430 | @classmethod 431 | def _children(val): 432 | raise NotImplementError('children') 433 | 434 | def children(self): 435 | return {classname}._children(self.val) 436 | """ 437 | def build_pprinter(to_string, display_hint=None, children=None): 438 | """Build a pretty printer. 439 | For example: 440 | def buffer_pretty_printer(val): 441 | return "size: %d\n" % self.val['size'] 442 | def children(val): 443 | return [] 444 | pp = build_pprinter(buffer_pretty_printer, display_hint='array', 445 | children=children) 446 | 447 | ... is equal to: 448 | 449 | class BufferPrettyPrinter: 450 | def __init__(self, val): 451 | self.val = val 452 | def to_string(self): 453 | return "size: %d\n" % self.val['size'] 454 | def children(self): 455 | return [] 456 | def display_hint(self): 457 | return 'array' 458 | pp = BufferPrettyPrinter 459 | """ 460 | classname = to_classname(to_string.__name__) 461 | template = _pp_template 462 | if display_hint is not None: 463 | template += _display_hint_snippet 464 | if children is not None: 465 | template += _children_snippet 466 | class_definition = template.format( 467 | classname = classname 468 | ) 469 | namespace = dict(__name__='template_%s' % classname) 470 | exec(class_definition, namespace) 471 | result = namespace[classname] 472 | 473 | result._to_string = to_string 474 | if display_hint is not None: 475 | result.display_hint = lambda self: display_hint 476 | if children is not None: 477 | result._children = children 478 | 479 | result._source = class_definition 480 | try: 481 | result.__module__ = sys._getframe(1).f_globals.get( 482 | '__name__', '__main__') 483 | except (AttributeError, ValueError): 484 | pass 485 | return result 486 | 487 | def to_classname(name): 488 | return ''.join(word.capitalize() for word in name.split('_')) 489 | --------------------------------------------------------------------------------