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 78func s:Breakpoint2SignNumber(nr) 79 let t = split(a:nr, '\.') 80 return t[0] * 1000 + (len(t) == 2 ? t[1] : 0) 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 let s:breakpoints = {} 366 367 augroup TermDebug 368 au BufRead * call s:BufRead() 369 au BufUnload * call s:BufUnloaded() 370 au OptionSet background call s:Highlight(0, v:option_old, v:option_new) 371 augroup END 372 373 " Run the command if the bang attribute was given and got to the debug 374 " window. 375 if get(a:dict, 'bang', 0) 376 call s:SendCommand('-exec-run') 377 call win_gotoid(s:ptywin) 378 endif 379endfunc 380 381" Send a command to gdb. "cmd" is the string without line terminator. 382func s:SendCommand(cmd) 383 call ch_log('sending to gdb: ' . a:cmd) 384 if s:way == 'prompt' 385 call ch_sendraw(s:gdb_channel, a:cmd . "\n") 386 else 387 call term_sendkeys(s:commbuf, a:cmd . "\r") 388 endif 389endfunc 390 391" This is global so that a user can create their mappings with this. 392func TermDebugSendCommand(cmd) 393 if s:way == 'prompt' 394 call ch_sendraw(s:gdb_channel, a:cmd . "\n") 395 else 396 let do_continue = 0 397 if !s:stopped 398 let do_continue = 1 399 call s:SendCommand('-exec-interrupt') 400 sleep 10m 401 endif 402 call term_sendkeys(s:gdbbuf, a:cmd . "\r") 403 if do_continue 404 Continue 405 endif 406 endif 407endfunc 408 409" Function called when entering a line in the prompt buffer. 410func s:PromptCallback(text) 411 call s:SendCommand(a:text) 412endfunc 413 414" Function called when pressing CTRL-C in the prompt buffer and when placing a 415" breakpoint. 416func s:PromptInterrupt() 417 call ch_log('Interrupting gdb') 418 if has('win32') 419 " Using job_stop() does not work on MS-Windows, need to send SIGTRAP to 420 " the debugger program so that gdb responds again. 421 if s:pid == 0 422 echoerr 'Cannot interrupt gdb, did not find a process ID' 423 else 424 call debugbreak(s:pid) 425 endif 426 else 427 call job_stop(s:gdbjob, 'int') 428 endif 429endfunc 430 431" Function called when gdb outputs text. 432func s:GdbOutCallback(channel, text) 433 call ch_log('received from gdb: ' . a:text) 434 435 " Drop the gdb prompt, we have our own. 436 " Drop status and echo'd commands. 437 if a:text == '(gdb) ' || a:text == '^done' || a:text[0] == '&' 438 return 439 endif 440 if a:text =~ '^^error,msg=' 441 let text = s:DecodeMessage(a:text[11:]) 442 if exists('s:evalexpr') && text =~ 'A syntax error in expression, near\|No symbol .* in current context' 443 " Silently drop evaluation errors. 444 unlet s:evalexpr 445 return 446 endif 447 elseif a:text[0] == '~' 448 let text = s:DecodeMessage(a:text[1:]) 449 else 450 call s:CommOutput(a:channel, a:text) 451 return 452 endif 453 454 let curwinid = win_getid(winnr()) 455 call win_gotoid(s:gdbwin) 456 457 " Add the output above the current prompt. 458 call append(line('$') - 1, text) 459 set modified 460 461 call win_gotoid(curwinid) 462endfunc 463 464" Decode a message from gdb. quotedText starts with a ", return the text up 465" to the next ", unescaping characters. 466func s:DecodeMessage(quotedText) 467 if a:quotedText[0] != '"' 468 echoerr 'DecodeMessage(): missing quote in ' . a:quotedText 469 return 470 endif 471 let result = '' 472 let i = 1 473 while a:quotedText[i] != '"' && i < len(a:quotedText) 474 if a:quotedText[i] == '\' 475 let i += 1 476 if a:quotedText[i] == 'n' 477 " drop \n 478 let i += 1 479 continue 480 endif 481 endif 482 let result .= a:quotedText[i] 483 let i += 1 484 endwhile 485 return result 486endfunc 487 488" Extract the "name" value from a gdb message with fullname="name". 489func s:GetFullname(msg) 490 if a:msg !~ 'fullname' 491 return '' 492 endif 493 let name = s:DecodeMessage(substitute(a:msg, '.*fullname=', '', '')) 494 if has('win32') && name =~ ':\\\\' 495 " sometimes the name arrives double-escaped 496 let name = substitute(name, '\\\\', '\\', 'g') 497 endif 498 return name 499endfunc 500 501func s:EndTermDebug(job, status) 502 exe 'bwipe! ' . s:commbuf 503 unlet s:gdbwin 504 505 call s:EndDebugCommon() 506endfunc 507 508func s:EndDebugCommon() 509 let curwinid = win_getid(winnr()) 510 511 if exists('s:ptybuf') && s:ptybuf 512 exe 'bwipe! ' . s:ptybuf 513 endif 514 515 call win_gotoid(s:sourcewin) 516 let &signcolumn = s:startsigncolumn 517 call s:DeleteCommands() 518 519 call win_gotoid(curwinid) 520 521 if s:save_columns > 0 522 let &columns = s:save_columns 523 endif 524 525 if has("balloon_eval") || has("balloon_eval_term") 526 set balloonexpr= 527 if has("balloon_eval") 528 set noballooneval 529 endif 530 if has("balloon_eval_term") 531 set noballoonevalterm 532 endif 533 endif 534 535 au! TermDebug 536endfunc 537 538func s:EndPromptDebug(job, status) 539 let curwinid = win_getid(winnr()) 540 call win_gotoid(s:gdbwin) 541 set nomodified 542 close 543 if curwinid != s:gdbwin 544 call win_gotoid(curwinid) 545 endif 546 547 call s:EndDebugCommon() 548 unlet s:gdbwin 549 call ch_log("Returning from EndPromptDebug()") 550endfunc 551 552" Handle a message received from gdb on the GDB/MI interface. 553func s:CommOutput(chan, msg) 554 let msgs = split(a:msg, "\r") 555 556 for msg in msgs 557 " remove prefixed NL 558 if msg[0] == "\n" 559 let msg = msg[1:] 560 endif 561 if msg != '' 562 if msg =~ '^\(\*stopped\|\*running\|=thread-selected\)' 563 call s:HandleCursor(msg) 564 elseif msg =~ '^\^done,bkpt=' || msg =~ '^=breakpoint-created,' 565 call s:HandleNewBreakpoint(msg) 566 elseif msg =~ '^=breakpoint-deleted,' 567 call s:HandleBreakpointDelete(msg) 568 elseif msg =~ '^=thread-group-started' 569 call s:HandleProgramRun(msg) 570 elseif msg =~ '^\^done,value=' 571 call s:HandleEvaluate(msg) 572 elseif msg =~ '^\^error,msg=' 573 call s:HandleError(msg) 574 endif 575 endif 576 endfor 577endfunc 578 579" Install commands in the current window to control the debugger. 580func s:InstallCommands() 581 let save_cpo = &cpo 582 set cpo&vim 583 584 command Break call s:SetBreakpoint() 585 command Clear call s:ClearBreakpoint() 586 command Step call s:SendCommand('-exec-step') 587 command Over call s:SendCommand('-exec-next') 588 command Finish call s:SendCommand('-exec-finish') 589 command -nargs=* Run call s:Run(<q-args>) 590 command -nargs=* Arguments call s:SendCommand('-exec-arguments ' . <q-args>) 591 command Stop call s:SendCommand('-exec-interrupt') 592 593 " using -exec-continue results in CTRL-C in gdb window not working 594 if s:way == 'prompt' 595 command Continue call s:SendCommand('continue') 596 else 597 command Continue call term_sendkeys(s:gdbbuf, "continue\r") 598 endif 599 600 command -range -nargs=* Evaluate call s:Evaluate(<range>, <q-args>) 601 command Gdb call win_gotoid(s:gdbwin) 602 command Program call win_gotoid(s:ptywin) 603 command Source call s:GotoSourcewinOrCreateIt() 604 command Winbar call s:InstallWinbar() 605 606 " TODO: can the K mapping be restored? 607 nnoremap K :Evaluate<CR> 608 609 if has('menu') && &mouse != '' 610 call s:InstallWinbar() 611 612 if !exists('g:termdebug_popup') || g:termdebug_popup != 0 613 let s:saved_mousemodel = &mousemodel 614 let &mousemodel = 'popup_setpos' 615 an 1.200 PopUp.-SEP3- <Nop> 616 an 1.210 PopUp.Set\ breakpoint :Break<CR> 617 an 1.220 PopUp.Clear\ breakpoint :Clear<CR> 618 an 1.230 PopUp.Evaluate :Evaluate<CR> 619 endif 620 endif 621 622 let &cpo = save_cpo 623endfunc 624 625let s:winbar_winids = [] 626 627" Install the window toolbar in the current window. 628func s:InstallWinbar() 629 if has('menu') && &mouse != '' 630 nnoremenu WinBar.Step :Step<CR> 631 nnoremenu WinBar.Next :Over<CR> 632 nnoremenu WinBar.Finish :Finish<CR> 633 nnoremenu WinBar.Cont :Continue<CR> 634 nnoremenu WinBar.Stop :Stop<CR> 635 nnoremenu WinBar.Eval :Evaluate<CR> 636 call add(s:winbar_winids, win_getid(winnr())) 637 endif 638endfunc 639 640" Delete installed debugger commands in the current window. 641func s:DeleteCommands() 642 delcommand Break 643 delcommand Clear 644 delcommand Step 645 delcommand Over 646 delcommand Finish 647 delcommand Run 648 delcommand Arguments 649 delcommand Stop 650 delcommand Continue 651 delcommand Evaluate 652 delcommand Gdb 653 delcommand Program 654 delcommand Source 655 delcommand Winbar 656 657 nunmap K 658 659 if has('menu') 660 " Remove the WinBar entries from all windows where it was added. 661 let curwinid = win_getid(winnr()) 662 for winid in s:winbar_winids 663 if win_gotoid(winid) 664 aunmenu WinBar.Step 665 aunmenu WinBar.Next 666 aunmenu WinBar.Finish 667 aunmenu WinBar.Cont 668 aunmenu WinBar.Stop 669 aunmenu WinBar.Eval 670 endif 671 endfor 672 call win_gotoid(curwinid) 673 let s:winbar_winids = [] 674 675 if exists('s:saved_mousemodel') 676 let &mousemodel = s:saved_mousemodel 677 unlet s:saved_mousemodel 678 aunmenu PopUp.-SEP3- 679 aunmenu PopUp.Set\ breakpoint 680 aunmenu PopUp.Clear\ breakpoint 681 aunmenu PopUp.Evaluate 682 endif 683 endif 684 685 exe 'sign unplace ' . s:pc_id 686 for key in keys(s:breakpoints) 687 exe 'sign unplace ' . (s:break_id + s:Breakpoint2SignNumber(key)) 688 endfor 689 unlet s:breakpoints 690 691 sign undefine debugPC 692 for val in s:BreakpointSigns 693 exe "sign undefine debugBreakpoint" . val 694 endfor 695 let s:BreakpointSigns = [] 696endfunc 697 698" :Break - Set a breakpoint at the cursor position. 699func s:SetBreakpoint() 700 " Setting a breakpoint may not work while the program is running. 701 " Interrupt to make it work. 702 let do_continue = 0 703 if !s:stopped 704 let do_continue = 1 705 if s:way == 'prompt' 706 call s:PromptInterrupt() 707 else 708 call s:SendCommand('-exec-interrupt') 709 endif 710 sleep 10m 711 endif 712 " Use the fname:lnum format, older gdb can't handle --source. 713 call s:SendCommand('-break-insert ' 714 \ . fnameescape(expand('%:p')) . ':' . line('.')) 715 if do_continue 716 call s:SendCommand('-exec-continue') 717 endif 718endfunc 719 720" :Clear - Delete a breakpoint at the cursor position. 721func s:ClearBreakpoint() 722 let fname = fnameescape(expand('%:p')) 723 let lnum = line('.') 724 for [key, val] in items(s:breakpoints) 725 if val['fname'] == fname && val['lnum'] == lnum 726 call s:SendCommand('-break-delete ' . key) 727 " Assume this always wors, the reply is simply "^done". 728 exe 'sign unplace ' . (s:break_id + s:Breakpoint2SignNumber(key)) 729 unlet s:breakpoints[key] 730 break 731 endif 732 endfor 733endfunc 734 735func s:Run(args) 736 if a:args != '' 737 call s:SendCommand('-exec-arguments ' . a:args) 738 endif 739 call s:SendCommand('-exec-run') 740endfunc 741 742func s:SendEval(expr) 743 call s:SendCommand('-data-evaluate-expression "' . a:expr . '"') 744 let s:evalexpr = a:expr 745endfunc 746 747" :Evaluate - evaluate what is under the cursor 748func s:Evaluate(range, arg) 749 if a:arg != '' 750 let expr = a:arg 751 elseif a:range == 2 752 let pos = getcurpos() 753 let reg = getreg('v', 1, 1) 754 let regt = getregtype('v') 755 normal! gv"vy 756 let expr = @v 757 call setpos('.', pos) 758 call setreg('v', reg, regt) 759 else 760 let expr = expand('<cexpr>') 761 endif 762 let s:ignoreEvalError = 0 763 call s:SendEval(expr) 764endfunc 765 766let s:ignoreEvalError = 0 767let s:evalFromBalloonExpr = 0 768 769" Handle the result of data-evaluate-expression 770func s:HandleEvaluate(msg) 771 let value = substitute(a:msg, '.*value="\(.*\)"', '\1', '') 772 let value = substitute(value, '\\"', '"', 'g') 773 if s:evalFromBalloonExpr 774 if s:evalFromBalloonExprResult == '' 775 let s:evalFromBalloonExprResult = s:evalexpr . ': ' . value 776 else 777 let s:evalFromBalloonExprResult .= ' = ' . value 778 endif 779 call balloon_show(s:evalFromBalloonExprResult) 780 else 781 echomsg '"' . s:evalexpr . '": ' . value 782 endif 783 784 if s:evalexpr[0] != '*' && value =~ '^0x' && value != '0x0' && value !~ '"$' 785 " Looks like a pointer, also display what it points to. 786 let s:ignoreEvalError = 1 787 call s:SendEval('*' . s:evalexpr) 788 else 789 let s:evalFromBalloonExpr = 0 790 endif 791endfunc 792 793" Show a balloon with information of the variable under the mouse pointer, 794" if there is any. 795func TermDebugBalloonExpr() 796 if v:beval_winid != s:sourcewin 797 return 798 endif 799 if !s:stopped 800 " Only evaluate when stopped, otherwise setting a breakpoint using the 801 " mouse triggers a balloon. 802 return 803 endif 804 let s:evalFromBalloonExpr = 1 805 let s:evalFromBalloonExprResult = '' 806 let s:ignoreEvalError = 1 807 call s:SendEval(v:beval_text) 808 return '' 809endfunc 810 811" Handle an error. 812func s:HandleError(msg) 813 if s:ignoreEvalError 814 " Result of s:SendEval() failed, ignore. 815 let s:ignoreEvalError = 0 816 let s:evalFromBalloonExpr = 0 817 return 818 endif 819 echoerr substitute(a:msg, '.*msg="\(.*\)"', '\1', '') 820endfunc 821 822func s:GotoSourcewinOrCreateIt() 823 if !win_gotoid(s:sourcewin) 824 new 825 let s:sourcewin = win_getid(winnr()) 826 call s:InstallWinbar() 827 endif 828endfunc 829 830" Handle stopping and running message from gdb. 831" Will update the sign that shows the current position. 832func s:HandleCursor(msg) 833 let wid = win_getid(winnr()) 834 835 if a:msg =~ '^\*stopped' 836 call ch_log('program stopped') 837 let s:stopped = 1 838 elseif a:msg =~ '^\*running' 839 call ch_log('program running') 840 let s:stopped = 0 841 endif 842 843 if a:msg =~ 'fullname=' 844 let fname = s:GetFullname(a:msg) 845 else 846 let fname = '' 847 endif 848 if a:msg =~ '^\(\*stopped\|=thread-selected\)' && filereadable(fname) 849 let lnum = substitute(a:msg, '.*line="\([^"]*\)".*', '\1', '') 850 if lnum =~ '^[0-9]*$' 851 call s:GotoSourcewinOrCreateIt() 852 if expand('%:p') != fnamemodify(fname, ':p') 853 if &modified 854 " TODO: find existing window 855 exe 'split ' . fnameescape(fname) 856 let s:sourcewin = win_getid(winnr()) 857 call s:InstallWinbar() 858 else 859 exe 'edit ' . fnameescape(fname) 860 endif 861 endif 862 exe lnum 863 exe 'sign unplace ' . s:pc_id 864 exe 'sign place ' . s:pc_id . ' line=' . lnum . ' name=debugPC file=' . fname 865 setlocal signcolumn=yes 866 endif 867 elseif !s:stopped || fname != '' 868 exe 'sign unplace ' . s:pc_id 869 endif 870 871 call win_gotoid(wid) 872endfunc 873 874let s:BreakpointSigns = [] 875 876func s:CreateBreakpoint(nr) 877 if index(s:BreakpointSigns, a:nr) == -1 878 call add(s:BreakpointSigns, a:nr) 879 exe "sign define debugBreakpoint" . a:nr . " text=" . substitute(a:nr, '\..*', '', '') . " texthl=debugBreakpoint" 880 endif 881endfunc 882 883func s:SplitMsg(s) 884 return split(a:s, '{\%([a-z-]\+=[^,]\+,*\)\+}\zs') 885endfunction 886 887" Handle setting a breakpoint 888" Will update the sign that shows the breakpoint 889func s:HandleNewBreakpoint(msg) 890 if a:msg !~ 'fullname=' 891 " a watch does not have a file name 892 return 893 endif 894 for msg in s:SplitMsg(a:msg) 895 let fname = s:GetFullname(msg) 896 if empty(fname) 897 continue 898 endif 899 let nr = substitute(msg, '.*number="\([0-9.]*\)\".*', '\1', '') 900 if empty(nr) 901 return 902 endif 903 call s:CreateBreakpoint(nr) 904 905 if has_key(s:breakpoints, nr) 906 let entry = s:breakpoints[nr] 907 else 908 let entry = {} 909 let s:breakpoints[nr] = entry 910 endif 911 912 let lnum = substitute(msg, '.*line="\([^"]*\)".*', '\1', '') 913 let entry['fname'] = fname 914 let entry['lnum'] = lnum 915 916 if bufloaded(fname) 917 call s:PlaceSign(nr, entry) 918 endif 919 endfor 920endfunc 921 922func s:PlaceSign(nr, entry) 923 exe 'sign place ' . (s:break_id + s:Breakpoint2SignNumber(a:nr)) . ' line=' . a:entry['lnum'] . ' name=debugBreakpoint' . a:nr . ' file=' . a:entry['fname'] 924 let a:entry['placed'] = 1 925endfunc 926 927" Handle deleting a breakpoint 928" Will remove the sign that shows the breakpoint 929func s:HandleBreakpointDelete(msg) 930 let key = substitute(a:msg, '.*id="\([0-9.]*\)\".*', '\1', '') 931 if empty(key) 932 return 933 endif 934 for [nr, entry] in items(s:breakpoints) 935 if stridx(nr, key) != 0 936 continue 937 endif 938 let entry = s:breakpoints[nr] 939 if has_key(entry, 'placed') 940 exe 'sign unplace ' . (s:break_id + s:Breakpoint2SignNumber(nr)) 941 unlet entry['placed'] 942 endif 943 unlet s:breakpoints[nr] 944 endfor 945endfunc 946 947" Handle the debugged program starting to run. 948" Will store the process ID in s:pid 949func s:HandleProgramRun(msg) 950 let nr = substitute(a:msg, '.*pid="\([0-9]*\)\".*', '\1', '') + 0 951 if nr == 0 952 return 953 endif 954 let s:pid = nr 955 call ch_log('Detected process ID: ' . s:pid) 956endfunc 957 958" Handle a BufRead autocommand event: place any signs. 959func s:BufRead() 960 let fname = expand('<afile>:p') 961 for [nr, entry] in items(s:breakpoints) 962 if entry['fname'] == fname 963 call s:PlaceSign(nr, entry) 964 endif 965 endfor 966endfunc 967 968" Handle a BufUnloaded autocommand event: unplace any signs. 969func s:BufUnloaded() 970 let fname = expand('<afile>:p') 971 for [nr, entry] in items(s:breakpoints) 972 if entry['fname'] == fname 973 let entry['placed'] = 0 974 endif 975 endfor 976endfunc 977 978let &cpo = s:keepcpo 979unlet s:keepcpo 980