1fe386641SBram Moolenaar" Debugger plugin using gdb. 2c572da5fSBram Moolenaar" 3c572da5fSBram Moolenaar" WORK IN PROGRESS - much doesn't work yet 4c572da5fSBram Moolenaar" 5fe386641SBram Moolenaar" Open two visible terminal windows: 6c572da5fSBram Moolenaar" 1. run a pty, as with ":term NONE" 7c572da5fSBram Moolenaar" 2. run gdb, passing the pty 8fe386641SBram Moolenaar" The current window is used to view source code and follows gdb. 9fe386641SBram Moolenaar" 10fe386641SBram Moolenaar" A third terminal window is hidden, it is used for communication with gdb. 11fe386641SBram Moolenaar" 12fe386641SBram Moolenaar" The communication with gdb uses GDB/MI. See: 13fe386641SBram Moolenaar" https://sourceware.org/gdb/current/onlinedocs/gdb/GDB_002fMI.html 14c572da5fSBram Moolenaar" 15c572da5fSBram Moolenaar" Author: Bram Moolenaar 16fe386641SBram Moolenaar" Copyright: Vim license applies, see ":help license" 17c572da5fSBram Moolenaar 18fe386641SBram Moolenaar" The command that starts debugging, e.g. ":Termdebug vim". 19fe386641SBram Moolenaar" To end type "quit" in the gdb window. 20c572da5fSBram Moolenaarcommand -nargs=* -complete=file Termdebug call s:StartDebug(<q-args>) 21c572da5fSBram Moolenaar 22fe386641SBram Moolenaar" Name of the gdb command, defaults to "gdb". 23e09ba7baSBram Moolenaarif !exists('termdebugger') 24e09ba7baSBram Moolenaar let termdebugger = 'gdb' 25c572da5fSBram Moolenaarendif 26c572da5fSBram Moolenaar 27fe386641SBram Moolenaarlet s:pc_id = 12 28e09ba7baSBram Moolenaarlet s:break_id = 13 29e09ba7baSBram Moolenaar 30e09ba7baSBram Moolenaarif &background == 'light' 31e09ba7baSBram Moolenaar hi default debugPC term=reverse ctermbg=lightblue guibg=lightblue 32e09ba7baSBram Moolenaarelse 33e09ba7baSBram Moolenaar hi default debugPC term=reverse ctermbg=darkblue guibg=darkblue 34e09ba7baSBram Moolenaarendif 35e09ba7baSBram Moolenaarhi default debugBreakpoint term=reverse ctermbg=red guibg=red 36fe386641SBram Moolenaar 37c572da5fSBram Moolenaarfunc s:StartDebug(cmd) 38fe386641SBram Moolenaar let s:startwin = win_getid(winnr()) 39fe386641SBram Moolenaar let s:startsigncolumn = &signcolumn 40fe386641SBram Moolenaar 41*38baa3e6SBram Moolenaar if exists('g:termdebug_wide') && &columns < g:termdebug_wide 42*38baa3e6SBram Moolenaar let s:save_columns = &columns 43*38baa3e6SBram Moolenaar let &columns = g:termdebug_wide 44*38baa3e6SBram Moolenaar let vertical = 1 45*38baa3e6SBram Moolenaar else 46*38baa3e6SBram Moolenaar let s:save_columns = 0 47*38baa3e6SBram Moolenaar let vertical = 0 48*38baa3e6SBram Moolenaar endif 49*38baa3e6SBram Moolenaar 50c572da5fSBram Moolenaar " Open a terminal window without a job, to run the debugged program 51fe386641SBram Moolenaar let s:ptybuf = term_start('NONE', { 52fe386641SBram Moolenaar \ 'term_name': 'gdb program', 53*38baa3e6SBram Moolenaar \ 'vertical': vertical, 54fe386641SBram Moolenaar \ }) 55fe386641SBram Moolenaar if s:ptybuf == 0 56fe386641SBram Moolenaar echoerr 'Failed to open the program terminal window' 57fe386641SBram Moolenaar return 58fe386641SBram Moolenaar endif 59fe386641SBram Moolenaar let pty = job_info(term_getjob(s:ptybuf))['tty_out'] 6045d5f26dSBram Moolenaar let s:ptywin = win_getid(winnr()) 61fe386641SBram Moolenaar 62fe386641SBram Moolenaar " Create a hidden terminal window to communicate with gdb 63fe386641SBram Moolenaar let s:commbuf = term_start('NONE', { 64fe386641SBram Moolenaar \ 'term_name': 'gdb communication', 65fe386641SBram Moolenaar \ 'out_cb': function('s:CommOutput'), 66fe386641SBram Moolenaar \ 'hidden': 1, 67fe386641SBram Moolenaar \ }) 68fe386641SBram Moolenaar if s:commbuf == 0 69fe386641SBram Moolenaar echoerr 'Failed to open the communication terminal window' 70fe386641SBram Moolenaar exe 'bwipe! ' . s:ptybuf 71fe386641SBram Moolenaar return 72fe386641SBram Moolenaar endif 73fe386641SBram Moolenaar let commpty = job_info(term_getjob(s:commbuf))['tty_out'] 74c572da5fSBram Moolenaar 75c572da5fSBram Moolenaar " Open a terminal window to run the debugger. 76e09ba7baSBram Moolenaar let cmd = [g:termdebugger, '-tty', pty, a:cmd] 77c572da5fSBram Moolenaar echomsg 'executing "' . join(cmd) . '"' 78c572da5fSBram Moolenaar let gdbbuf = term_start(cmd, { 79c572da5fSBram Moolenaar \ 'exit_cb': function('s:EndDebug'), 80fe386641SBram Moolenaar \ 'term_finish': 'close', 81c572da5fSBram Moolenaar \ }) 82fe386641SBram Moolenaar if gdbbuf == 0 83fe386641SBram Moolenaar echoerr 'Failed to open the gdb terminal window' 84fe386641SBram Moolenaar exe 'bwipe! ' . s:ptybuf 85fe386641SBram Moolenaar exe 'bwipe! ' . s:commbuf 86fe386641SBram Moolenaar return 87fe386641SBram Moolenaar endif 8845d5f26dSBram Moolenaar let s:gdbwin = win_getid(winnr()) 89fe386641SBram Moolenaar 90fe386641SBram Moolenaar " Connect gdb to the communication pty, using the GDB/MI interface 91fe386641SBram Moolenaar call term_sendkeys(gdbbuf, 'new-ui mi ' . commpty . "\r") 92e09ba7baSBram Moolenaar 93*38baa3e6SBram Moolenaar " Sign used to highlight the line where the program has stopped. 94*38baa3e6SBram Moolenaar " There can be only one. 95*38baa3e6SBram Moolenaar sign define debugPC linehl=debugPC 96*38baa3e6SBram Moolenaar 97*38baa3e6SBram Moolenaar " Sign used to indicate a breakpoint. 98*38baa3e6SBram Moolenaar " Can be used multiple times. 99*38baa3e6SBram Moolenaar sign define debugBreakpoint text=>> texthl=debugBreakpoint 100*38baa3e6SBram Moolenaar 10145d5f26dSBram Moolenaar " Install debugger commands in the text window. 10245d5f26dSBram Moolenaar call win_gotoid(s:startwin) 103e09ba7baSBram Moolenaar call s:InstallCommands() 10445d5f26dSBram Moolenaar call win_gotoid(s:gdbwin) 105e09ba7baSBram Moolenaar 106e09ba7baSBram Moolenaar let s:breakpoints = {} 107c572da5fSBram Moolenaarendfunc 108c572da5fSBram Moolenaar 109c572da5fSBram Moolenaarfunc s:EndDebug(job, status) 110c572da5fSBram Moolenaar exe 'bwipe! ' . s:ptybuf 111fe386641SBram Moolenaar exe 'bwipe! ' . s:commbuf 112e09ba7baSBram Moolenaar 113e09ba7baSBram Moolenaar let curwinid = win_getid(winnr()) 114e09ba7baSBram Moolenaar 115e09ba7baSBram Moolenaar call win_gotoid(s:startwin) 116e09ba7baSBram Moolenaar let &signcolumn = s:startsigncolumn 117e09ba7baSBram Moolenaar call s:DeleteCommands() 118e09ba7baSBram Moolenaar 119e09ba7baSBram Moolenaar call win_gotoid(curwinid) 120*38baa3e6SBram Moolenaar if s:save_columns > 0 121*38baa3e6SBram Moolenaar let &columns = s:save_columns 122*38baa3e6SBram Moolenaar endif 123fe386641SBram Moolenaarendfunc 124fe386641SBram Moolenaar 125fe386641SBram Moolenaar" Handle a message received from gdb on the GDB/MI interface. 126fe386641SBram Moolenaarfunc s:CommOutput(chan, msg) 127fe386641SBram Moolenaar let msgs = split(a:msg, "\r") 128fe386641SBram Moolenaar 129fe386641SBram Moolenaar for msg in msgs 130fe386641SBram Moolenaar " remove prefixed NL 131fe386641SBram Moolenaar if msg[0] == "\n" 132fe386641SBram Moolenaar let msg = msg[1:] 133fe386641SBram Moolenaar endif 134fe386641SBram Moolenaar if msg != '' 135fe386641SBram Moolenaar if msg =~ '^\*\(stopped\|running\)' 136e09ba7baSBram Moolenaar call s:HandleCursor(msg) 13745d5f26dSBram Moolenaar elseif msg =~ '^\^done,bkpt=' || msg =~ '^=breakpoint-created,' 138e09ba7baSBram Moolenaar call s:HandleNewBreakpoint(msg) 139e09ba7baSBram Moolenaar elseif msg =~ '^=breakpoint-deleted,' 140e09ba7baSBram Moolenaar call s:HandleBreakpointDelete(msg) 14145d5f26dSBram Moolenaar elseif msg =~ '^\^done,value=' 14245d5f26dSBram Moolenaar call s:HandleEvaluate(msg) 14345d5f26dSBram Moolenaar elseif msg =~ '^\^error,msg=' 14445d5f26dSBram Moolenaar call s:HandleError(msg) 145e09ba7baSBram Moolenaar endif 146e09ba7baSBram Moolenaar endif 147e09ba7baSBram Moolenaar endfor 148e09ba7baSBram Moolenaarendfunc 149e09ba7baSBram Moolenaar 150e09ba7baSBram Moolenaar" Install commands in the current window to control the debugger. 151e09ba7baSBram Moolenaarfunc s:InstallCommands() 152e09ba7baSBram Moolenaar command Break call s:SetBreakpoint() 153e09ba7baSBram Moolenaar command Delete call s:DeleteBreakpoint() 154e09ba7baSBram Moolenaar command Step call s:SendCommand('-exec-step') 15545d5f26dSBram Moolenaar command Over call s:SendCommand('-exec-next') 156e09ba7baSBram Moolenaar command Finish call s:SendCommand('-exec-finish') 157e09ba7baSBram Moolenaar command Continue call s:SendCommand('-exec-continue') 15845d5f26dSBram Moolenaar command -range -nargs=* Evaluate call s:Evaluate(<range>, <q-args>) 15945d5f26dSBram Moolenaar command Gdb call win_gotoid(s:gdbwin) 16045d5f26dSBram Moolenaar command Program call win_gotoid(s:ptywin) 16145d5f26dSBram Moolenaar 16245d5f26dSBram Moolenaar " TODO: can the K mapping be restored? 16345d5f26dSBram Moolenaar nnoremap K :Evaluate<CR> 164e09ba7baSBram Moolenaarendfunc 165e09ba7baSBram Moolenaar 166e09ba7baSBram Moolenaar" Delete installed debugger commands in the current window. 167e09ba7baSBram Moolenaarfunc s:DeleteCommands() 168e09ba7baSBram Moolenaar delcommand Break 169e09ba7baSBram Moolenaar delcommand Delete 170e09ba7baSBram Moolenaar delcommand Step 17145d5f26dSBram Moolenaar delcommand Over 172e09ba7baSBram Moolenaar delcommand Finish 173e09ba7baSBram Moolenaar delcommand Continue 17445d5f26dSBram Moolenaar delcommand Evaluate 17545d5f26dSBram Moolenaar delcommand Gdb 17645d5f26dSBram Moolenaar delcommand Program 17745d5f26dSBram Moolenaar 17845d5f26dSBram Moolenaar nunmap K 17945d5f26dSBram Moolenaar exe 'sign unplace ' . s:pc_id 18045d5f26dSBram Moolenaar for key in keys(s:breakpoints) 18145d5f26dSBram Moolenaar exe 'sign unplace ' . (s:break_id + key) 18245d5f26dSBram Moolenaar endfor 183*38baa3e6SBram Moolenaar sign undefine debugPC 184*38baa3e6SBram Moolenaar sign undefine debugBreakpoint 18545d5f26dSBram Moolenaar unlet s:breakpoints 186e09ba7baSBram Moolenaarendfunc 187e09ba7baSBram Moolenaar 188e09ba7baSBram Moolenaar" :Break - Set a breakpoint at the cursor position. 189e09ba7baSBram Moolenaarfunc s:SetBreakpoint() 190e09ba7baSBram Moolenaar call term_sendkeys(s:commbuf, '-break-insert --source ' 191e09ba7baSBram Moolenaar \ . fnameescape(expand('%:p')) . ' --line ' . line('.') . "\r") 192e09ba7baSBram Moolenaarendfunc 193e09ba7baSBram Moolenaar 194e09ba7baSBram Moolenaar" :Delete - Delete a breakpoint at the cursor position. 195e09ba7baSBram Moolenaarfunc s:DeleteBreakpoint() 196e09ba7baSBram Moolenaar let fname = fnameescape(expand('%:p')) 197e09ba7baSBram Moolenaar let lnum = line('.') 198e09ba7baSBram Moolenaar for [key, val] in items(s:breakpoints) 199e09ba7baSBram Moolenaar if val['fname'] == fname && val['lnum'] == lnum 200e09ba7baSBram Moolenaar call term_sendkeys(s:commbuf, '-break-delete ' . key . "\r") 201e09ba7baSBram Moolenaar " Assume this always wors, the reply is simply "^done". 202e09ba7baSBram Moolenaar exe 'sign unplace ' . (s:break_id + key) 203e09ba7baSBram Moolenaar unlet s:breakpoints[key] 204e09ba7baSBram Moolenaar break 205e09ba7baSBram Moolenaar endif 206e09ba7baSBram Moolenaar endfor 207e09ba7baSBram Moolenaarendfunc 208e09ba7baSBram Moolenaar 209e09ba7baSBram Moolenaar" :Next, :Continue, etc - send a command to gdb 210e09ba7baSBram Moolenaarfunc s:SendCommand(cmd) 211e09ba7baSBram Moolenaar call term_sendkeys(s:commbuf, a:cmd . "\r") 212e09ba7baSBram Moolenaarendfunc 213e09ba7baSBram Moolenaar 21445d5f26dSBram Moolenaar" :Evaluate - evaluate what is under the cursor 21545d5f26dSBram Moolenaarfunc s:Evaluate(range, arg) 21645d5f26dSBram Moolenaar if a:arg != '' 21745d5f26dSBram Moolenaar let expr = a:arg 21845d5f26dSBram Moolenaar elseif a:range == 2 21945d5f26dSBram Moolenaar let pos = getcurpos() 22045d5f26dSBram Moolenaar let reg = getreg('v', 1, 1) 22145d5f26dSBram Moolenaar let regt = getregtype('v') 22245d5f26dSBram Moolenaar normal! gv"vy 22345d5f26dSBram Moolenaar let expr = @v 22445d5f26dSBram Moolenaar call setpos('.', pos) 22545d5f26dSBram Moolenaar call setreg('v', reg, regt) 22645d5f26dSBram Moolenaar else 22745d5f26dSBram Moolenaar let expr = expand('<cexpr>') 22845d5f26dSBram Moolenaar endif 22945d5f26dSBram Moolenaar call term_sendkeys(s:commbuf, '-data-evaluate-expression "' . expr . "\"\r") 23045d5f26dSBram Moolenaar let s:evalexpr = expr 23145d5f26dSBram Moolenaarendfunc 23245d5f26dSBram Moolenaar 23345d5f26dSBram Moolenaar" Handle the result of data-evaluate-expression 23445d5f26dSBram Moolenaarfunc s:HandleEvaluate(msg) 23545d5f26dSBram Moolenaar echomsg '"' . s:evalexpr . '": ' . substitute(a:msg, '.*value="\(.*\)"', '\1', '') 23645d5f26dSBram Moolenaarendfunc 23745d5f26dSBram Moolenaar 23845d5f26dSBram Moolenaar" Handle an error. 23945d5f26dSBram Moolenaarfunc s:HandleError(msg) 24045d5f26dSBram Moolenaar echoerr substitute(a:msg, '.*msg="\(.*\)"', '\1', '') 24145d5f26dSBram Moolenaarendfunc 24245d5f26dSBram Moolenaar 243e09ba7baSBram Moolenaar" Handle stopping and running message from gdb. 244e09ba7baSBram Moolenaar" Will update the sign that shows the current position. 245e09ba7baSBram Moolenaarfunc s:HandleCursor(msg) 246fe386641SBram Moolenaar let wid = win_getid(winnr()) 247fe386641SBram Moolenaar 248fe386641SBram Moolenaar if win_gotoid(s:startwin) 249e09ba7baSBram Moolenaar let fname = substitute(a:msg, '.*fullname="\([^"]*\)".*', '\1', '') 250*38baa3e6SBram Moolenaar if a:msg =~ '^\*stopped' && filereadable(fname) 251e09ba7baSBram Moolenaar let lnum = substitute(a:msg, '.*line="\([^"]*\)".*', '\1', '') 252fe386641SBram Moolenaar if lnum =~ '^[0-9]*$' 253fe386641SBram Moolenaar if expand('%:h') != fname 254fe386641SBram Moolenaar if &modified 255fe386641SBram Moolenaar " TODO: find existing window 256fe386641SBram Moolenaar exe 'split ' . fnameescape(fname) 257fe386641SBram Moolenaar let s:startwin = win_getid(winnr()) 258fe386641SBram Moolenaar else 259fe386641SBram Moolenaar exe 'edit ' . fnameescape(fname) 260fe386641SBram Moolenaar endif 261fe386641SBram Moolenaar endif 262fe386641SBram Moolenaar exe lnum 263fe386641SBram Moolenaar exe 'sign place ' . s:pc_id . ' line=' . lnum . ' name=debugPC file=' . fnameescape(fname) 264fe386641SBram Moolenaar setlocal signcolumn=yes 265fe386641SBram Moolenaar endif 266fe386641SBram Moolenaar else 267fe386641SBram Moolenaar exe 'sign unplace ' . s:pc_id 268fe386641SBram Moolenaar endif 269fe386641SBram Moolenaar 270fe386641SBram Moolenaar call win_gotoid(wid) 271fe386641SBram Moolenaar endif 272e09ba7baSBram Moolenaarendfunc 273e09ba7baSBram Moolenaar 274e09ba7baSBram Moolenaar" Handle setting a breakpoint 275e09ba7baSBram Moolenaar" Will update the sign that shows the breakpoint 276e09ba7baSBram Moolenaarfunc s:HandleNewBreakpoint(msg) 277e09ba7baSBram Moolenaar let nr = substitute(a:msg, '.*number="\([0-9]\)*\".*', '\1', '') + 0 278e09ba7baSBram Moolenaar if nr == 0 279e09ba7baSBram Moolenaar return 280fe386641SBram Moolenaar endif 281e09ba7baSBram Moolenaar 282e09ba7baSBram Moolenaar if has_key(s:breakpoints, nr) 283e09ba7baSBram Moolenaar let entry = s:breakpoints[nr] 284e09ba7baSBram Moolenaar else 285e09ba7baSBram Moolenaar let entry = {} 286e09ba7baSBram Moolenaar let s:breakpoints[nr] = entry 287fe386641SBram Moolenaar endif 288e09ba7baSBram Moolenaar 289e09ba7baSBram Moolenaar let fname = substitute(a:msg, '.*fullname="\([^"]*\)".*', '\1', '') 290e09ba7baSBram Moolenaar let lnum = substitute(a:msg, '.*line="\([^"]*\)".*', '\1', '') 291e09ba7baSBram Moolenaar 292e09ba7baSBram Moolenaar exe 'sign place ' . (s:break_id + nr) . ' line=' . lnum . ' name=debugBreakpoint file=' . fnameescape(fname) 293e09ba7baSBram Moolenaar 294e09ba7baSBram Moolenaar let entry['fname'] = fname 295e09ba7baSBram Moolenaar let entry['lnum'] = lnum 296e09ba7baSBram Moolenaarendfunc 297e09ba7baSBram Moolenaar 298e09ba7baSBram Moolenaar" Handle deleting a breakpoint 299e09ba7baSBram Moolenaar" Will remove the sign that shows the breakpoint 300e09ba7baSBram Moolenaarfunc s:HandleBreakpointDelete(msg) 301e09ba7baSBram Moolenaar let nr = substitute(a:msg, '.*id="\([0-9]*\)\".*', '\1', '') + 0 302e09ba7baSBram Moolenaar if nr == 0 303e09ba7baSBram Moolenaar return 304e09ba7baSBram Moolenaar endif 305e09ba7baSBram Moolenaar exe 'sign unplace ' . (s:break_id + nr) 306e09ba7baSBram Moolenaar unlet s:breakpoints[nr] 307c572da5fSBram Moolenaarendfunc 308