1" Debugger plugin using gdb. 2" 3" Author: Bram Moolenaar 4" Copyright: Vim license applies, see ":help license" 5" Last Change: 2020 May 22 6" 7" WORK IN PROGRESS - Only the basics work 8" Note: On MS-Windows you need a recent version of gdb. The one included with 9" MingW is too old (7.6.1). 10" I used version 7.12 from http://www.equation.com/servlet/equation.cmd?fa=gdb 11" 12" There are two ways to run gdb: 13" - In a terminal window; used if possible, does not work on MS-Windows 14" Not used when g:termdebug_use_prompt is set to 1. 15" - Using a "prompt" buffer; may use a terminal window for the program 16" 17" For both the current window is used to view source code and shows the 18" current statement from gdb. 19" 20" USING A TERMINAL WINDOW 21" 22" Opens two visible terminal windows: 23" 1. runs a pty for the debugged program, as with ":term NONE" 24" 2. runs gdb, passing the pty of the debugged program 25" A third terminal window is hidden, it is used for communication with gdb. 26" 27" USING A PROMPT BUFFER 28" 29" Opens a window with a prompt buffer to communicate with gdb. 30" Gdb is run as a job with callbacks for I/O. 31" On Unix another terminal window is opened to run the debugged program 32" On MS-Windows a separate console is opened to run the debugged program 33" 34" The communication with gdb uses GDB/MI. See: 35" https://sourceware.org/gdb/current/onlinedocs/gdb/GDB_002fMI.html 36 37" In case this gets sourced twice. 38if exists(':Termdebug') 39 finish 40endif 41 42" Need either the +terminal feature or +channel and the prompt buffer. 43" The terminal feature does not work with gdb on win32. 44if has('terminal') && !has('win32') 45 let s:way = 'terminal' 46elseif has('channel') && exists('*prompt_setprompt') 47 let s:way = 'prompt' 48else 49 if has('terminal') 50 let s:err = 'Cannot debug, missing prompt buffer support' 51 else 52 let s:err = 'Cannot debug, +channel feature is not supported' 53 endif 54 command -nargs=* -complete=file -bang Termdebug echoerr s:err 55 command -nargs=+ -complete=file -bang TermdebugCommand echoerr s:err 56 finish 57endif 58 59let s:keepcpo = &cpo 60set cpo&vim 61 62" The command that starts debugging, e.g. ":Termdebug vim". 63" To end type "quit" in the gdb window. 64command -nargs=* -complete=file -bang Termdebug call s:StartDebug(<bang>0, <f-args>) 65command -nargs=+ -complete=file -bang TermdebugCommand call s:StartDebugCommand(<bang>0, <f-args>) 66 67" Name of the gdb command, defaults to "gdb". 68if !exists('g:termdebugger') 69 let g:termdebugger = 'gdb' 70endif 71 72let s:pc_id = 12 73let s:break_id = 13 " breakpoint number is added to this 74let s:stopped = 1 75 76" Take a breakpoint number as used by GDB and turn it into an integer. 77" The breakpoint may contain a dot: 123.4 -> 123004 78" The main breakpoint has a zero subid. 79func s:Breakpoint2SignNumber(id, subid) 80 return s:break_id + a:id * 1000 + a:subid 81endfunction 82 83func s:Highlight(init, old, new) 84 let default = a:init ? 'default ' : '' 85 if a:new ==# 'light' && a:old !=# 'light' 86 exe "hi " . default . "debugPC term=reverse ctermbg=lightblue guibg=lightblue" 87 elseif a:new ==# 'dark' && a:old !=# 'dark' 88 exe "hi " . default . "debugPC term=reverse ctermbg=darkblue guibg=darkblue" 89 endif 90endfunc 91 92call s:Highlight(1, '', &background) 93hi default debugBreakpoint term=reverse ctermbg=red guibg=red 94 95func s:StartDebug(bang, ...) 96 " First argument is the command to debug, second core file or process ID. 97 call s:StartDebug_internal({'gdb_args': a:000, 'bang': a:bang}) 98endfunc 99 100func s:StartDebugCommand(bang, ...) 101 " First argument is the command to debug, rest are run arguments. 102 call s:StartDebug_internal({'gdb_args': [a:1], 'proc_args': a:000[1:], 'bang': a:bang}) 103endfunc 104 105func s:StartDebug_internal(dict) 106 if exists('s:gdbwin') 107 echoerr 'Terminal debugger already running, cannot run two' 108 return 109 endif 110 if !executable(g:termdebugger) 111 echoerr 'Cannot execute debugger program "' .. g:termdebugger .. '"' 112 return 113 endif 114 115 let s:ptywin = 0 116 let s:pid = 0 117 118 " Uncomment this line to write logging in "debuglog". 119 " call ch_logfile('debuglog', 'w') 120 121 let s:sourcewin = win_getid(winnr()) 122 let s:startsigncolumn = &signcolumn 123 124 let s:save_columns = 0 125 let s:allleft = 0 126 if exists('g:termdebug_wide') 127 if &columns < g:termdebug_wide 128 let s:save_columns = &columns 129 let &columns = g:termdebug_wide 130 " If we make the Vim window wider, use the whole left halve for the debug 131 " windows. 132 let s:allleft = 1 133 endif 134 let s:vertical = 1 135 else 136 let s:vertical = 0 137 endif 138 139 " Override using a terminal window by setting g:termdebug_use_prompt to 1. 140 let use_prompt = exists('g:termdebug_use_prompt') && g:termdebug_use_prompt 141 if has('terminal') && !has('win32') && !use_prompt 142 let s:way = 'terminal' 143 else 144 let s:way = 'prompt' 145 endif 146 147 if s:way == 'prompt' 148 call s:StartDebug_prompt(a:dict) 149 else 150 call s:StartDebug_term(a:dict) 151 endif 152endfunc 153 154" Use when debugger didn't start or ended. 155func s:CloseBuffers() 156 exe 'bwipe! ' . s:ptybuf 157 exe 'bwipe! ' . s:commbuf 158 unlet! s:gdbwin 159endfunc 160 161func s:StartDebug_term(dict) 162 " Open a terminal window without a job, to run the debugged program in. 163 let s:ptybuf = term_start('NONE', { 164 \ 'term_name': 'debugged program', 165 \ 'vertical': s:vertical, 166 \ }) 167 if s:ptybuf == 0 168 echoerr 'Failed to open the program terminal window' 169 return 170 endif 171 let pty = job_info(term_getjob(s:ptybuf))['tty_out'] 172 let s:ptywin = win_getid(winnr()) 173 if s:vertical 174 " Assuming the source code window will get a signcolumn, use two more 175 " columns for that, thus one less for the terminal window. 176 exe (&columns / 2 - 1) . "wincmd |" 177 if s:allleft 178 " use the whole left column 179 wincmd H 180 endif 181 endif 182 183 " Create a hidden terminal window to communicate with gdb 184 let s:commbuf = term_start('NONE', { 185 \ 'term_name': 'gdb communication', 186 \ 'out_cb': function('s:CommOutput'), 187 \ 'hidden': 1, 188 \ }) 189 if s:commbuf == 0 190 echoerr 'Failed to open the communication terminal window' 191 exe 'bwipe! ' . s:ptybuf 192 return 193 endif 194 let commpty = job_info(term_getjob(s:commbuf))['tty_out'] 195 196 " Open a terminal window to run the debugger. 197 " Add -quiet to avoid the intro message causing a hit-enter prompt. 198 let gdb_args = get(a:dict, 'gdb_args', []) 199 let proc_args = get(a:dict, 'proc_args', []) 200 201 let cmd = [g:termdebugger, '-quiet', '-tty', pty] + gdb_args 202 call ch_log('executing "' . join(cmd) . '"') 203 let s:gdbbuf = term_start(cmd, { 204 \ 'term_finish': 'close', 205 \ }) 206 if s:gdbbuf == 0 207 echoerr 'Failed to open the gdb terminal window' 208 call s:CloseBuffers() 209 return 210 endif 211 let s:gdbwin = win_getid(winnr()) 212 213 " Set arguments to be run 214 if len(proc_args) 215 call term_sendkeys(s:gdbbuf, 'set args ' . join(proc_args) . "\r") 216 endif 217 218 " Connect gdb to the communication pty, using the GDB/MI interface 219 call term_sendkeys(s:gdbbuf, 'new-ui mi ' . commpty . "\r") 220 221 " Wait for the response to show up, users may not notice the error and wonder 222 " why the debugger doesn't work. 223 let try_count = 0 224 while 1 225 let gdbproc = term_getjob(s:gdbbuf) 226 if gdbproc == v:null || job_status(gdbproc) !=# 'run' 227 echoerr string(g:termdebugger) . ' exited unexpectedly' 228 call s:CloseBuffers() 229 return 230 endif 231 232 let response = '' 233 for lnum in range(1, 200) 234 let line1 = term_getline(s:gdbbuf, lnum) 235 let line2 = term_getline(s:gdbbuf, lnum + 1) 236 if line1 =~ 'new-ui mi ' 237 " response can be in the same line or the next line 238 let response = line1 . line2 239 if response =~ 'Undefined command' 240 echoerr 'Sorry, your gdb is too old, gdb 7.12 is required' 241 call s:CloseBuffers() 242 return 243 endif 244 if response =~ 'New UI allocated' 245 " Success! 246 break 247 endif 248 elseif line1 =~ 'Reading symbols from' && line2 !~ 'new-ui mi ' 249 " Reading symbols might take a while, try more times 250 let try_count -= 1 251 endif 252 endfor 253 if response =~ 'New UI allocated' 254 break 255 endif 256 let try_count += 1 257 if try_count > 100 258 echoerr 'Cannot check if your gdb works, continuing anyway' 259 break 260 endif 261 sleep 10m 262 endwhile 263 264 " Interpret commands while the target is running. This should usually only be 265 " exec-interrupt, since many commands don't work properly while the target is 266 " running. 267 call s:SendCommand('-gdb-set mi-async on') 268 " Older gdb uses a different command. 269 call s:SendCommand('-gdb-set target-async on') 270 271 " Disable pagination, it causes everything to stop at the gdb 272 " "Type <return> to continue" prompt. 273 call s:SendCommand('set pagination off') 274 275 call job_setoptions(gdbproc, {'exit_cb': function('s:EndTermDebug')}) 276 call s:StartDebugCommon(a:dict) 277endfunc 278 279func s:StartDebug_prompt(dict) 280 " Open a window with a prompt buffer to run gdb in. 281 if s:vertical 282 vertical new 283 else 284 new 285 endif 286 let s:gdbwin = win_getid(winnr()) 287 let s:promptbuf = bufnr('') 288 call prompt_setprompt(s:promptbuf, 'gdb> ') 289 set buftype=prompt 290 file gdb 291 call prompt_setcallback(s:promptbuf, function('s:PromptCallback')) 292 call prompt_setinterrupt(s:promptbuf, function('s:PromptInterrupt')) 293 294 if s:vertical 295 " Assuming the source code window will get a signcolumn, use two more 296 " columns for that, thus one less for the terminal window. 297 exe (&columns / 2 - 1) . "wincmd |" 298 endif 299 300 " Add -quiet to avoid the intro message causing a hit-enter prompt. 301 let gdb_args = get(a:dict, 'gdb_args', []) 302 let proc_args = get(a:dict, 'proc_args', []) 303 304 let cmd = [g:termdebugger, '-quiet', '--interpreter=mi2'] + gdb_args 305 call ch_log('executing "' . join(cmd) . '"') 306 307 let s:gdbjob = job_start(cmd, { 308 \ 'exit_cb': function('s:EndPromptDebug'), 309 \ 'out_cb': function('s:GdbOutCallback'), 310 \ }) 311 if job_status(s:gdbjob) != "run" 312 echoerr 'Failed to start gdb' 313 exe 'bwipe! ' . s:promptbuf 314 return 315 endif 316 " Mark the buffer modified so that it's not easy to close. 317 set modified 318 let s:gdb_channel = job_getchannel(s:gdbjob) 319 320 " Interpret commands while the target is running. This should usually only 321 " be exec-interrupt, since many commands don't work properly while the 322 " target is running. 323 call s:SendCommand('-gdb-set mi-async on') 324 " Older gdb uses a different command. 325 call s:SendCommand('-gdb-set target-async on') 326 327 let s:ptybuf = 0 328 if has('win32') 329 " MS-Windows: run in a new console window for maximum compatibility 330 call s:SendCommand('set new-console on') 331 elseif has('terminal') 332 " Unix: Run the debugged program in a terminal window. Open it below the 333 " gdb window. 334 belowright let s:ptybuf = term_start('NONE', { 335 \ 'term_name': 'debugged program', 336 \ }) 337 if s:ptybuf == 0 338 echoerr 'Failed to open the program terminal window' 339 call job_stop(s:gdbjob) 340 return 341 endif 342 let s:ptywin = win_getid(winnr()) 343 let pty = job_info(term_getjob(s:ptybuf))['tty_out'] 344 call s:SendCommand('tty ' . pty) 345 346 " Since GDB runs in a prompt window, the environment has not been set to 347 " match a terminal window, need to do that now. 348 call s:SendCommand('set env TERM = xterm-color') 349 call s:SendCommand('set env ROWS = ' . winheight(s:ptywin)) 350 call s:SendCommand('set env LINES = ' . winheight(s:ptywin)) 351 call s:SendCommand('set env COLUMNS = ' . winwidth(s:ptywin)) 352 call s:SendCommand('set env COLORS = ' . &t_Co) 353 call s:SendCommand('set env VIM_TERMINAL = ' . v:version) 354 else 355 " TODO: open a new terminal get get the tty name, pass on to gdb 356 call s:SendCommand('show inferior-tty') 357 endif 358 call s:SendCommand('set print pretty on') 359 call s:SendCommand('set breakpoint pending on') 360 " Disable pagination, it causes everything to stop at the gdb 361 call s:SendCommand('set pagination off') 362 363 " Set arguments to be run 364 if len(proc_args) 365 call s:SendCommand('set args ' . join(proc_args)) 366 endif 367 368 call s:StartDebugCommon(a:dict) 369 startinsert 370endfunc 371 372func s:StartDebugCommon(dict) 373 " Sign used to highlight the line where the program has stopped. 374 " There can be only one. 375 sign define debugPC linehl=debugPC 376 377 " Install debugger commands in the text window. 378 call win_gotoid(s:sourcewin) 379 call s:InstallCommands() 380 call win_gotoid(s:gdbwin) 381 382 " Enable showing a balloon with eval info 383 if has("balloon_eval") || has("balloon_eval_term") 384 set balloonexpr=TermDebugBalloonExpr() 385 if has("balloon_eval") 386 set ballooneval 387 endif 388 if has("balloon_eval_term") 389 set balloonevalterm 390 endif 391 endif 392 393 " Contains breakpoints that have been placed, key is a string with the GDB 394 " breakpoint number. 395 " Each entry is a dict, containing the sub-breakpoints. Key is the subid. 396 " For a breakpoint that is just a number the subid is zero. 397 " For a breakpoint "123.4" the id is "123" and subid is "4". 398 " Example, when breakpoint "44", "123", "123.1" and "123.2" exist: 399 " {'44': {'0': entry}, '123': {'0': entry, '1': entry, '2': entry}} 400 let s:breakpoints = {} 401 402 " Contains breakpoints by file/lnum. The key is "fname:lnum". 403 " Each entry is a list of breakpoint IDs at that position. 404 let s:breakpoint_locations = {} 405 406 augroup TermDebug 407 au BufRead * call s:BufRead() 408 au BufUnload * call s:BufUnloaded() 409 au OptionSet background call s:Highlight(0, v:option_old, v:option_new) 410 augroup END 411 412 " Run the command if the bang attribute was given and got to the debug 413 " window. 414 if get(a:dict, 'bang', 0) 415 call s:SendCommand('-exec-run') 416 call win_gotoid(s:ptywin) 417 endif 418endfunc 419 420" Send a command to gdb. "cmd" is the string without line terminator. 421func s:SendCommand(cmd) 422 call ch_log('sending to gdb: ' . a:cmd) 423 if s:way == 'prompt' 424 call ch_sendraw(s:gdb_channel, a:cmd . "\n") 425 else 426 call term_sendkeys(s:commbuf, a:cmd . "\r") 427 endif 428endfunc 429 430" This is global so that a user can create their mappings with this. 431func TermDebugSendCommand(cmd) 432 if s:way == 'prompt' 433 call ch_sendraw(s:gdb_channel, a:cmd . "\n") 434 else 435 let do_continue = 0 436 if !s:stopped 437 let do_continue = 1 438 call s:SendCommand('-exec-interrupt') 439 sleep 10m 440 endif 441 call term_sendkeys(s:gdbbuf, a:cmd . "\r") 442 if do_continue 443 Continue 444 endif 445 endif 446endfunc 447 448" Function called when entering a line in the prompt buffer. 449func s:PromptCallback(text) 450 call s:SendCommand(a:text) 451endfunc 452 453" Function called when pressing CTRL-C in the prompt buffer and when placing a 454" breakpoint. 455func s:PromptInterrupt() 456 call ch_log('Interrupting gdb') 457 if has('win32') 458 " Using job_stop() does not work on MS-Windows, need to send SIGTRAP to 459 " the debugger program so that gdb responds again. 460 if s:pid == 0 461 echoerr 'Cannot interrupt gdb, did not find a process ID' 462 else 463 call debugbreak(s:pid) 464 endif 465 else 466 call job_stop(s:gdbjob, 'int') 467 endif 468endfunc 469 470" Function called when gdb outputs text. 471func s:GdbOutCallback(channel, text) 472 call ch_log('received from gdb: ' . a:text) 473 474 " Drop the gdb prompt, we have our own. 475 " Drop status and echo'd commands. 476 if a:text == '(gdb) ' || a:text == '^done' || a:text[0] == '&' 477 return 478 endif 479 if a:text =~ '^^error,msg=' 480 let text = s:DecodeMessage(a:text[11:]) 481 if exists('s:evalexpr') && text =~ 'A syntax error in expression, near\|No symbol .* in current context' 482 " Silently drop evaluation errors. 483 unlet s:evalexpr 484 return 485 endif 486 elseif a:text[0] == '~' 487 let text = s:DecodeMessage(a:text[1:]) 488 else 489 call s:CommOutput(a:channel, a:text) 490 return 491 endif 492 493 let curwinid = win_getid(winnr()) 494 call win_gotoid(s:gdbwin) 495 496 " Add the output above the current prompt. 497 call append(line('$') - 1, text) 498 set modified 499 500 call win_gotoid(curwinid) 501endfunc 502 503" Decode a message from gdb. quotedText starts with a ", return the text up 504" to the next ", unescaping characters. 505func s:DecodeMessage(quotedText) 506 if a:quotedText[0] != '"' 507 echoerr 'DecodeMessage(): missing quote in ' . a:quotedText 508 return 509 endif 510 let result = '' 511 let i = 1 512 while a:quotedText[i] != '"' && i < len(a:quotedText) 513 if a:quotedText[i] == '\' 514 let i += 1 515 if a:quotedText[i] == 'n' 516 " drop \n 517 let i += 1 518 continue 519 elseif a:quotedText[i] == 't' 520 " append \t 521 let i += 1 522 let result .= "\t" 523 continue 524 endif 525 endif 526 let result .= a:quotedText[i] 527 let i += 1 528 endwhile 529 return result 530endfunc 531 532" Extract the "name" value from a gdb message with fullname="name". 533func s:GetFullname(msg) 534 if a:msg !~ 'fullname' 535 return '' 536 endif 537 let name = s:DecodeMessage(substitute(a:msg, '.*fullname=', '', '')) 538 if has('win32') && name =~ ':\\\\' 539 " sometimes the name arrives double-escaped 540 let name = substitute(name, '\\\\', '\\', 'g') 541 endif 542 return name 543endfunc 544 545func s:EndTermDebug(job, status) 546 exe 'bwipe! ' . s:commbuf 547 unlet s:gdbwin 548 549 call s:EndDebugCommon() 550endfunc 551 552func s:EndDebugCommon() 553 let curwinid = win_getid(winnr()) 554 555 if exists('s:ptybuf') && s:ptybuf 556 exe 'bwipe! ' . s:ptybuf 557 endif 558 559 call win_gotoid(s:sourcewin) 560 let &signcolumn = s:startsigncolumn 561 call s:DeleteCommands() 562 563 call win_gotoid(curwinid) 564 565 if s:save_columns > 0 566 let &columns = s:save_columns 567 endif 568 569 if has("balloon_eval") || has("balloon_eval_term") 570 set balloonexpr= 571 if has("balloon_eval") 572 set noballooneval 573 endif 574 if has("balloon_eval_term") 575 set noballoonevalterm 576 endif 577 endif 578 579 au! TermDebug 580endfunc 581 582func s:EndPromptDebug(job, status) 583 let curwinid = win_getid(winnr()) 584 call win_gotoid(s:gdbwin) 585 set nomodified 586 close 587 if curwinid != s:gdbwin 588 call win_gotoid(curwinid) 589 endif 590 591 call s:EndDebugCommon() 592 unlet s:gdbwin 593 call ch_log("Returning from EndPromptDebug()") 594endfunc 595 596" Handle a message received from gdb on the GDB/MI interface. 597func s:CommOutput(chan, msg) 598 let msgs = split(a:msg, "\r") 599 600 for msg in msgs 601 " remove prefixed NL 602 if msg[0] == "\n" 603 let msg = msg[1:] 604 endif 605 if msg != '' 606 if msg =~ '^\(\*stopped\|\*running\|=thread-selected\)' 607 call s:HandleCursor(msg) 608 elseif msg =~ '^\^done,bkpt=' || msg =~ '^=breakpoint-created,' 609 call s:HandleNewBreakpoint(msg) 610 elseif msg =~ '^=breakpoint-deleted,' 611 call s:HandleBreakpointDelete(msg) 612 elseif msg =~ '^=thread-group-started' 613 call s:HandleProgramRun(msg) 614 elseif msg =~ '^\^done,value=' 615 call s:HandleEvaluate(msg) 616 elseif msg =~ '^\^error,msg=' 617 call s:HandleError(msg) 618 endif 619 endif 620 endfor 621endfunc 622 623func s:GotoProgram() 624 if has('win32') 625 if executable('powershell') 626 call system(printf('powershell -Command "add-type -AssemblyName microsoft.VisualBasic;[Microsoft.VisualBasic.Interaction]::AppActivate(%d);"', s:pid)) 627 endif 628 else 629 call win_gotoid(s:ptywin) 630 endif 631endfunc 632 633" Install commands in the current window to control the debugger. 634func s:InstallCommands() 635 let save_cpo = &cpo 636 set cpo&vim 637 638 command -nargs=? Break call s:SetBreakpoint(<q-args>) 639 command Clear call s:ClearBreakpoint() 640 command Step call s:SendCommand('-exec-step') 641 command Over call s:SendCommand('-exec-next') 642 command Finish call s:SendCommand('-exec-finish') 643 command -nargs=* Run call s:Run(<q-args>) 644 command -nargs=* Arguments call s:SendCommand('-exec-arguments ' . <q-args>) 645 command Stop call s:SendCommand('-exec-interrupt') 646 647 " using -exec-continue results in CTRL-C in gdb window not working 648 if s:way == 'prompt' 649 command Continue call s:SendCommand('continue') 650 else 651 command Continue call term_sendkeys(s:gdbbuf, "continue\r") 652 endif 653 654 command -range -nargs=* Evaluate call s:Evaluate(<range>, <q-args>) 655 command Gdb call win_gotoid(s:gdbwin) 656 command Program call s:GotoProgram() 657 command Source call s:GotoSourcewinOrCreateIt() 658 command Winbar call s:InstallWinbar() 659 660 if !exists('g:termdebug_map_K') || g:termdebug_map_K 661 let s:k_map_saved = maparg('K', 'n', 0, 1) 662 nnoremap K :Evaluate<CR> 663 endif 664 665 if has('menu') && &mouse != '' 666 call s:InstallWinbar() 667 668 if !exists('g:termdebug_popup') || g:termdebug_popup != 0 669 let s:saved_mousemodel = &mousemodel 670 let &mousemodel = 'popup_setpos' 671 an 1.200 PopUp.-SEP3- <Nop> 672 an 1.210 PopUp.Set\ breakpoint :Break<CR> 673 an 1.220 PopUp.Clear\ breakpoint :Clear<CR> 674 an 1.230 PopUp.Evaluate :Evaluate<CR> 675 endif 676 endif 677 678 let &cpo = save_cpo 679endfunc 680 681let s:winbar_winids = [] 682 683" Install the window toolbar in the current window. 684func s:InstallWinbar() 685 if has('menu') && &mouse != '' 686 nnoremenu WinBar.Step :Step<CR> 687 nnoremenu WinBar.Next :Over<CR> 688 nnoremenu WinBar.Finish :Finish<CR> 689 nnoremenu WinBar.Cont :Continue<CR> 690 nnoremenu WinBar.Stop :Stop<CR> 691 nnoremenu WinBar.Eval :Evaluate<CR> 692 call add(s:winbar_winids, win_getid(winnr())) 693 endif 694endfunc 695 696" Delete installed debugger commands in the current window. 697func s:DeleteCommands() 698 delcommand Break 699 delcommand Clear 700 delcommand Step 701 delcommand Over 702 delcommand Finish 703 delcommand Run 704 delcommand Arguments 705 delcommand Stop 706 delcommand Continue 707 delcommand Evaluate 708 delcommand Gdb 709 delcommand Program 710 delcommand Source 711 delcommand Winbar 712 713 if exists('s:k_map_saved') 714 call mapset('n', 0, s:k_map_saved) 715 unlet s:k_map_saved 716 endif 717 718 if has('menu') 719 " Remove the WinBar entries from all windows where it was added. 720 let curwinid = win_getid(winnr()) 721 for winid in s:winbar_winids 722 if win_gotoid(winid) 723 aunmenu WinBar.Step 724 aunmenu WinBar.Next 725 aunmenu WinBar.Finish 726 aunmenu WinBar.Cont 727 aunmenu WinBar.Stop 728 aunmenu WinBar.Eval 729 endif 730 endfor 731 call win_gotoid(curwinid) 732 let s:winbar_winids = [] 733 734 if exists('s:saved_mousemodel') 735 let &mousemodel = s:saved_mousemodel 736 unlet s:saved_mousemodel 737 aunmenu PopUp.-SEP3- 738 aunmenu PopUp.Set\ breakpoint 739 aunmenu PopUp.Clear\ breakpoint 740 aunmenu PopUp.Evaluate 741 endif 742 endif 743 744 exe 'sign unplace ' . s:pc_id 745 for [id, entries] in items(s:breakpoints) 746 for subid in keys(entries) 747 exe 'sign unplace ' . s:Breakpoint2SignNumber(id, subid) 748 endfor 749 endfor 750 unlet s:breakpoints 751 unlet s:breakpoint_locations 752 753 sign undefine debugPC 754 for val in s:BreakpointSigns 755 exe "sign undefine debugBreakpoint" . val 756 endfor 757 let s:BreakpointSigns = [] 758endfunc 759 760" :Break - Set a breakpoint at the cursor position. 761func s:SetBreakpoint(at) 762 " Setting a breakpoint may not work while the program is running. 763 " Interrupt to make it work. 764 let do_continue = 0 765 if !s:stopped 766 let do_continue = 1 767 if s:way == 'prompt' 768 call s:PromptInterrupt() 769 else 770 call s:SendCommand('-exec-interrupt') 771 endif 772 sleep 10m 773 endif 774 775 " Use the fname:lnum format, older gdb can't handle --source. 776 let at = empty(a:at) ? 777 \ fnameescape(expand('%:p')) . ':' . line('.') : a:at 778 call s:SendCommand('-break-insert ' . at) 779 if do_continue 780 call s:SendCommand('-exec-continue') 781 endif 782endfunc 783 784" :Clear - Delete a breakpoint at the cursor position. 785func s:ClearBreakpoint() 786 let fname = fnameescape(expand('%:p')) 787 let lnum = line('.') 788 let bploc = printf('%s:%d', fname, lnum) 789 if has_key(s:breakpoint_locations, bploc) 790 let idx = 0 791 for id in s:breakpoint_locations[bploc] 792 if has_key(s:breakpoints, id) 793 " Assume this always works, the reply is simply "^done". 794 call s:SendCommand('-break-delete ' . id) 795 for subid in keys(s:breakpoints[id]) 796 exe 'sign unplace ' . s:Breakpoint2SignNumber(id, subid) 797 endfor 798 unlet s:breakpoints[id] 799 unlet s:breakpoint_locations[bploc][idx] 800 break 801 else 802 let idx += 1 803 endif 804 endfor 805 if empty(s:breakpoint_locations[bploc]) 806 unlet s:breakpoint_locations[bploc] 807 endif 808 endif 809endfunc 810 811func s:Run(args) 812 if a:args != '' 813 call s:SendCommand('-exec-arguments ' . a:args) 814 endif 815 call s:SendCommand('-exec-run') 816endfunc 817 818func s:SendEval(expr) 819 call s:SendCommand('-data-evaluate-expression "' . a:expr . '"') 820 let s:evalexpr = a:expr 821endfunc 822 823" :Evaluate - evaluate what is under the cursor 824func s:Evaluate(range, arg) 825 if a:arg != '' 826 let expr = a:arg 827 elseif a:range == 2 828 let pos = getcurpos() 829 let reg = getreg('v', 1, 1) 830 let regt = getregtype('v') 831 normal! gv"vy 832 let expr = @v 833 call setpos('.', pos) 834 call setreg('v', reg, regt) 835 else 836 let expr = expand('<cexpr>') 837 endif 838 let s:ignoreEvalError = 0 839 call s:SendEval(expr) 840endfunc 841 842let s:ignoreEvalError = 0 843let s:evalFromBalloonExpr = 0 844 845" Handle the result of data-evaluate-expression 846func s:HandleEvaluate(msg) 847 let value = substitute(a:msg, '.*value="\(.*\)"', '\1', '') 848 let value = substitute(value, '\\"', '"', 'g') 849 if s:evalFromBalloonExpr 850 if s:evalFromBalloonExprResult == '' 851 let s:evalFromBalloonExprResult = s:evalexpr . ': ' . value 852 else 853 let s:evalFromBalloonExprResult .= ' = ' . value 854 endif 855 call balloon_show(s:evalFromBalloonExprResult) 856 else 857 echomsg '"' . s:evalexpr . '": ' . value 858 endif 859 860 if s:evalexpr[0] != '*' && value =~ '^0x' && value != '0x0' && value !~ '"$' 861 " Looks like a pointer, also display what it points to. 862 let s:ignoreEvalError = 1 863 call s:SendEval('*' . s:evalexpr) 864 else 865 let s:evalFromBalloonExpr = 0 866 endif 867endfunc 868 869" Show a balloon with information of the variable under the mouse pointer, 870" if there is any. 871func TermDebugBalloonExpr() 872 if v:beval_winid != s:sourcewin 873 return '' 874 endif 875 if !s:stopped 876 " Only evaluate when stopped, otherwise setting a breakpoint using the 877 " mouse triggers a balloon. 878 return '' 879 endif 880 let s:evalFromBalloonExpr = 1 881 let s:evalFromBalloonExprResult = '' 882 let s:ignoreEvalError = 1 883 call s:SendEval(v:beval_text) 884 return '' 885endfunc 886 887" Handle an error. 888func s:HandleError(msg) 889 if s:ignoreEvalError 890 " Result of s:SendEval() failed, ignore. 891 let s:ignoreEvalError = 0 892 let s:evalFromBalloonExpr = 0 893 return 894 endif 895 echoerr substitute(a:msg, '.*msg="\(.*\)"', '\1', '') 896endfunc 897 898func s:GotoSourcewinOrCreateIt() 899 if !win_gotoid(s:sourcewin) 900 new 901 let s:sourcewin = win_getid(winnr()) 902 call s:InstallWinbar() 903 endif 904endfunc 905 906" Handle stopping and running message from gdb. 907" Will update the sign that shows the current position. 908func s:HandleCursor(msg) 909 let wid = win_getid(winnr()) 910 911 if a:msg =~ '^\*stopped' 912 call ch_log('program stopped') 913 let s:stopped = 1 914 elseif a:msg =~ '^\*running' 915 call ch_log('program running') 916 let s:stopped = 0 917 endif 918 919 if a:msg =~ 'fullname=' 920 let fname = s:GetFullname(a:msg) 921 else 922 let fname = '' 923 endif 924 if a:msg =~ '^\(\*stopped\|=thread-selected\)' && filereadable(fname) 925 let lnum = substitute(a:msg, '.*line="\([^"]*\)".*', '\1', '') 926 if lnum =~ '^[0-9]*$' 927 call s:GotoSourcewinOrCreateIt() 928 if expand('%:p') != fnamemodify(fname, ':p') 929 if &modified 930 " TODO: find existing window 931 exe 'split ' . fnameescape(fname) 932 let s:sourcewin = win_getid(winnr()) 933 call s:InstallWinbar() 934 else 935 exe 'edit ' . fnameescape(fname) 936 endif 937 endif 938 exe lnum 939 exe 'sign unplace ' . s:pc_id 940 exe 'sign place ' . s:pc_id . ' line=' . lnum . ' name=debugPC file=' . fname 941 setlocal signcolumn=yes 942 endif 943 elseif !s:stopped || fname != '' 944 exe 'sign unplace ' . s:pc_id 945 endif 946 947 call win_gotoid(wid) 948endfunc 949 950let s:BreakpointSigns = [] 951 952func s:CreateBreakpoint(id, subid) 953 let nr = printf('%d.%d', a:id, a:subid) 954 if index(s:BreakpointSigns, nr) == -1 955 call add(s:BreakpointSigns, nr) 956 exe "sign define debugBreakpoint" . nr . " text=" . substitute(nr, '\..*', '', '') . " texthl=debugBreakpoint" 957 endif 958endfunc 959 960func! s:SplitMsg(s) 961 return split(a:s, '{.\{-}}\zs') 962endfunction 963 964" Handle setting a breakpoint 965" Will update the sign that shows the breakpoint 966func s:HandleNewBreakpoint(msg) 967 if a:msg !~ 'fullname=' 968 " a watch does not have a file name 969 return 970 endif 971 for msg in s:SplitMsg(a:msg) 972 let fname = s:GetFullname(msg) 973 if empty(fname) 974 continue 975 endif 976 let nr = substitute(msg, '.*number="\([0-9.]*\)\".*', '\1', '') 977 if empty(nr) 978 return 979 endif 980 981 " If "nr" is 123 it becomes "123.0" and subid is "0". 982 " If "nr" is 123.4 it becomes "123.4.0" and subid is "4"; "0" is discarded. 983 let [id, subid; _] = map(split(nr . '.0', '\.'), 'v:val + 0') 984 call s:CreateBreakpoint(id, subid) 985 986 if has_key(s:breakpoints, id) 987 let entries = s:breakpoints[id] 988 else 989 let entries = {} 990 let s:breakpoints[id] = entries 991 endif 992 if has_key(entries, subid) 993 let entry = entries[subid] 994 else 995 let entry = {} 996 let entries[subid] = entry 997 endif 998 999 let lnum = substitute(msg, '.*line="\([^"]*\)".*', '\1', '') 1000 let entry['fname'] = fname 1001 let entry['lnum'] = lnum 1002 1003 let bploc = printf('%s:%d', fname, lnum) 1004 if !has_key(s:breakpoint_locations, bploc) 1005 let s:breakpoint_locations[bploc] = [] 1006 endif 1007 let s:breakpoint_locations[bploc] += [id] 1008 1009 if bufloaded(fname) 1010 call s:PlaceSign(id, subid, entry) 1011 endif 1012 endfor 1013endfunc 1014 1015func s:PlaceSign(id, subid, entry) 1016 let nr = printf('%d.%d', a:id, a:subid) 1017 exe 'sign place ' . s:Breakpoint2SignNumber(a:id, a:subid) . ' line=' . a:entry['lnum'] . ' name=debugBreakpoint' . nr . ' file=' . a:entry['fname'] 1018 let a:entry['placed'] = 1 1019endfunc 1020 1021" Handle deleting a breakpoint 1022" Will remove the sign that shows the breakpoint 1023func s:HandleBreakpointDelete(msg) 1024 let id = substitute(a:msg, '.*id="\([0-9]*\)\".*', '\1', '') + 0 1025 if empty(id) 1026 return 1027 endif 1028 if has_key(s:breakpoints, id) 1029 for [subid, entry] in items(s:breakpoints[id]) 1030 if has_key(entry, 'placed') 1031 exe 'sign unplace ' . s:Breakpoint2SignNumber(id, subid) 1032 unlet entry['placed'] 1033 endif 1034 endfor 1035 unlet s:breakpoints[id] 1036 endif 1037endfunc 1038 1039" Handle the debugged program starting to run. 1040" Will store the process ID in s:pid 1041func s:HandleProgramRun(msg) 1042 let nr = substitute(a:msg, '.*pid="\([0-9]*\)\".*', '\1', '') + 0 1043 if nr == 0 1044 return 1045 endif 1046 let s:pid = nr 1047 call ch_log('Detected process ID: ' . s:pid) 1048endfunc 1049 1050" Handle a BufRead autocommand event: place any signs. 1051func s:BufRead() 1052 let fname = expand('<afile>:p') 1053 for [id, entries] in items(s:breakpoints) 1054 for [subid, entry] in items(entries) 1055 if entry['fname'] == fname 1056 call s:PlaceSign(id, subid, entry) 1057 endif 1058 endfor 1059 endfor 1060endfunc 1061 1062" Handle a BufUnloaded autocommand event: unplace any signs. 1063func s:BufUnloaded() 1064 let fname = expand('<afile>:p') 1065 for [id, entries] in items(s:breakpoints) 1066 for [subid, entry] in items(entries) 1067 if entry['fname'] == fname 1068 let entry['placed'] = 0 1069 endif 1070 endfor 1071 endfor 1072endfunc 1073 1074let &cpo = s:keepcpo 1075unlet s:keepcpo 1076