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".
23*e09ba7baSBram Moolenaarif !exists('termdebugger')
24*e09ba7baSBram Moolenaar  let termdebugger = 'gdb'
25c572da5fSBram Moolenaarendif
26c572da5fSBram Moolenaar
27fe386641SBram Moolenaar" Sign used to highlight the line where the program has stopped.
28*e09ba7baSBram Moolenaar" There can be only one.
29fe386641SBram Moolenaarsign define debugPC linehl=debugPC
30fe386641SBram Moolenaarlet s:pc_id = 12
31*e09ba7baSBram Moolenaarlet s:break_id = 13
32*e09ba7baSBram Moolenaar
33*e09ba7baSBram Moolenaar" Sign used to indicate a breakpoint.
34*e09ba7baSBram Moolenaar" Can be used multiple times.
35*e09ba7baSBram Moolenaarsign define debugBreakpoint text=>> texthl=debugBreakpoint
36*e09ba7baSBram Moolenaar
37*e09ba7baSBram Moolenaarif &background == 'light'
38*e09ba7baSBram Moolenaar  hi default debugPC term=reverse ctermbg=lightblue guibg=lightblue
39*e09ba7baSBram Moolenaarelse
40*e09ba7baSBram Moolenaar  hi default debugPC term=reverse ctermbg=darkblue guibg=darkblue
41*e09ba7baSBram Moolenaarendif
42*e09ba7baSBram Moolenaarhi default debugBreakpoint term=reverse ctermbg=red guibg=red
43fe386641SBram Moolenaar
44c572da5fSBram Moolenaarfunc s:StartDebug(cmd)
45fe386641SBram Moolenaar  let s:startwin = win_getid(winnr())
46fe386641SBram Moolenaar  let s:startsigncolumn = &signcolumn
47fe386641SBram Moolenaar
48c572da5fSBram Moolenaar  " Open a terminal window without a job, to run the debugged program
49fe386641SBram Moolenaar  let s:ptybuf = term_start('NONE', {
50fe386641SBram Moolenaar	\ 'term_name': 'gdb program',
51fe386641SBram Moolenaar	\ })
52fe386641SBram Moolenaar  if s:ptybuf == 0
53fe386641SBram Moolenaar    echoerr 'Failed to open the program terminal window'
54fe386641SBram Moolenaar    return
55fe386641SBram Moolenaar  endif
56fe386641SBram Moolenaar  let pty = job_info(term_getjob(s:ptybuf))['tty_out']
57fe386641SBram Moolenaar
58fe386641SBram Moolenaar  " Create a hidden terminal window to communicate with gdb
59fe386641SBram Moolenaar  let s:commbuf = term_start('NONE', {
60fe386641SBram Moolenaar	\ 'term_name': 'gdb communication',
61fe386641SBram Moolenaar	\ 'out_cb': function('s:CommOutput'),
62fe386641SBram Moolenaar	\ 'hidden': 1,
63fe386641SBram Moolenaar	\ })
64fe386641SBram Moolenaar  if s:commbuf == 0
65fe386641SBram Moolenaar    echoerr 'Failed to open the communication terminal window'
66fe386641SBram Moolenaar    exe 'bwipe! ' . s:ptybuf
67fe386641SBram Moolenaar    return
68fe386641SBram Moolenaar  endif
69fe386641SBram Moolenaar  let commpty = job_info(term_getjob(s:commbuf))['tty_out']
70c572da5fSBram Moolenaar
71c572da5fSBram Moolenaar  " Open a terminal window to run the debugger.
72*e09ba7baSBram Moolenaar  let cmd = [g:termdebugger, '-tty', pty, a:cmd]
73c572da5fSBram Moolenaar  echomsg 'executing "' . join(cmd) . '"'
74c572da5fSBram Moolenaar  let gdbbuf = term_start(cmd, {
75c572da5fSBram Moolenaar	\ 'exit_cb': function('s:EndDebug'),
76fe386641SBram Moolenaar	\ 'term_finish': 'close',
77c572da5fSBram Moolenaar	\ })
78fe386641SBram Moolenaar  if gdbbuf == 0
79fe386641SBram Moolenaar    echoerr 'Failed to open the gdb terminal window'
80fe386641SBram Moolenaar    exe 'bwipe! ' . s:ptybuf
81fe386641SBram Moolenaar    exe 'bwipe! ' . s:commbuf
82fe386641SBram Moolenaar    return
83fe386641SBram Moolenaar  endif
84fe386641SBram Moolenaar
85fe386641SBram Moolenaar  " Connect gdb to the communication pty, using the GDB/MI interface
86fe386641SBram Moolenaar  call term_sendkeys(gdbbuf, 'new-ui mi ' . commpty . "\r")
87*e09ba7baSBram Moolenaar
88*e09ba7baSBram Moolenaar  " Install debugger commands.
89*e09ba7baSBram Moolenaar  call s:InstallCommands()
90*e09ba7baSBram Moolenaar
91*e09ba7baSBram Moolenaar  let s:breakpoints = {}
92c572da5fSBram Moolenaarendfunc
93c572da5fSBram Moolenaar
94c572da5fSBram Moolenaarfunc s:EndDebug(job, status)
95c572da5fSBram Moolenaar  exe 'bwipe! ' . s:ptybuf
96fe386641SBram Moolenaar  exe 'bwipe! ' . s:commbuf
97*e09ba7baSBram Moolenaar
98*e09ba7baSBram Moolenaar  let curwinid = win_getid(winnr())
99*e09ba7baSBram Moolenaar
100*e09ba7baSBram Moolenaar  call win_gotoid(s:startwin)
101*e09ba7baSBram Moolenaar  let &signcolumn = s:startsigncolumn
102*e09ba7baSBram Moolenaar  call s:DeleteCommands()
103*e09ba7baSBram Moolenaar
104*e09ba7baSBram Moolenaar  call win_gotoid(curwinid)
105fe386641SBram Moolenaarendfunc
106fe386641SBram Moolenaar
107fe386641SBram Moolenaar" Handle a message received from gdb on the GDB/MI interface.
108fe386641SBram Moolenaarfunc s:CommOutput(chan, msg)
109fe386641SBram Moolenaar  let msgs = split(a:msg, "\r")
110fe386641SBram Moolenaar
111fe386641SBram Moolenaar  for msg in msgs
112fe386641SBram Moolenaar    " remove prefixed NL
113fe386641SBram Moolenaar    if msg[0] == "\n"
114fe386641SBram Moolenaar      let msg = msg[1:]
115fe386641SBram Moolenaar    endif
116fe386641SBram Moolenaar    if msg != ''
117fe386641SBram Moolenaar      if msg =~ '^\*\(stopped\|running\)'
118*e09ba7baSBram Moolenaar	call s:HandleCursor(msg)
119*e09ba7baSBram Moolenaar      elseif msg =~ '^\^done,bkpt='
120*e09ba7baSBram Moolenaar	call s:HandleNewBreakpoint(msg)
121*e09ba7baSBram Moolenaar      elseif msg =~ '^=breakpoint-deleted,'
122*e09ba7baSBram Moolenaar	call s:HandleBreakpointDelete(msg)
123*e09ba7baSBram Moolenaar      endif
124*e09ba7baSBram Moolenaar    endif
125*e09ba7baSBram Moolenaar  endfor
126*e09ba7baSBram Moolenaarendfunc
127*e09ba7baSBram Moolenaar
128*e09ba7baSBram Moolenaar" Install commands in the current window to control the debugger.
129*e09ba7baSBram Moolenaarfunc s:InstallCommands()
130*e09ba7baSBram Moolenaar  command Break call s:SetBreakpoint()
131*e09ba7baSBram Moolenaar  command Delete call s:DeleteBreakpoint()
132*e09ba7baSBram Moolenaar  command Step call s:SendCommand('-exec-step')
133*e09ba7baSBram Moolenaar  command NNext call s:SendCommand('-exec-next')
134*e09ba7baSBram Moolenaar  command Finish call s:SendCommand('-exec-finish')
135*e09ba7baSBram Moolenaar  command Continue call s:SendCommand('-exec-continue')
136*e09ba7baSBram Moolenaarendfunc
137*e09ba7baSBram Moolenaar
138*e09ba7baSBram Moolenaar" Delete installed debugger commands in the current window.
139*e09ba7baSBram Moolenaarfunc s:DeleteCommands()
140*e09ba7baSBram Moolenaar  delcommand Break
141*e09ba7baSBram Moolenaar  delcommand Delete
142*e09ba7baSBram Moolenaar  delcommand Step
143*e09ba7baSBram Moolenaar  delcommand NNext
144*e09ba7baSBram Moolenaar  delcommand Finish
145*e09ba7baSBram Moolenaar  delcommand Continue
146*e09ba7baSBram Moolenaarendfunc
147*e09ba7baSBram Moolenaar
148*e09ba7baSBram Moolenaar" :Break - Set a breakpoint at the cursor position.
149*e09ba7baSBram Moolenaarfunc s:SetBreakpoint()
150*e09ba7baSBram Moolenaar  call term_sendkeys(s:commbuf, '-break-insert --source '
151*e09ba7baSBram Moolenaar	\ . fnameescape(expand('%:p')) . ' --line ' . line('.') . "\r")
152*e09ba7baSBram Moolenaarendfunc
153*e09ba7baSBram Moolenaar
154*e09ba7baSBram Moolenaar" :Delete - Delete a breakpoint at the cursor position.
155*e09ba7baSBram Moolenaarfunc s:DeleteBreakpoint()
156*e09ba7baSBram Moolenaar  let fname = fnameescape(expand('%:p'))
157*e09ba7baSBram Moolenaar  let lnum = line('.')
158*e09ba7baSBram Moolenaar  for [key, val] in items(s:breakpoints)
159*e09ba7baSBram Moolenaar    if val['fname'] == fname && val['lnum'] == lnum
160*e09ba7baSBram Moolenaar      call term_sendkeys(s:commbuf, '-break-delete ' . key . "\r")
161*e09ba7baSBram Moolenaar      " Assume this always wors, the reply is simply "^done".
162*e09ba7baSBram Moolenaar      exe 'sign unplace ' . (s:break_id + key)
163*e09ba7baSBram Moolenaar      unlet s:breakpoints[key]
164*e09ba7baSBram Moolenaar      break
165*e09ba7baSBram Moolenaar    endif
166*e09ba7baSBram Moolenaar  endfor
167*e09ba7baSBram Moolenaarendfunc
168*e09ba7baSBram Moolenaar
169*e09ba7baSBram Moolenaar" :Next, :Continue, etc - send a command to gdb
170*e09ba7baSBram Moolenaarfunc s:SendCommand(cmd)
171*e09ba7baSBram Moolenaar  call term_sendkeys(s:commbuf, a:cmd . "\r")
172*e09ba7baSBram Moolenaarendfunc
173*e09ba7baSBram Moolenaar
174*e09ba7baSBram Moolenaar" Handle stopping and running message from gdb.
175*e09ba7baSBram Moolenaar" Will update the sign that shows the current position.
176*e09ba7baSBram Moolenaarfunc s:HandleCursor(msg)
177fe386641SBram Moolenaar  let wid = win_getid(winnr())
178fe386641SBram Moolenaar
179fe386641SBram Moolenaar  if win_gotoid(s:startwin)
180*e09ba7baSBram Moolenaar    if a:msg =~ '^\*stopped'
181*e09ba7baSBram Moolenaar      let fname = substitute(a:msg, '.*fullname="\([^"]*\)".*', '\1', '')
182*e09ba7baSBram Moolenaar      let lnum = substitute(a:msg, '.*line="\([^"]*\)".*', '\1', '')
183fe386641SBram Moolenaar      if lnum =~ '^[0-9]*$'
184fe386641SBram Moolenaar	if expand('%:h') != fname
185fe386641SBram Moolenaar	  if &modified
186fe386641SBram Moolenaar	    " TODO: find existing window
187fe386641SBram Moolenaar	    exe 'split ' . fnameescape(fname)
188fe386641SBram Moolenaar	    let s:startwin = win_getid(winnr())
189fe386641SBram Moolenaar	  else
190fe386641SBram Moolenaar	    exe 'edit ' . fnameescape(fname)
191fe386641SBram Moolenaar	  endif
192fe386641SBram Moolenaar	endif
193fe386641SBram Moolenaar	exe lnum
194fe386641SBram Moolenaar	exe 'sign place ' . s:pc_id . ' line=' . lnum . ' name=debugPC file=' . fnameescape(fname)
195fe386641SBram Moolenaar	setlocal signcolumn=yes
196fe386641SBram Moolenaar      endif
197fe386641SBram Moolenaar    else
198fe386641SBram Moolenaar      exe 'sign unplace ' . s:pc_id
199fe386641SBram Moolenaar    endif
200fe386641SBram Moolenaar
201fe386641SBram Moolenaar    call win_gotoid(wid)
202fe386641SBram Moolenaar  endif
203*e09ba7baSBram Moolenaarendfunc
204*e09ba7baSBram Moolenaar
205*e09ba7baSBram Moolenaar" Handle setting a breakpoint
206*e09ba7baSBram Moolenaar" Will update the sign that shows the breakpoint
207*e09ba7baSBram Moolenaarfunc s:HandleNewBreakpoint(msg)
208*e09ba7baSBram Moolenaar  let nr = substitute(a:msg, '.*number="\([0-9]\)*\".*', '\1', '') + 0
209*e09ba7baSBram Moolenaar  if nr == 0
210*e09ba7baSBram Moolenaar    return
211fe386641SBram Moolenaar  endif
212*e09ba7baSBram Moolenaar
213*e09ba7baSBram Moolenaar  if has_key(s:breakpoints, nr)
214*e09ba7baSBram Moolenaar    let entry = s:breakpoints[nr]
215*e09ba7baSBram Moolenaar  else
216*e09ba7baSBram Moolenaar    let entry = {}
217*e09ba7baSBram Moolenaar    let s:breakpoints[nr] = entry
218fe386641SBram Moolenaar  endif
219*e09ba7baSBram Moolenaar
220*e09ba7baSBram Moolenaar  let fname = substitute(a:msg, '.*fullname="\([^"]*\)".*', '\1', '')
221*e09ba7baSBram Moolenaar  let lnum = substitute(a:msg, '.*line="\([^"]*\)".*', '\1', '')
222*e09ba7baSBram Moolenaar
223*e09ba7baSBram Moolenaar  exe 'sign place ' . (s:break_id + nr) . ' line=' . lnum . ' name=debugBreakpoint file=' . fnameescape(fname)
224*e09ba7baSBram Moolenaar
225*e09ba7baSBram Moolenaar  let entry['fname'] = fname
226*e09ba7baSBram Moolenaar  let entry['lnum'] = lnum
227*e09ba7baSBram Moolenaarendfunc
228*e09ba7baSBram Moolenaar
229*e09ba7baSBram Moolenaar" Handle deleting a breakpoint
230*e09ba7baSBram Moolenaar" Will remove the sign that shows the breakpoint
231*e09ba7baSBram Moolenaarfunc s:HandleBreakpointDelete(msg)
232*e09ba7baSBram Moolenaar  let nr = substitute(a:msg, '.*id="\([0-9]*\)\".*', '\1', '') + 0
233*e09ba7baSBram Moolenaar  if nr == 0
234*e09ba7baSBram Moolenaar    return
235*e09ba7baSBram Moolenaar  endif
236*e09ba7baSBram Moolenaar  exe 'sign unplace ' . (s:break_id + nr)
237*e09ba7baSBram Moolenaar  unlet s:breakpoints[nr]
238c572da5fSBram Moolenaarendfunc
239