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