├── README.md ├── autoload └── minimal_gdb.vim ├── dbg_data └── README ├── plugin └── minimal_gdb.vim └── pythonx └── mingdb.py /README.md: -------------------------------------------------------------------------------- 1 | # Minimal gdb 2 | 3 | Minimal gdb is a lightweight vim -> gdb broker which uses `.gdbinit` mechanism to export breakpoints from vim into gdb session. 4 | 5 | The plugin provides `b` shortcut which allows user to set breakpoints while in vim. Breakpoints are added to special file which is sourced from `~/.gdbinit` (this source magic will be performed on the first run). When gdb starts all breakpoints are getting exported. If you add more breakpoints after gdb was started you have to execute `syncbp` command in gdb to reexport breakpoints from vim. `syncbp` is added to gdb via the same .gdbinit magic. 6 | 7 | The plugin doesn't provide functionality for debugging in vim window. You have to start gdb session. 8 | 9 | The main difference from other gdb vim plugins, is that Minimal gdb uses the `.gdbinit` file for breakpoint export and doesn't provide functionality for debugging inside vim window. 10 | 11 | #### A typical use case looks like this: 12 | 1. Set some breakpoints in vim, they will be highlighted in the 'sign' column 13 | 2. Run gdb, which will automatically export the breakpoints from step 1. 14 | 3. Set some more breakpoints in vim 15 | 4. Export them in gdb by using `syncbp` command, or by restarting the debugger (the former is easier). 16 | 17 | ## INSTALLATION: 18 | Copy the files to your .vim folder or use Vundle. 19 | The script will configure everything when you set a first breakpoint. 20 | 21 | ## COMMANDS: 22 | ### In vim: 23 | * `MinGDBToggleBP` or `b` - toggles a breakpoint. 24 | * `MinGDBDeleteAll` - delete all breakpoints. 25 | * `MinGDBListAll` - list all breakpoints in the quickfix window. 26 | * `MinGDBRefreshFile` - refresh breakpoints positions in a vim file. Use this in case something went wrong. 27 | 28 | ### In gdb: 29 | * `syncbp` - export new breakpoints from vim, which were set after gdb session has started. 30 | 31 | 32 | ## REQUIREMENTS: 33 | * gdb 34 | * python 2.7, or 3.xx 35 | * vim compiled with python and signs features. 36 | 37 | -------------------------------------------------------------------------------- /autoload/minimal_gdb.vim: -------------------------------------------------------------------------------- 1 | let s:python_env_initialized = 0 2 | let s:debug_session_is_active_cache_flag = 1 3 | let s:max_bp_age = 48 4 | 5 | "TODO make this script an ft plugin 6 | 7 | function! s:EnsurePythonInitialization() 8 | if (s:python_env_initialized) 9 | return 10 | endif 11 | if has("python3") 12 | py3 import sys 13 | py3 import vim 14 | py3 import mingdb 15 | py3 mingdb.InitCacheFlag() 16 | if exists("g:mingdb_gdbinit_path") 17 | py3 mingdb.GDB_INIT_PATH = vim.eval('expand(g:mingdb_gdbinit_path)') 18 | endif 19 | else 20 | py import sys 21 | py import vim 22 | py import mingdb 23 | py mingdb.InitCacheFlag() 24 | if exists("g:mingdb_gdbinit_path") 25 | py mingdb.GDB_INIT_PATH = vim.eval('expand(g:mingdb_gdbinit_path)') 26 | endif 27 | endif 28 | "if pyeval('mingdb.DatabaseIsEmpty()') 29 | " let s:debug_session_is_active_cache_flag = 0 30 | "endif 31 | let s:python_env_initialized = 1 32 | endfunction 33 | 34 | function minimal_gdb#toggle() 35 | if &modified 36 | echohl ErrorMsg | echo "Error. Buffer has unsaved changes. Cannot set the breakpoint. Please save the file and retry" | echohl None 37 | return 38 | endif 39 | let max_age = s:max_bp_age 40 | if exists("g:mingdb_bp_max_age") 41 | let max_age = g:mingdb_bp_max_age 42 | endif 43 | let lineno = line(".") 44 | let filename = expand("%:p") 45 | call s:EnsurePythonInitialization() 46 | if has("python3") 47 | py3 mingdb.ToggleBreakpoint(vim.eval('filename'), int(vim.eval('lineno')), int(vim.eval('max_age'))) 48 | else 49 | py mingdb.ToggleBreakpoint(vim.eval('filename'), int(vim.eval('lineno')), int(vim.eval('max_age'))) 50 | endif 51 | let s:debug_session_is_active_cache_flag = 1 52 | redraw! 53 | endfunction 54 | 55 | function minimal_gdb#delete_all() 56 | call s:EnsurePythonInitialization() 57 | if has("python3") 58 | py3 mingdb.DeleteAllBreakpoints() 59 | else 60 | py mingdb.DeleteAllBreakpoints() 61 | endif 62 | redraw! 63 | endfunction 64 | 65 | function minimal_gdb#list_all() 66 | call s:EnsurePythonInitialization() 67 | if has("python3") 68 | py3 mingdb.ListAllBreakpoints() 69 | else 70 | py mingdb.ListAllBreakpoints() 71 | endif 72 | endfunction 73 | 74 | function minimal_gdb#show_breakpoints() 75 | if (!s:debug_session_is_active_cache_flag) 76 | return 77 | endif 78 | call s:EnsurePythonInitialization() 79 | let filename = expand("%:p") 80 | if has("python3") 81 | py3 mingdb.ShowBreakpointsInFile(vim.eval('filename')) 82 | else 83 | py mingdb.ShowBreakpointsInFile(vim.eval('filename')) 84 | endif 85 | redraw! 86 | endfunction 87 | -------------------------------------------------------------------------------- /dbg_data/README: -------------------------------------------------------------------------------- 1 | This directory contains minimal_gdb generated files. Do not edit them manually! 2 | -------------------------------------------------------------------------------- /plugin/minimal_gdb.vim: -------------------------------------------------------------------------------- 1 | sign define mingdbtag text=<> texthl=Breakpoint 2 | 3 | function! MinGDBCheckFileType() 4 | if (&filetype != 'cpp' && &filetype != 'c') 5 | return 6 | endif 7 | cal minimal_gdb#show_breakpoints() 8 | endfunc 9 | 10 | com! MinGDBToggleBP cal minimal_gdb#toggle() 11 | com! MinGDBDeleteAll cal minimal_gdb#delete_all() 12 | com! MinGDBShowBreakpoints cal minimal_gdb#show_breakpoints() 13 | com! MinGDBRefreshFile MinGDBShowBreakpoints 14 | com! MinGDBListAll cal minimal_gdb#list_all() 15 | 16 | nnoremap b :MinGDBToggleBP 17 | autocmd BufRead * cal MinGDBCheckFileType() 18 | -------------------------------------------------------------------------------- /pythonx/mingdb.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | import os 5 | import optparse 6 | import errno 7 | from os.path import dirname 8 | import time 9 | 10 | #XXX Attention! there shouldn't be any unsaved changes in the file in which we want to set a breakpoint. 11 | 12 | 13 | _debug_mode = False 14 | 15 | 16 | def iterkeys6(x): 17 | if sys.version_info[0] < 3: 18 | return x.iterkeys() 19 | return list(x.keys()) 20 | 21 | def iteritems6(x): 22 | if sys.version_info[0] < 3: 23 | return x.iteritems() 24 | return list(x.items()) 25 | 26 | def itervalues6(x): 27 | if sys.version_info[0] < 3: 28 | return x.itervalues() 29 | return list(x.values()) 30 | 31 | 32 | BREAKPOINTS_DB_PATH = os.path.join(dirname(dirname(os.path.abspath(__file__))), 'dbg_data' ,'breakpoints.db') 33 | BREAKPOINTS_GDB_PATH = os.path.join(dirname(dirname(os.path.abspath(__file__))), 'dbg_data' ,'breakpoints.gdb') 34 | MIN_GDB_SETTINGS_PATH = os.path.join(dirname(dirname(os.path.abspath(__file__))), 'dbg_data' ,'min_settings.gdb') 35 | SCRIPT_SELF_PATH = os.path.abspath(__file__.rstrip('c')) 36 | GDB_INIT_PATH = os.path.join(os.path.expanduser('~'), '.gdbinit') 37 | BREAKPOINT_START_ID = 1000000 38 | 39 | class TBreakpoint: 40 | def __init__(self, time, maxAgeInHours, file, repeatNumber, line): 41 | self.Time = time 42 | self.MaxAgeInHours = maxAgeInHours 43 | self.File = file 44 | self.RepeatNumber = repeatNumber 45 | self.Line = line 46 | 47 | def __hash__(self): 48 | return hash((self.File, self.RepeatNumber, self.Line)) 49 | 50 | def __eq__(self, other): 51 | return (self.File, self.RepeatNumber, self.Line) == (other.File, other.RepeatNumber, other.Line) 52 | 53 | def __str__(self): 54 | return '\t'.join([str(self.Time), str(self.MaxAgeInHours), self.File, str(self.RepeatNumber), self.Line]) 55 | 56 | def IsExpired(self): 57 | if self.MaxAgeInHours <= 0: 58 | return False 59 | age = time.time() - self.Time 60 | if age > self.MaxAgeInHours * 3600: 61 | return True 62 | return False 63 | 64 | 65 | class TEntry: 66 | def __init__(self, id, breakpoint): 67 | self.Id = id 68 | self.Breakpoint = breakpoint 69 | 70 | 71 | @classmethod 72 | def from_string(cls, line): 73 | maxFieldNo = 5 74 | fields = line.split('\t', maxFieldNo) 75 | id = int(fields[0]) 76 | time = float(fields[1]) 77 | maxAgeInHours = int(fields[2]) 78 | file = fields[3] 79 | repeatNumber = int(fields[4]) 80 | line = fields[maxFieldNo] 81 | breakpoint = TBreakpoint(time, maxAgeInHours, file, repeatNumber, line) 82 | return cls(id, breakpoint) 83 | 84 | def __str__(self): 85 | return '\t'.join([str(self.Id), str(self.Breakpoint)]) 86 | 87 | 88 | def ReadBreakpoints(): 89 | result = dict() 90 | try: 91 | with open(BREAKPOINTS_DB_PATH, 'r') as f: 92 | for line in f: 93 | line = line.rstrip('\n') 94 | entry = TEntry.from_string(line) 95 | if not entry.Breakpoint.IsExpired(): 96 | result[entry.Breakpoint] = entry.Id 97 | except IOError as exc: 98 | if exc.errno != errno.ENOENT: 99 | raise 100 | return result 101 | 102 | 103 | def ListAllBreakpoints(): 104 | result = ReadBreakpoints() 105 | import tempfile 106 | f = tempfile.NamedTemporaryFile(mode='w', delete=False) 107 | filename = f.name 108 | for bp in result: 109 | lineNumber = RestoreLineNumber(bp) 110 | f.write(bp.File) 111 | f.write(':') 112 | f.write(str(lineNumber)) 113 | f.write(': ') 114 | f.write(bp.Line) 115 | f.write('\n') 116 | f.close() 117 | 118 | ExecuteVimCommand("cg {}".format(filename)) 119 | ExecuteVimCommand('copen') 120 | 121 | 122 | def GetMaxId(breakpoints): 123 | if not len(breakpoints): 124 | return BREAKPOINT_START_ID 125 | return max(itervalues6(breakpoints)) 126 | 127 | 128 | def RestoreLineNumber(breakpoint): 129 | try: 130 | with open(breakpoint.File, 'r') as f: 131 | repeatNumber = 0 132 | for iLine, line in enumerate(f, start = 1): 133 | line = line.rstrip('\n') 134 | if line == breakpoint.Line: 135 | if repeatNumber == breakpoint.RepeatNumber: 136 | return iLine 137 | repeatNumber += 1 138 | except IOError as exc: 139 | if exc.errno != errno.ENOENT: 140 | raise 141 | return None 142 | 143 | 144 | def DeleteAllBreakpoints(): 145 | breakpoints = ReadBreakpoints() 146 | for id in itervalues6(breakpoints): 147 | ExecuteVimCommand('sign unplace %d' % id) 148 | open(BREAKPOINTS_DB_PATH, 'w').close() 149 | 150 | 151 | def ShowBreakpointsInFile(fileName): 152 | breakpoints = ReadBreakpoints() 153 | breakpoints = dict((k, breakpoints[k]) for k in iterkeys6(breakpoints) if k.File == fileName) 154 | #unplacing all breakpoints by ids, to make sure there are no duplicates 155 | for id in itervalues6(breakpoints): 156 | ExecuteVimCommand('sign unplace %d' % id) 157 | for bp, id in iteritems6(breakpoints): 158 | lineNo = RestoreLineNumber(bp) 159 | if lineNo: 160 | ExecuteVimCommand('sign place %d line=%d name=mingdbtag file=%s' % (id, lineNo, bp.File)) 161 | 162 | 163 | def CommitBreakpoints(breakpoints): 164 | with open(BREAKPOINTS_DB_PATH, 'w') as f: 165 | for bp, id in iteritems6(breakpoints): 166 | entry = TEntry(id, bp) 167 | f.write(str(entry) + '\n') 168 | 169 | 170 | def ExecuteVimCommand(cmd): 171 | if _debug_mode: 172 | print(cmd) 173 | else: 174 | import vim 175 | vim.command(cmd) 176 | 177 | 178 | def GetLineTextAndRepeatNumber(fileName, lineNo): 179 | with open(fileName) as f: 180 | content = f.readlines() 181 | lineText = content[lineNo - 1].rstrip('\n') 182 | enumeratedContent = [(lineno, line.rstrip('\n')) for lineno, line in enumerate(content, start = 1)] 183 | repeatedLines = [rl for rl in enumeratedContent if rl[1] == lineText] 184 | repeatNumber = repeatedLines.index((lineNo, lineText)) 185 | return (lineText, repeatNumber) 186 | 187 | 188 | def ToggleBreakpoint(fileName, lineNo, maxAgeInHours = 0): 189 | assert (fileName.find('\t') == -1) 190 | lineText, repeatNumber = GetLineTextAndRepeatNumber(fileName, lineNo) 191 | newBreakpoint = TBreakpoint(time.time(), maxAgeInHours, fileName, repeatNumber, lineText) 192 | breakpoints = ReadBreakpoints() 193 | if newBreakpoint in breakpoints: 194 | oldBpId = breakpoints[newBreakpoint] 195 | del breakpoints[newBreakpoint] 196 | ExecuteVimCommand('sign unplace %d' % oldBpId) 197 | else: 198 | newBpId = GetMaxId(breakpoints) + 1 199 | breakpoints[newBreakpoint] = newBpId 200 | ExecuteVimCommand('sign place %d line=%d name=mingdbtag file=%s' % (newBpId, lineNo, fileName)) 201 | EnsureDebugEnvironment() 202 | CommitBreakpoints(breakpoints) 203 | 204 | 205 | def ExportBreakpoints(): 206 | breakpoints = ReadBreakpoints() 207 | with open(BREAKPOINTS_GDB_PATH, 'w') as f: 208 | for bp, id in iteritems6(breakpoints): 209 | lineNo = RestoreLineNumber(bp) 210 | if lineNo: 211 | f.write('break %s:%d\n' % (bp.File, lineNo)) 212 | 213 | 214 | def PatchGdbInit(): 215 | lines = [] 216 | try: 217 | with open(GDB_INIT_PATH, 'r') as f: 218 | lines = f.readlines() 219 | except IOError as exc: 220 | if exc.errno != errno.ENOENT: 221 | raise 222 | 223 | settings_spell = 'source {}\n'.format(MIN_GDB_SETTINGS_PATH) 224 | if settings_spell in lines: 225 | return 226 | 227 | with open(GDB_INIT_PATH, 'w') as f: 228 | for line in lines: 229 | if line.find('source') != -1 and line.find('min_settings.gdb') != -1: 230 | continue 231 | f.write(line) 232 | f.write('\n') 233 | f.write(settings_spell) 234 | 235 | 236 | 237 | GDB_SETTINGS_CONTENT = """ 238 | define syncbp 239 | delete 240 | set breakpoint pending on 241 | shell %s -e 242 | source %s 243 | set breakpoint pending off 244 | end 245 | 246 | syncbp 247 | """ 248 | 249 | def EnsureDebugEnvironment(): 250 | with open(MIN_GDB_SETTINGS_PATH, 'w') as f: 251 | f.write(GDB_SETTINGS_CONTENT % (SCRIPT_SELF_PATH, BREAKPOINTS_GDB_PATH)) 252 | PatchGdbInit() 253 | 254 | 255 | def DatabaseIsEmpty(): 256 | breakpoints = ReadBreakpoints() 257 | return (len(breakpoints) == 0) 258 | 259 | def InitCacheFlag(): 260 | if DatabaseIsEmpty(): 261 | ExecuteVimCommand("let s:debug_session_is_active_cache_flag = 0") 262 | 263 | 264 | def main(): 265 | parser = optparse.OptionParser("%prog [options]") 266 | parser.add_option('-D', action='store_true', dest='debug', help='debug script', default=False) 267 | parser.add_option('-B', action='store_true', dest='breakpoint', help='toggle breakpoint', default=False) 268 | parser.add_option('-b', action='store', dest='database', help='breakpoints database') 269 | parser.add_option('-d', action='store_true', dest='delete', help='delete all breakpoints', default=False) 270 | parser.add_option('-s', action='store_true', dest='show', help='show breakpoints in file', default=False) 271 | parser.add_option('-f', action='store', dest='file', help='file name (in bp toggle mode)') 272 | parser.add_option('-n', action='store', dest='lineno', help='line no, 1-based (in bp toggle mode)', type='int') 273 | parser.add_option('-e', action='store_true', dest='export', help='export breakpoints', default=False) 274 | parser.add_option('-c', action='store_true', dest='check', help='check whether database is empty', default=False) 275 | parser.add_option('-m', action='store', dest='age', help='breakpoint max age (time to keep) in hours', type='int', default=0) 276 | (options, args) = parser.parse_args() 277 | 278 | if options.database: 279 | global BREAKPOINTS_DB_PATH 280 | BREAKPOINTS_DB_PATH = options.database 281 | 282 | if options.check: 283 | result = 'empty' if DatabaseIsEmpty() else 'full' 284 | print(result) 285 | return 286 | 287 | global _debug_mode 288 | _debug_mode = options.debug 289 | 290 | if options.breakpoint: 291 | ToggleBreakpoint(options.file, options.lineno, options.age) 292 | return 293 | 294 | if options.delete: 295 | DeleteAllBreakpoints() 296 | return 297 | 298 | if options.show: 299 | ShowBreakpointsInFile(options.file) 300 | return 301 | 302 | if options.export: 303 | ExportBreakpoints() 304 | return 305 | 306 | if __name__ == '__main__': 307 | main() 308 | --------------------------------------------------------------------------------