github.com/neilgarb/delve@v1.9.2-nobreaks/pkg/terminal/command.go (about) 1 // Package terminal implements functions for responding to user 2 // input and dispatching to appropriate backend commands. 3 package terminal 4 5 //lint:file-ignore ST1005 errors here can be capitalized 6 7 import ( 8 "bufio" 9 "bytes" 10 "errors" 11 "fmt" 12 "go/parser" 13 "go/scanner" 14 "io" 15 "io/ioutil" 16 "math" 17 "os" 18 "os/exec" 19 "path/filepath" 20 "reflect" 21 "regexp" 22 "sort" 23 "strconv" 24 "strings" 25 "text/tabwriter" 26 "time" 27 28 "github.com/cosiner/argv" 29 "github.com/go-delve/delve/pkg/config" 30 "github.com/go-delve/delve/pkg/locspec" 31 "github.com/go-delve/delve/pkg/proc/debuginfod" 32 "github.com/go-delve/delve/service" 33 "github.com/go-delve/delve/service/api" 34 "github.com/go-delve/delve/service/rpc2" 35 ) 36 37 const optimizedFunctionWarning = "Warning: debugging optimized function" 38 39 type cmdPrefix int 40 41 const ( 42 noPrefix = cmdPrefix(0) 43 onPrefix = cmdPrefix(1 << iota) 44 deferredPrefix 45 revPrefix 46 ) 47 48 type callContext struct { 49 Prefix cmdPrefix 50 Scope api.EvalScope 51 Breakpoint *api.Breakpoint 52 } 53 54 func (ctx *callContext) scoped() bool { 55 return ctx.Scope.GoroutineID >= 0 || ctx.Scope.Frame > 0 56 } 57 58 type frameDirection int 59 60 const ( 61 frameSet frameDirection = iota 62 frameUp 63 frameDown 64 ) 65 66 type cmdfunc func(t *Term, ctx callContext, args string) error 67 68 type command struct { 69 aliases []string 70 builtinAliases []string 71 group commandGroup 72 allowedPrefixes cmdPrefix 73 helpMsg string 74 cmdFn cmdfunc 75 } 76 77 // Returns true if the command string matches one of the aliases for this command 78 func (c command) match(cmdstr string) bool { 79 for _, v := range c.aliases { 80 if v == cmdstr { 81 return true 82 } 83 } 84 return false 85 } 86 87 // Commands represents the commands for Delve terminal process. 88 type Commands struct { 89 cmds []command 90 client service.Client 91 frame int // Current frame as set by frame/up/down commands. 92 } 93 94 var ( 95 // longLoadConfig loads more information: 96 // * Follows pointers 97 // * Loads more array values 98 // * Does not limit struct fields 99 longLoadConfig = api.LoadConfig{FollowPointers: true, MaxVariableRecurse: 1, MaxStringLen: 64, MaxArrayValues: 64, MaxStructFields: -1} 100 // ShortLoadConfig loads less information, not following pointers 101 // and limiting struct fields loaded to 3. 102 ShortLoadConfig = api.LoadConfig{MaxStringLen: 64, MaxStructFields: 3} 103 ) 104 105 // byFirstAlias will sort by the first 106 // alias of a command. 107 type byFirstAlias []command 108 109 func (a byFirstAlias) Len() int { return len(a) } 110 func (a byFirstAlias) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 111 func (a byFirstAlias) Less(i, j int) bool { return a[i].aliases[0] < a[j].aliases[0] } 112 113 // DebugCommands returns a Commands struct with default commands defined. 114 func DebugCommands(client service.Client) *Commands { 115 c := &Commands{client: client} 116 117 c.cmds = []command{ 118 {aliases: []string{"help", "h"}, cmdFn: c.help, helpMsg: `Prints the help message. 119 120 help [command] 121 122 Type "help" followed by the name of a command for more information about it.`}, 123 {aliases: []string{"break", "b"}, group: breakCmds, cmdFn: breakpoint, helpMsg: `Sets a breakpoint. 124 125 break [name] [locspec] 126 127 See $GOPATH/src/github.com/go-delve/delve/Documentation/cli/locspec.md for the syntax of locspec. If locspec is omitted a breakpoint will be set on the current line. 128 129 See also: "help on", "help cond" and "help clear"`}, 130 {aliases: []string{"trace", "t"}, group: breakCmds, cmdFn: tracepoint, allowedPrefixes: onPrefix, helpMsg: `Set tracepoint. 131 132 trace [name] [locspec] 133 134 A tracepoint is a breakpoint that does not stop the execution of the program, instead when the tracepoint is hit a notification is displayed. See $GOPATH/src/github.com/go-delve/delve/Documentation/cli/locspec.md for the syntax of locspec. If locspec is omitted a tracepoint will be set on the current line. 135 136 See also: "help on", "help cond" and "help clear"`}, 137 {aliases: []string{"watch"}, group: breakCmds, cmdFn: watchpoint, helpMsg: `Set watchpoint. 138 139 watch [-r|-w|-rw] <expr> 140 141 -r stops when the memory location is read 142 -w stops when the memory location is written 143 -rw stops when the memory location is read or written 144 145 The memory location is specified with the same expression language used by 'print', for example: 146 147 watch v 148 149 will watch the address of variable 'v'. 150 151 Note that writes that do not change the value of the watched memory address might not be reported. 152 153 See also: "help print".`}, 154 {aliases: []string{"restart", "r"}, group: runCmds, cmdFn: restart, helpMsg: `Restart process. 155 156 For recorded targets the command takes the following forms: 157 158 restart resets to the start of the recording 159 restart [checkpoint] resets the recording to the given checkpoint 160 restart -r [newargv...] [redirects...] re-records the target process 161 162 For live targets the command takes the following forms: 163 164 restart [newargv...] [redirects...] restarts the process 165 166 If newargv is omitted the process is restarted (or re-recorded) with the same argument vector. 167 If -noargs is specified instead, the argument vector is cleared. 168 169 A list of file redirections can be specified after the new argument list to override the redirections defined using the '--redirect' command line option. A syntax similar to Unix shells is used: 170 171 <input.txt redirects the standard input of the target process from input.txt 172 >output.txt redirects the standard output of the target process to output.txt 173 2>error.txt redirects the standard error of the target process to error.txt 174 `}, 175 {aliases: []string{"rebuild"}, group: runCmds, cmdFn: c.rebuild, allowedPrefixes: revPrefix, helpMsg: "Rebuild the target executable and restarts it. It does not work if the executable was not built by delve."}, 176 {aliases: []string{"continue", "c"}, group: runCmds, cmdFn: c.cont, allowedPrefixes: revPrefix, helpMsg: `Run until breakpoint or program termination. 177 178 continue [<locspec>] 179 180 Optional locspec argument allows you to continue until a specific location is reached. The program will halt if a breakpoint is hit before reaching the specified location. 181 182 For example: 183 184 continue main.main 185 continue encoding/json.Marshal 186 `}, 187 {aliases: []string{"step", "s"}, group: runCmds, cmdFn: c.step, allowedPrefixes: revPrefix, helpMsg: "Single step through program."}, 188 {aliases: []string{"step-instruction", "si"}, group: runCmds, allowedPrefixes: revPrefix, cmdFn: c.stepInstruction, helpMsg: "Single step a single cpu instruction."}, 189 {aliases: []string{"next", "n"}, group: runCmds, cmdFn: c.next, allowedPrefixes: revPrefix, helpMsg: `Step over to next source line. 190 191 next [count] 192 193 Optional [count] argument allows you to skip multiple lines. 194 `}, 195 {aliases: []string{"stepout", "so"}, group: runCmds, allowedPrefixes: revPrefix, cmdFn: c.stepout, helpMsg: "Step out of the current function."}, 196 {aliases: []string{"call"}, group: runCmds, cmdFn: c.call, helpMsg: `Resumes process, injecting a function call (EXPERIMENTAL!!!) 197 198 call [-unsafe] <function call expression> 199 200 Current limitations: 201 - only pointers to stack-allocated objects can be passed as argument. 202 - only some automatic type conversions are supported. 203 - functions can only be called on running goroutines that are not 204 executing the runtime. 205 - the current goroutine needs to have at least 256 bytes of free space on 206 the stack. 207 - functions can only be called when the goroutine is stopped at a safe 208 point. 209 - calling a function will resume execution of all goroutines. 210 - only supported on linux's native backend. 211 `}, 212 {aliases: []string{"threads"}, group: goroutineCmds, cmdFn: threads, helpMsg: "Print out info for every traced thread."}, 213 {aliases: []string{"thread", "tr"}, group: goroutineCmds, cmdFn: thread, helpMsg: `Switch to the specified thread. 214 215 thread <id>`}, 216 {aliases: []string{"clear"}, group: breakCmds, cmdFn: clear, helpMsg: `Deletes breakpoint. 217 218 clear <breakpoint name or id>`}, 219 {aliases: []string{"clearall"}, group: breakCmds, cmdFn: clearAll, helpMsg: `Deletes multiple breakpoints. 220 221 clearall [<locspec>] 222 223 If called with the locspec argument it will delete all the breakpoints matching the locspec. If locspec is omitted all breakpoints are deleted.`}, 224 {aliases: []string{"toggle"}, group: breakCmds, cmdFn: toggle, helpMsg: `Toggles on or off a breakpoint. 225 226 toggle <breakpoint name or id>`}, 227 {aliases: []string{"goroutines", "grs"}, group: goroutineCmds, cmdFn: goroutines, helpMsg: `List program goroutines. 228 229 goroutines [-u|-r|-g|-s] [-t [depth]] [-l] [-with loc expr] [-without loc expr] [-group argument] 230 231 Print out info for every goroutine. The flag controls what information is shown along with each goroutine: 232 233 -u displays location of topmost stackframe in user code (default) 234 -r displays location of topmost stackframe (including frames inside private runtime functions) 235 -g displays location of go instruction that created the goroutine 236 -s displays location of the start function 237 -t displays goroutine's stacktrace (an optional depth value can be specified, default: 10) 238 -l displays goroutine's labels 239 240 If no flag is specified the default is -u, i.e. the first frame within the first 30 frames that is not executing a runtime private function. 241 242 FILTERING 243 244 If -with or -without are specified only goroutines that match the given condition are returned. 245 246 To only display goroutines where the specified location contains (or does not contain, for -without and -wo) expr as a substring, use: 247 248 goroutines -with (userloc|curloc|goloc|startloc) expr 249 goroutines -w (userloc|curloc|goloc|startloc) expr 250 goroutines -without (userloc|curloc|goloc|startloc) expr 251 goroutines -wo (userloc|curloc|goloc|startloc) expr 252 253 To only display goroutines that have (or do not have) the specified label key and value, use: 254 255 256 goroutines -with label key=value 257 goroutines -without label key=value 258 259 To only display goroutines that have (or do not have) the specified label key, use: 260 261 goroutines -with label key 262 goroutines -without label key 263 264 To only display goroutines that are running (or are not running) on a OS thread, use: 265 266 267 goroutines -with running 268 goroutines -without running 269 270 To only display user (or runtime) goroutines, use: 271 272 goroutines -with user 273 goroutines -without user 274 275 GROUPING 276 277 goroutines -group (userloc|curloc|goloc|startloc|running|user) 278 279 Groups goroutines by the given location, running status or user classification, up to 5 goroutines per group will be displayed as well as the total number of goroutines in the group. 280 281 goroutines -group label key 282 283 Groups goroutines by the value of the label with the specified key. 284 `}, 285 {aliases: []string{"goroutine", "gr"}, group: goroutineCmds, allowedPrefixes: onPrefix, cmdFn: c.goroutine, helpMsg: `Shows or changes current goroutine 286 287 goroutine 288 goroutine <id> 289 goroutine <id> <command> 290 291 Called without arguments it will show information about the current goroutine. 292 Called with a single argument it will switch to the specified goroutine. 293 Called with more arguments it will execute a command on the specified goroutine.`}, 294 {aliases: []string{"breakpoints", "bp"}, group: breakCmds, cmdFn: breakpoints, helpMsg: `Print out info for active breakpoints. 295 296 breakpoints [-a] 297 298 Specifying -a prints all physical breakpoint, including internal breakpoints.`}, 299 {aliases: []string{"print", "p"}, group: dataCmds, allowedPrefixes: onPrefix | deferredPrefix, cmdFn: printVar, helpMsg: `Evaluate an expression. 300 301 [goroutine <n>] [frame <m>] print [%format] <expression> 302 303 See Documentation/cli/expr.md for a description of supported expressions. 304 305 The optional format argument is a format specifier, like the ones used by the fmt package. For example "print %x v" will print v as an hexadecimal number.`}, 306 {aliases: []string{"whatis"}, group: dataCmds, cmdFn: whatisCommand, helpMsg: `Prints type of an expression. 307 308 whatis <expression>`}, 309 {aliases: []string{"set"}, group: dataCmds, cmdFn: setVar, helpMsg: `Changes the value of a variable. 310 311 [goroutine <n>] [frame <m>] set <variable> = <value> 312 313 See Documentation/cli/expr.md for a description of supported expressions. Only numerical variables and pointers can be changed.`}, 314 {aliases: []string{"sources"}, cmdFn: sources, helpMsg: `Print list of source files. 315 316 sources [<regex>] 317 318 If regex is specified only the source files matching it will be returned.`}, 319 {aliases: []string{"funcs"}, cmdFn: funcs, helpMsg: `Print list of functions. 320 321 funcs [<regex>] 322 323 If regex is specified only the functions matching it will be returned.`}, 324 {aliases: []string{"types"}, cmdFn: types, helpMsg: `Print list of types 325 326 types [<regex>] 327 328 If regex is specified only the types matching it will be returned.`}, 329 {aliases: []string{"args"}, allowedPrefixes: onPrefix | deferredPrefix, group: dataCmds, cmdFn: args, helpMsg: `Print function arguments. 330 331 [goroutine <n>] [frame <m>] args [-v] [<regex>] 332 333 If regex is specified only function arguments with a name matching it will be returned. If -v is specified more information about each function argument will be shown.`}, 334 {aliases: []string{"locals"}, allowedPrefixes: onPrefix | deferredPrefix, group: dataCmds, cmdFn: locals, helpMsg: `Print local variables. 335 336 [goroutine <n>] [frame <m>] locals [-v] [<regex>] 337 338 The name of variables that are shadowed in the current scope will be shown in parenthesis. 339 340 If regex is specified only local variables with a name matching it will be returned. If -v is specified more information about each local variable will be shown.`}, 341 {aliases: []string{"vars"}, cmdFn: vars, group: dataCmds, helpMsg: `Print package variables. 342 343 vars [-v] [<regex>] 344 345 If regex is specified only package variables with a name matching it will be returned. If -v is specified more information about each package variable will be shown.`}, 346 {aliases: []string{"regs"}, cmdFn: regs, group: dataCmds, helpMsg: `Print contents of CPU registers. 347 348 regs [-a] 349 350 Argument -a shows more registers. Individual registers can also be displayed by 'print' and 'display'. See Documentation/cli/expr.md.`}, 351 {aliases: []string{"exit", "quit", "q"}, cmdFn: exitCommand, helpMsg: `Exit the debugger. 352 353 exit [-c] 354 355 When connected to a headless instance started with the --accept-multiclient, pass -c to resume the execution of the target process before disconnecting.`}, 356 {aliases: []string{"list", "ls", "l"}, cmdFn: listCommand, helpMsg: `Show source code. 357 358 [goroutine <n>] [frame <m>] list [<locspec>] 359 360 Show source around current point or provided locspec. 361 362 For example: 363 364 frame 1 list 69 365 list testvariables.go:10000 366 list main.main:30 367 list 40`}, 368 {aliases: []string{"stack", "bt"}, allowedPrefixes: onPrefix, group: stackCmds, cmdFn: stackCommand, helpMsg: `Print stack trace. 369 370 [goroutine <n>] [frame <m>] stack [<depth>] [-full] [-offsets] [-defer] [-a <n>] [-adepth <depth>] [-mode <mode>] 371 372 -full every stackframe is decorated with the value of its local variables and arguments. 373 -offsets prints frame offset of each frame. 374 -defer prints deferred function call stack for each frame. 375 -a <n> prints stacktrace of n ancestors of the selected goroutine (target process must have tracebackancestors enabled) 376 -adepth <depth> configures depth of ancestor stacktrace 377 -mode <mode> specifies the stacktrace mode, possible values are: 378 normal - attempts to automatically switch between cgo frames and go frames 379 simple - disables automatic switch between cgo and go 380 fromg - starts from the registers stored in the runtime.g struct 381 `}, 382 {aliases: []string{"frame"}, 383 group: stackCmds, 384 cmdFn: func(t *Term, ctx callContext, arg string) error { 385 return c.frameCommand(t, ctx, arg, frameSet) 386 }, 387 helpMsg: `Set the current frame, or execute command on a different frame. 388 389 frame <m> 390 frame <m> <command> 391 392 The first form sets frame used by subsequent commands such as "print" or "set". 393 The second form runs the command on the given frame.`}, 394 {aliases: []string{"up"}, 395 group: stackCmds, 396 cmdFn: func(t *Term, ctx callContext, arg string) error { 397 return c.frameCommand(t, ctx, arg, frameUp) 398 }, 399 helpMsg: `Move the current frame up. 400 401 up [<m>] 402 up [<m>] <command> 403 404 Move the current frame up by <m>. The second form runs the command on the given frame.`}, 405 {aliases: []string{"down"}, 406 group: stackCmds, 407 cmdFn: func(t *Term, ctx callContext, arg string) error { 408 return c.frameCommand(t, ctx, arg, frameDown) 409 }, 410 helpMsg: `Move the current frame down. 411 412 down [<m>] 413 down [<m>] <command> 414 415 Move the current frame down by <m>. The second form runs the command on the given frame.`}, 416 {aliases: []string{"deferred"}, group: stackCmds, cmdFn: c.deferredCommand, helpMsg: `Executes command in the context of a deferred call. 417 418 deferred <n> <command> 419 420 Executes the specified command (print, args, locals) in the context of the n-th deferred call in the current frame.`}, 421 {aliases: []string{"source"}, cmdFn: c.sourceCommand, helpMsg: `Executes a file containing a list of delve commands 422 423 source <path> 424 425 If path ends with the .star extension it will be interpreted as a starlark script. See $GOPATH/src/github.com/go-delve/delve/Documentation/cli/starlark.md for the syntax. 426 427 If path is a single '-' character an interactive starlark interpreter will start instead. Type 'exit' to exit.`}, 428 {aliases: []string{"disassemble", "disass"}, cmdFn: disassCommand, helpMsg: `Disassembler. 429 430 [goroutine <n>] [frame <m>] disassemble [-a <start> <end>] [-l <locspec>] 431 432 If no argument is specified the function being executed in the selected stack frame will be executed. 433 434 -a <start> <end> disassembles the specified address range 435 -l <locspec> disassembles the specified function`}, 436 {aliases: []string{"on"}, group: breakCmds, cmdFn: c.onCmd, helpMsg: `Executes a command when a breakpoint is hit. 437 438 on <breakpoint name or id> <command> 439 on <breakpoint name or id> -edit 440 441 442 Supported commands: print, stack, goroutine, trace and cond. 443 To convert a breakpoint into a tracepoint use: 444 445 on <breakpoint name or id> trace 446 447 The command 'on <bp> cond <cond-arguments>' is equivalent to 'cond <bp> <cond-arguments>'. 448 449 The command 'on x -edit' can be used to edit the list of commands executed when the breakpoint is hit.`}, 450 {aliases: []string{"condition", "cond"}, group: breakCmds, cmdFn: conditionCmd, allowedPrefixes: onPrefix, helpMsg: `Set breakpoint condition. 451 452 condition <breakpoint name or id> <boolean expression>. 453 condition -hitcount <breakpoint name or id> <operator> <argument>. 454 condition -clear <breakpoint name or id>. 455 456 Specifies that the breakpoint, tracepoint or watchpoint should break only if the boolean expression is true. 457 458 See Documentation/cli/expr.md for a description of supported expressions. 459 460 With the -hitcount option a condition on the breakpoint hit count can be set, the following operators are supported 461 462 condition -hitcount bp > n 463 condition -hitcount bp >= n 464 condition -hitcount bp < n 465 condition -hitcount bp <= n 466 condition -hitcount bp == n 467 condition -hitcount bp != n 468 condition -hitcount bp % n 469 470 With the -clear option a condtion on the breakpoint can removed. 471 472 The '% n' form means we should stop at the breakpoint when the hitcount is a multiple of n. 473 474 Examples: 475 476 cond 2 i == 10 breakpoint 2 will stop when variable i equals 10 477 cond name runtime.curg.goid == 5 breakpoint 'name' will stop only on goroutine 5 478 cond -clear 2 the condition on breakpoint 2 will be removed 479 `}, 480 {aliases: []string{"config"}, cmdFn: configureCmd, helpMsg: `Changes configuration parameters. 481 482 config -list 483 484 Show all configuration parameters. 485 486 config -save 487 488 Saves the configuration file to disk, overwriting the current configuration file. 489 490 config <parameter> <value> 491 492 Changes the value of a configuration parameter. 493 494 config substitute-path <from> <to> 495 config substitute-path <from> 496 497 Adds or removes a path substitution rule. 498 499 config alias <command> <alias> 500 config alias <alias> 501 502 Defines <alias> as an alias to <command> or removes an alias.`}, 503 504 {aliases: []string{"edit", "ed"}, cmdFn: edit, helpMsg: `Open where you are in $DELVE_EDITOR or $EDITOR 505 506 edit [locspec] 507 508 If locspec is omitted edit will open the current source file in the editor, otherwise it will open the specified location.`}, 509 {aliases: []string{"libraries"}, cmdFn: libraries, helpMsg: `List loaded dynamic libraries`}, 510 511 {aliases: []string{"examinemem", "x"}, group: dataCmds, cmdFn: examineMemoryCmd, helpMsg: `Examine raw memory at the given address. 512 513 Examine memory: 514 515 examinemem [-fmt <format>] [-count|-len <count>] [-size <size>] <address> 516 examinemem [-fmt <format>] [-count|-len <count>] [-size <size>] -x <expression> 517 518 Format represents the data format and the value is one of this list (default hex): bin(binary), oct(octal), dec(decimal), hex(hexadecimal), addr(address). 519 Length is the number of bytes (default 1) and must be less than or equal to 1000. 520 Address is the memory location of the target to examine. Please note '-len' is deprecated by '-count and -size'. 521 Expression can be an integer expression or pointer value of the memory location to examine. 522 523 For example: 524 525 x -fmt hex -count 20 -size 1 0xc00008af38 526 x -fmt hex -count 20 -size 1 -x 0xc00008af38 + 8 527 x -fmt hex -count 20 -size 1 -x &myVar 528 x -fmt hex -count 20 -size 1 -x myPtrVar`}, 529 530 {aliases: []string{"display"}, group: dataCmds, cmdFn: display, helpMsg: `Print value of an expression every time the program stops. 531 532 display -a [%format] <expression> 533 display -d <number> 534 535 The '-a' option adds an expression to the list of expression printed every time the program stops. The '-d' option removes the specified expression from the list. 536 537 If display is called without arguments it will print the value of all expression in the list.`}, 538 539 {aliases: []string{"dump"}, cmdFn: dump, helpMsg: `Creates a core dump from the current process state 540 541 dump <output file> 542 543 The core dump is always written in ELF, even on systems (windows, macOS) where this is not customary. For environments other than linux/amd64 threads and registers are dumped in a format that only Delve can read back.`}, 544 545 {aliases: []string{"transcript"}, cmdFn: transcript, helpMsg: `Appends command output to a file. 546 547 transcript [-t] [-x] <output file> 548 transcript -off 549 550 Output of Delve's command is appended to the specified output file. If '-t' is specified and the output file exists it is truncated. If '-x' is specified output to stdout is suppressed instead. 551 552 Using the -off option disables the transcript.`}, 553 } 554 555 addrecorded := client == nil 556 if !addrecorded { 557 if state, err := client.GetStateNonBlocking(); err == nil { 558 addrecorded = state.Recording 559 if !addrecorded { 560 addrecorded = client.Recorded() 561 } 562 } 563 } 564 565 if addrecorded { 566 c.cmds = append(c.cmds, 567 command{ 568 aliases: []string{"rewind", "rw"}, 569 group: runCmds, 570 cmdFn: c.rewind, 571 helpMsg: "Run backwards until breakpoint or start of recorded history.", 572 }, 573 command{ 574 aliases: []string{"check", "checkpoint"}, 575 cmdFn: checkpoint, 576 helpMsg: `Creates a checkpoint at the current position. 577 578 checkpoint [note] 579 580 The "note" is arbitrary text that can be used to identify the checkpoint, if it is not specified it defaults to the current filename:line position.`, 581 }, 582 command{ 583 aliases: []string{"checkpoints"}, 584 cmdFn: checkpoints, 585 helpMsg: "Print out info for existing checkpoints.", 586 }, 587 command{ 588 aliases: []string{"clear-checkpoint", "clearcheck"}, 589 cmdFn: clearCheckpoint, 590 helpMsg: `Deletes checkpoint. 591 592 clear-checkpoint <id>`, 593 }, 594 command{ 595 aliases: []string{"rev"}, 596 group: runCmds, 597 cmdFn: c.revCmd, 598 helpMsg: `Reverses the execution of the target program for the command specified. 599 Currently, only the rev step-instruction command is supported.`, 600 }) 601 } 602 603 sort.Sort(byFirstAlias(c.cmds)) 604 return c 605 } 606 607 // Register custom commands. Expects cf to be a func of type cmdfunc, 608 // returning only an error. 609 func (c *Commands) Register(cmdstr string, cf cmdfunc, helpMsg string) { 610 for _, v := range c.cmds { 611 if v.match(cmdstr) { 612 v.cmdFn = cf 613 return 614 } 615 } 616 617 c.cmds = append(c.cmds, command{aliases: []string{cmdstr}, cmdFn: cf, helpMsg: helpMsg}) 618 } 619 620 // Find will look up the command function for the given command input. 621 // If it cannot find the command it will default to noCmdAvailable(). 622 // If the command is an empty string it will replay the last command. 623 func (c *Commands) Find(cmdstr string, prefix cmdPrefix) command { 624 // If <enter> use last command, if there was one. 625 if cmdstr == "" { 626 return command{aliases: []string{"nullcmd"}, cmdFn: nullCommand} 627 } 628 629 for _, v := range c.cmds { 630 if v.match(cmdstr) { 631 if prefix != noPrefix && v.allowedPrefixes&prefix == 0 { 632 continue 633 } 634 return v 635 } 636 } 637 638 return command{aliases: []string{"nocmd"}, cmdFn: noCmdAvailable} 639 } 640 641 // CallWithContext takes a command and a context that command should be executed in. 642 func (c *Commands) CallWithContext(cmdstr string, t *Term, ctx callContext) error { 643 vals := strings.SplitN(strings.TrimSpace(cmdstr), " ", 2) 644 cmdname := vals[0] 645 var args string 646 if len(vals) > 1 { 647 args = strings.TrimSpace(vals[1]) 648 } 649 return c.Find(cmdname, ctx.Prefix).cmdFn(t, ctx, args) 650 } 651 652 // Call takes a command to execute. 653 func (c *Commands) Call(cmdstr string, t *Term) error { 654 ctx := callContext{Prefix: noPrefix, Scope: api.EvalScope{GoroutineID: -1, Frame: c.frame, DeferredCall: 0}} 655 return c.CallWithContext(cmdstr, t, ctx) 656 } 657 658 // Merge takes aliases defined in the config struct and merges them with the default aliases. 659 func (c *Commands) Merge(allAliases map[string][]string) { 660 for i := range c.cmds { 661 if c.cmds[i].builtinAliases != nil { 662 c.cmds[i].aliases = append(c.cmds[i].aliases[:0], c.cmds[i].builtinAliases...) 663 } 664 } 665 for i := range c.cmds { 666 if aliases, ok := allAliases[c.cmds[i].aliases[0]]; ok { 667 if c.cmds[i].builtinAliases == nil { 668 c.cmds[i].builtinAliases = make([]string, len(c.cmds[i].aliases)) 669 copy(c.cmds[i].builtinAliases, c.cmds[i].aliases) 670 } 671 c.cmds[i].aliases = append(c.cmds[i].aliases, aliases...) 672 } 673 } 674 } 675 676 var errNoCmd = errors.New("command not available") 677 678 func noCmdAvailable(t *Term, ctx callContext, args string) error { 679 return errNoCmd 680 } 681 682 func nullCommand(t *Term, ctx callContext, args string) error { 683 return nil 684 } 685 686 func (c *Commands) help(t *Term, ctx callContext, args string) error { 687 if args != "" { 688 for _, cmd := range c.cmds { 689 for _, alias := range cmd.aliases { 690 if alias == args { 691 fmt.Fprintln(t.stdout, cmd.helpMsg) 692 return nil 693 } 694 } 695 } 696 return errNoCmd 697 } 698 699 fmt.Fprintln(t.stdout, "The following commands are available:") 700 701 for _, cgd := range commandGroupDescriptions { 702 fmt.Fprintf(t.stdout, "\n%s:\n", cgd.description) 703 w := new(tabwriter.Writer) 704 w.Init(t.stdout, 0, 8, 0, '-', 0) 705 for _, cmd := range c.cmds { 706 if cmd.group != cgd.group { 707 continue 708 } 709 h := cmd.helpMsg 710 if idx := strings.Index(h, "\n"); idx >= 0 { 711 h = h[:idx] 712 } 713 if len(cmd.aliases) > 1 { 714 fmt.Fprintf(w, " %s (alias: %s) \t %s\n", cmd.aliases[0], strings.Join(cmd.aliases[1:], " | "), h) 715 } else { 716 fmt.Fprintf(w, " %s \t %s\n", cmd.aliases[0], h) 717 } 718 } 719 if err := w.Flush(); err != nil { 720 return err 721 } 722 } 723 724 fmt.Fprintln(t.stdout) 725 fmt.Fprintln(t.stdout, "Type help followed by a command for full documentation.") 726 return nil 727 } 728 729 type byThreadID []*api.Thread 730 731 func (a byThreadID) Len() int { return len(a) } 732 func (a byThreadID) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 733 func (a byThreadID) Less(i, j int) bool { return a[i].ID < a[j].ID } 734 735 func threads(t *Term, ctx callContext, args string) error { 736 threads, err := t.client.ListThreads() 737 if err != nil { 738 return err 739 } 740 state, err := t.client.GetState() 741 if err != nil { 742 return err 743 } 744 sort.Sort(byThreadID(threads)) 745 for _, th := range threads { 746 prefix := " " 747 if state.CurrentThread != nil && state.CurrentThread.ID == th.ID { 748 prefix = "* " 749 } 750 if th.Function != nil { 751 fmt.Fprintf(t.stdout, "%sThread %d at %#v %s:%d %s\n", 752 prefix, th.ID, th.PC, t.formatPath(th.File), 753 th.Line, th.Function.Name()) 754 } else { 755 fmt.Fprintf(t.stdout, "%sThread %s\n", prefix, t.formatThread(th)) 756 } 757 } 758 return nil 759 } 760 761 func thread(t *Term, ctx callContext, args string) error { 762 if len(args) == 0 { 763 return fmt.Errorf("you must specify a thread") 764 } 765 tid, err := strconv.Atoi(args) 766 if err != nil { 767 return err 768 } 769 oldState, err := t.client.GetState() 770 if err != nil { 771 return err 772 } 773 newState, err := t.client.SwitchThread(tid) 774 if err != nil { 775 return err 776 } 777 778 oldThread := "<none>" 779 newThread := "<none>" 780 if oldState.CurrentThread != nil { 781 oldThread = strconv.Itoa(oldState.CurrentThread.ID) 782 } 783 if newState.CurrentThread != nil { 784 newThread = strconv.Itoa(newState.CurrentThread.ID) 785 } 786 fmt.Fprintf(t.stdout, "Switched from %s to %s\n", oldThread, newThread) 787 return nil 788 } 789 790 type byGoroutineID []*api.Goroutine 791 792 func (a byGoroutineID) Len() int { return len(a) } 793 func (a byGoroutineID) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 794 func (a byGoroutineID) Less(i, j int) bool { return a[i].ID < a[j].ID } 795 796 func printGoroutines(t *Term, indent string, gs []*api.Goroutine, fgl api.FormatGoroutineLoc, flags api.PrintGoroutinesFlags, depth int, state *api.DebuggerState) error { 797 for _, g := range gs { 798 prefix := indent + " " 799 if state.SelectedGoroutine != nil && g.ID == state.SelectedGoroutine.ID { 800 prefix = indent + "* " 801 } 802 fmt.Fprintf(t.stdout, "%sGoroutine %s\n", prefix, t.formatGoroutine(g, fgl)) 803 if flags&api.PrintGoroutinesLabels != 0 { 804 writeGoroutineLabels(t.stdout, g, indent+"\t") 805 } 806 if flags&api.PrintGoroutinesStack != 0 { 807 stack, err := t.client.Stacktrace(g.ID, depth, 0, nil) 808 if err != nil { 809 return err 810 } 811 printStack(t, t.stdout, stack, indent+"\t", false) 812 } 813 } 814 return nil 815 } 816 817 func goroutines(t *Term, ctx callContext, argstr string) error { 818 filters, group, fgl, flags, depth, batchSize, err := api.ParseGoroutineArgs(argstr) 819 if err != nil { 820 return err 821 } 822 823 state, err := t.client.GetState() 824 if err != nil { 825 return err 826 } 827 var ( 828 start = 0 829 gslen = 0 830 gs []*api.Goroutine 831 groups []api.GoroutineGroup 832 tooManyGroups bool 833 ) 834 t.longCommandStart() 835 for start >= 0 { 836 if t.longCommandCanceled() { 837 fmt.Fprintf(t.stdout, "interrupted\n") 838 return nil 839 } 840 gs, groups, start, tooManyGroups, err = t.client.ListGoroutinesWithFilter(start, batchSize, filters, &group) 841 if err != nil { 842 return err 843 } 844 if len(groups) > 0 { 845 for i := range groups { 846 fmt.Fprintf(t.stdout, "%s\n", groups[i].Name) 847 err = printGoroutines(t, "\t", gs[groups[i].Offset:][:groups[i].Count], fgl, flags, depth, state) 848 if err != nil { 849 return err 850 } 851 fmt.Fprintf(t.stdout, "\tTotal: %d\n", groups[i].Total) 852 if i != len(groups)-1 { 853 fmt.Fprintf(t.stdout, "\n") 854 } 855 } 856 if tooManyGroups { 857 fmt.Fprintf(t.stdout, "Too many groups\n") 858 } 859 } else { 860 sort.Sort(byGoroutineID(gs)) 861 err = printGoroutines(t, "", gs, fgl, flags, depth, state) 862 if err != nil { 863 return err 864 } 865 gslen += len(gs) 866 } 867 } 868 if gslen > 0 { 869 fmt.Fprintf(t.stdout, "[%d goroutines]\n", gslen) 870 } 871 return nil 872 } 873 874 func selectedGID(state *api.DebuggerState) int { 875 if state.SelectedGoroutine == nil { 876 return 0 877 } 878 return state.SelectedGoroutine.ID 879 } 880 881 func (c *Commands) goroutine(t *Term, ctx callContext, argstr string) error { 882 args := config.Split2PartsBySpace(argstr) 883 884 if ctx.Prefix == onPrefix { 885 if len(args) != 1 || args[0] != "" { 886 return errors.New("too many arguments to goroutine") 887 } 888 ctx.Breakpoint.Goroutine = true 889 return nil 890 } 891 892 if len(args) == 1 { 893 if args[0] == "" { 894 return printscope(t) 895 } 896 gid, err := strconv.Atoi(argstr) 897 if err != nil { 898 return err 899 } 900 901 oldState, err := t.client.GetState() 902 if err != nil { 903 return err 904 } 905 newState, err := t.client.SwitchGoroutine(gid) 906 if err != nil { 907 return err 908 } 909 c.frame = 0 910 fmt.Fprintf(t.stdout, "Switched from %d to %d (thread %d)\n", selectedGID(oldState), gid, newState.CurrentThread.ID) 911 return nil 912 } 913 914 var err error 915 ctx.Scope.GoroutineID, err = strconv.Atoi(args[0]) 916 if err != nil { 917 return err 918 } 919 return c.CallWithContext(args[1], t, ctx) 920 } 921 922 // Handle "frame", "up", "down" commands. 923 func (c *Commands) frameCommand(t *Term, ctx callContext, argstr string, direction frameDirection) error { 924 frame := 1 925 arg := "" 926 if len(argstr) == 0 { 927 if direction == frameSet { 928 return errors.New("not enough arguments") 929 } 930 } else { 931 args := config.Split2PartsBySpace(argstr) 932 var err error 933 if frame, err = strconv.Atoi(args[0]); err != nil { 934 return err 935 } 936 if len(args) > 1 { 937 arg = args[1] 938 } 939 } 940 switch direction { 941 case frameUp: 942 frame = c.frame + frame 943 case frameDown: 944 frame = c.frame - frame 945 } 946 if len(arg) > 0 { 947 ctx.Scope.Frame = frame 948 return c.CallWithContext(arg, t, ctx) 949 } 950 if frame < 0 { 951 return fmt.Errorf("Invalid frame %d", frame) 952 } 953 stack, err := t.client.Stacktrace(ctx.Scope.GoroutineID, frame, 0, nil) 954 if err != nil { 955 return err 956 } 957 if frame >= len(stack) { 958 return fmt.Errorf("Invalid frame %d", frame) 959 } 960 c.frame = frame 961 state, err := t.client.GetState() 962 if err != nil { 963 return err 964 } 965 printcontext(t, state) 966 th := stack[frame] 967 fmt.Fprintf(t.stdout, "Frame %d: %s:%d (PC: %x)\n", frame, t.formatPath(th.File), th.Line, th.PC) 968 printfile(t, th.File, th.Line, true) 969 return nil 970 } 971 972 func (c *Commands) deferredCommand(t *Term, ctx callContext, argstr string) error { 973 ctx.Prefix = deferredPrefix 974 975 space := strings.IndexRune(argstr, ' ') 976 if space < 0 { 977 return errors.New("not enough arguments") 978 } 979 980 var err error 981 ctx.Scope.DeferredCall, err = strconv.Atoi(argstr[:space]) 982 if err != nil { 983 return err 984 } 985 if ctx.Scope.DeferredCall <= 0 { 986 return errors.New("argument of deferred must be a number greater than 0 (use 'stack -defer' to see the list of deferred calls)") 987 } 988 return c.CallWithContext(argstr[space:], t, ctx) 989 } 990 991 func printscope(t *Term) error { 992 state, err := t.client.GetState() 993 if err != nil { 994 return err 995 } 996 997 fmt.Fprintf(t.stdout, "Thread %s\n", t.formatThread(state.CurrentThread)) 998 if state.SelectedGoroutine != nil { 999 writeGoroutineLong(t, t.stdout, state.SelectedGoroutine, "") 1000 } 1001 return nil 1002 } 1003 1004 func (t *Term) formatThread(th *api.Thread) string { 1005 if th == nil { 1006 return "<nil>" 1007 } 1008 return fmt.Sprintf("%d at %s:%d", th.ID, t.formatPath(th.File), th.Line) 1009 } 1010 1011 func (t *Term) formatLocation(loc api.Location) string { 1012 return fmt.Sprintf("%s:%d %s (%#v)", t.formatPath(loc.File), loc.Line, loc.Function.Name(), loc.PC) 1013 } 1014 1015 func (t *Term) formatGoroutine(g *api.Goroutine, fgl api.FormatGoroutineLoc) string { 1016 if g == nil { 1017 return "<nil>" 1018 } 1019 if g.Unreadable != "" { 1020 return fmt.Sprintf("(unreadable %s)", g.Unreadable) 1021 } 1022 var locname string 1023 var loc api.Location 1024 switch fgl { 1025 case api.FglRuntimeCurrent: 1026 locname = "Runtime" 1027 loc = g.CurrentLoc 1028 case api.FglUserCurrent: 1029 locname = "User" 1030 loc = g.UserCurrentLoc 1031 case api.FglGo: 1032 locname = "Go" 1033 loc = g.GoStatementLoc 1034 case api.FglStart: 1035 locname = "Start" 1036 loc = g.StartLoc 1037 } 1038 1039 buf := new(strings.Builder) 1040 fmt.Fprintf(buf, "%d - %s: %s", g.ID, locname, t.formatLocation(loc)) 1041 if g.ThreadID != 0 { 1042 fmt.Fprintf(buf, " (thread %d)", g.ThreadID) 1043 } 1044 1045 if (g.Status == api.GoroutineWaiting || g.Status == api.GoroutineSyscall) && g.WaitReason != 0 { 1046 var wr string 1047 if g.WaitReason > 0 && g.WaitReason < int64(len(waitReasonStrings)) { 1048 wr = waitReasonStrings[g.WaitReason] 1049 } else { 1050 wr = fmt.Sprintf("unknown wait reason %d", g.WaitReason) 1051 } 1052 fmt.Fprintf(buf, " [%s", wr) 1053 if g.WaitSince > 0 { 1054 fmt.Fprintf(buf, " %s", time.Since(time.Unix(0, g.WaitSince)).String()) 1055 } 1056 fmt.Fprintf(buf, "]") 1057 } 1058 1059 return buf.String() 1060 } 1061 1062 var waitReasonStrings = [...]string{ 1063 "", 1064 "GC assist marking", 1065 "IO wait", 1066 "chan receive (nil chan)", 1067 "chan send (nil chan)", 1068 "dumping heap", 1069 "garbage collection", 1070 "garbage collection scan", 1071 "panicwait", 1072 "select", 1073 "select (no cases)", 1074 "GC assist wait", 1075 "GC sweep wait", 1076 "GC scavenge wait", 1077 "chan receive", 1078 "chan send", 1079 "finalizer wait", 1080 "force gc (idle)", 1081 "semacquire", 1082 "sleep", 1083 "sync.Cond.Wait", 1084 "timer goroutine (idle)", 1085 "trace reader (blocked)", 1086 "wait for GC cycle", 1087 "GC worker (idle)", 1088 "preempted", 1089 "debug call", 1090 } 1091 1092 func writeGoroutineLong(t *Term, w io.Writer, g *api.Goroutine, prefix string) { 1093 fmt.Fprintf(w, "%sGoroutine %d:\n%s\tRuntime: %s\n%s\tUser: %s\n%s\tGo: %s\n%s\tStart: %s\n", 1094 prefix, g.ID, 1095 prefix, t.formatLocation(g.CurrentLoc), 1096 prefix, t.formatLocation(g.UserCurrentLoc), 1097 prefix, t.formatLocation(g.GoStatementLoc), 1098 prefix, t.formatLocation(g.StartLoc)) 1099 writeGoroutineLabels(w, g, prefix+"\t") 1100 } 1101 1102 func writeGoroutineLabels(w io.Writer, g *api.Goroutine, prefix string) { 1103 const maxNumberOfGoroutineLabels = 5 1104 1105 if len(g.Labels) <= 0 { 1106 return 1107 } 1108 1109 keys := make([]string, 0, len(g.Labels)) 1110 for k := range g.Labels { 1111 keys = append(keys, k) 1112 } 1113 sort.Strings(keys) 1114 more := false 1115 if len(keys) > maxNumberOfGoroutineLabels { 1116 more = true 1117 keys = keys[:maxNumberOfGoroutineLabels] 1118 } 1119 fmt.Fprintf(w, "%sLabels: ", prefix) 1120 for i, k := range keys { 1121 fmt.Fprintf(w, "%q:%q", k, g.Labels[k]) 1122 if i != len(keys)-1 { 1123 fmt.Fprintf(w, ", ") 1124 } else if more { 1125 fmt.Fprintf(w, "... (%d more)", len(g.Labels)-maxNumberOfGoroutineLabels) 1126 } 1127 } 1128 fmt.Fprintf(w, "\n") 1129 } 1130 1131 func restart(t *Term, ctx callContext, args string) error { 1132 if t.client.Recorded() { 1133 return restartRecorded(t, ctx, args) 1134 } 1135 1136 return restartLive(t, ctx, args) 1137 } 1138 1139 func restartRecorded(t *Term, ctx callContext, args string) error { 1140 v := config.Split2PartsBySpace(args) 1141 1142 rerecord := false 1143 resetArgs := false 1144 newArgv := []string{} 1145 newRedirects := [3]string{} 1146 restartPos := "" 1147 1148 if len(v) > 0 { 1149 if v[0] == "-r" { 1150 rerecord = true 1151 if len(v) == 2 { 1152 var err error 1153 resetArgs, newArgv, newRedirects, err = parseNewArgv(v[1]) 1154 if err != nil { 1155 return err 1156 } 1157 } 1158 } else { 1159 if len(v) > 1 { 1160 return fmt.Errorf("too many arguments to restart") 1161 } 1162 restartPos = v[0] 1163 } 1164 } 1165 1166 if err := restartIntl(t, rerecord, restartPos, resetArgs, newArgv, newRedirects); err != nil { 1167 return err 1168 } 1169 1170 state, err := t.client.GetState() 1171 if err != nil { 1172 return err 1173 } 1174 printcontext(t, state) 1175 printfile(t, state.CurrentThread.File, state.CurrentThread.Line, true) 1176 t.onStop() 1177 return nil 1178 } 1179 1180 // parseOptionalCount parses an optional count argument. 1181 // If there are not arguments, a value of 1 is returned as the default. 1182 func parseOptionalCount(arg string) (int64, error) { 1183 if len(arg) == 0 { 1184 return 1, nil 1185 } 1186 return strconv.ParseInt(arg, 0, 64) 1187 } 1188 1189 func restartLive(t *Term, ctx callContext, args string) error { 1190 resetArgs, newArgv, newRedirects, err := parseNewArgv(args) 1191 if err != nil { 1192 return err 1193 } 1194 1195 if err := restartIntl(t, false, "", resetArgs, newArgv, newRedirects); err != nil { 1196 return err 1197 } 1198 1199 fmt.Fprintln(t.stdout, "Process restarted with PID", t.client.ProcessPid()) 1200 return nil 1201 } 1202 1203 func restartIntl(t *Term, rerecord bool, restartPos string, resetArgs bool, newArgv []string, newRedirects [3]string) error { 1204 discarded, err := t.client.RestartFrom(rerecord, restartPos, resetArgs, newArgv, newRedirects, false) 1205 if err != nil { 1206 return err 1207 } 1208 for i := range discarded { 1209 fmt.Fprintf(t.stdout, "Discarded %s at %s: %v\n", formatBreakpointName(discarded[i].Breakpoint, false), t.formatBreakpointLocation(discarded[i].Breakpoint), discarded[i].Reason) 1210 } 1211 return nil 1212 } 1213 1214 func parseNewArgv(args string) (resetArgs bool, newArgv []string, newRedirects [3]string, err error) { 1215 if args == "" { 1216 return false, nil, [3]string{}, nil 1217 } 1218 v, err := argv.Argv(args, 1219 func(s string) (string, error) { 1220 return "", fmt.Errorf("Backtick not supported in '%s'", s) 1221 }, 1222 nil) 1223 if err != nil { 1224 return false, nil, [3]string{}, err 1225 } 1226 if len(v) != 1 { 1227 return false, nil, [3]string{}, fmt.Errorf("illegal commandline '%s'", args) 1228 } 1229 w := v[0] 1230 if len(w) == 0 { 1231 return false, nil, [3]string{}, nil 1232 } 1233 if w[0] == "-noargs" { 1234 if len(w) > 1 { 1235 return false, nil, [3]string{}, fmt.Errorf("too many arguments to restart") 1236 } 1237 return true, nil, [3]string{}, nil 1238 } 1239 redirs := [3]string{} 1240 for len(w) > 0 { 1241 var found bool 1242 var err error 1243 w, found, err = parseOneRedirect(w, &redirs) 1244 if err != nil { 1245 return false, nil, [3]string{}, err 1246 } 1247 if !found { 1248 break 1249 } 1250 } 1251 return true, w, redirs, nil 1252 } 1253 1254 func parseOneRedirect(w []string, redirs *[3]string) ([]string, bool, error) { 1255 prefixes := []string{"<", ">", "2>"} 1256 names := []string{"stdin", "stdout", "stderr"} 1257 if len(w) >= 2 { 1258 for _, prefix := range prefixes { 1259 if w[len(w)-2] == prefix { 1260 w[len(w)-2] += w[len(w)-1] 1261 w = w[:len(w)-1] 1262 break 1263 } 1264 } 1265 } 1266 for i, prefix := range prefixes { 1267 if strings.HasPrefix(w[len(w)-1], prefix) { 1268 if redirs[i] != "" { 1269 return nil, false, fmt.Errorf("redirect error: %s redirected twice", names[i]) 1270 } 1271 redirs[i] = w[len(w)-1][len(prefix):] 1272 return w[:len(w)-1], true, nil 1273 } 1274 } 1275 return w, false, nil 1276 } 1277 1278 func printcontextNoState(t *Term) { 1279 state, _ := t.client.GetState() 1280 if state == nil || state.CurrentThread == nil { 1281 return 1282 } 1283 printcontext(t, state) 1284 } 1285 1286 func (c *Commands) rebuild(t *Term, ctx callContext, args string) error { 1287 if ctx.Prefix == revPrefix { 1288 return c.rewind(t, ctx, args) 1289 } 1290 defer t.onStop() 1291 discarded, err := t.client.Restart(true) 1292 if len(discarded) > 0 { 1293 fmt.Fprintf(t.stdout, "not all breakpoints could be restored.") 1294 } 1295 return err 1296 } 1297 1298 func (c *Commands) cont(t *Term, ctx callContext, args string) error { 1299 if args != "" { 1300 tmp, err := setBreakpoint(t, ctx, false, args) 1301 if err != nil { 1302 if !strings.Contains(err.Error(), "Breakpoint exists") { 1303 return err 1304 } 1305 } 1306 defer func() { 1307 for _, bp := range tmp { 1308 if _, err := t.client.ClearBreakpoint(bp.ID); err != nil { 1309 fmt.Fprintf(t.stdout, "failed to clear temporary breakpoint: %d", bp.ID) 1310 } 1311 } 1312 }() 1313 } 1314 if ctx.Prefix == revPrefix { 1315 return c.rewind(t, ctx, args) 1316 } 1317 defer t.onStop() 1318 c.frame = 0 1319 stateChan := t.client.Continue() 1320 var state *api.DebuggerState 1321 for state = range stateChan { 1322 if state.Err != nil { 1323 printcontextNoState(t) 1324 return state.Err 1325 } 1326 printcontext(t, state) 1327 } 1328 printfile(t, state.CurrentThread.File, state.CurrentThread.Line, true) 1329 return nil 1330 } 1331 1332 func continueUntilCompleteNext(t *Term, state *api.DebuggerState, op string, shouldPrintFile bool) error { 1333 defer t.onStop() 1334 if !state.NextInProgress { 1335 if shouldPrintFile { 1336 printfile(t, state.CurrentThread.File, state.CurrentThread.Line, true) 1337 } 1338 return nil 1339 } 1340 skipBreakpoints := false 1341 for { 1342 fmt.Fprintf(t.stdout, "\tbreakpoint hit during %s", op) 1343 if !skipBreakpoints { 1344 fmt.Fprintf(t.stdout, "\n") 1345 answer, err := promptAutoContinue(t, op) 1346 switch answer { 1347 case "f": // finish next 1348 skipBreakpoints = true 1349 fallthrough 1350 case "c": // continue once 1351 fmt.Fprintf(t.stdout, "continuing...\n") 1352 case "s": // stop and cancel 1353 fallthrough 1354 default: 1355 t.client.CancelNext() 1356 printfile(t, state.CurrentThread.File, state.CurrentThread.Line, true) 1357 return err 1358 } 1359 } else { 1360 fmt.Fprintf(t.stdout, ", continuing...\n") 1361 } 1362 stateChan := t.client.DirectionCongruentContinue() 1363 var state *api.DebuggerState 1364 for state = range stateChan { 1365 if state.Err != nil { 1366 printcontextNoState(t) 1367 return state.Err 1368 } 1369 printcontext(t, state) 1370 } 1371 if !state.NextInProgress { 1372 printfile(t, state.CurrentThread.File, state.CurrentThread.Line, true) 1373 return nil 1374 } 1375 } 1376 } 1377 1378 func promptAutoContinue(t *Term, op string) (string, error) { 1379 for { 1380 answer, err := t.line.Prompt(fmt.Sprintf("[c] continue [s] stop here and cancel %s, [f] finish %s skipping all breakpoints? ", op, op)) 1381 if err != nil { 1382 return "", err 1383 } 1384 answer = strings.ToLower(strings.TrimSpace(answer)) 1385 switch answer { 1386 case "f", "c", "s": 1387 return answer, nil 1388 } 1389 } 1390 } 1391 1392 func scopePrefixSwitch(t *Term, ctx callContext) error { 1393 if ctx.Scope.GoroutineID > 0 { 1394 _, err := t.client.SwitchGoroutine(ctx.Scope.GoroutineID) 1395 if err != nil { 1396 return err 1397 } 1398 } 1399 return nil 1400 } 1401 1402 func exitedToError(state *api.DebuggerState, err error) (*api.DebuggerState, error) { 1403 if err == nil && state.Exited { 1404 return nil, fmt.Errorf("Process %d has exited with status %d", state.Pid, state.ExitStatus) 1405 } 1406 return state, err 1407 } 1408 1409 func (c *Commands) step(t *Term, ctx callContext, args string) error { 1410 if err := scopePrefixSwitch(t, ctx); err != nil { 1411 return err 1412 } 1413 c.frame = 0 1414 stepfn := t.client.Step 1415 if ctx.Prefix == revPrefix { 1416 stepfn = t.client.ReverseStep 1417 } 1418 state, err := exitedToError(stepfn()) 1419 if err != nil { 1420 printcontextNoState(t) 1421 return err 1422 } 1423 printcontext(t, state) 1424 return continueUntilCompleteNext(t, state, "step", true) 1425 } 1426 1427 var errNotOnFrameZero = errors.New("not on topmost frame") 1428 1429 func (c *Commands) stepInstruction(t *Term, ctx callContext, args string) error { 1430 if err := scopePrefixSwitch(t, ctx); err != nil { 1431 return err 1432 } 1433 if c.frame != 0 { 1434 return errNotOnFrameZero 1435 } 1436 1437 defer t.onStop() 1438 1439 var fn func() (*api.DebuggerState, error) 1440 if ctx.Prefix == revPrefix { 1441 fn = t.client.ReverseStepInstruction 1442 } else { 1443 fn = t.client.StepInstruction 1444 } 1445 1446 state, err := exitedToError(fn()) 1447 if err != nil { 1448 printcontextNoState(t) 1449 return err 1450 } 1451 printcontext(t, state) 1452 printfile(t, state.CurrentThread.File, state.CurrentThread.Line, true) 1453 return nil 1454 } 1455 1456 func (c *Commands) revCmd(t *Term, ctx callContext, args string) error { 1457 if len(args) == 0 { 1458 return errors.New("not enough arguments") 1459 } 1460 1461 ctx.Prefix = revPrefix 1462 return c.CallWithContext(args, t, ctx) 1463 } 1464 1465 func (c *Commands) next(t *Term, ctx callContext, args string) error { 1466 if err := scopePrefixSwitch(t, ctx); err != nil { 1467 return err 1468 } 1469 if c.frame != 0 { 1470 return errNotOnFrameZero 1471 } 1472 1473 nextfn := t.client.Next 1474 if ctx.Prefix == revPrefix { 1475 nextfn = t.client.ReverseNext 1476 } 1477 1478 var count int64 1479 var err error 1480 if count, err = parseOptionalCount(args); err != nil { 1481 return err 1482 } else if count <= 0 { 1483 return errors.New("Invalid next count") 1484 } 1485 for ; count > 0; count-- { 1486 state, err := exitedToError(nextfn()) 1487 if err != nil { 1488 printcontextNoState(t) 1489 return err 1490 } 1491 // If we're about the exit the loop, print the context. 1492 finishedNext := count == 1 1493 if finishedNext { 1494 printcontext(t, state) 1495 } 1496 if err := continueUntilCompleteNext(t, state, "next", finishedNext); err != nil { 1497 return err 1498 } 1499 } 1500 return nil 1501 } 1502 1503 func (c *Commands) stepout(t *Term, ctx callContext, args string) error { 1504 if err := scopePrefixSwitch(t, ctx); err != nil { 1505 return err 1506 } 1507 if c.frame != 0 { 1508 return errNotOnFrameZero 1509 } 1510 1511 stepoutfn := t.client.StepOut 1512 if ctx.Prefix == revPrefix { 1513 stepoutfn = t.client.ReverseStepOut 1514 } 1515 1516 state, err := exitedToError(stepoutfn()) 1517 if err != nil { 1518 printcontextNoState(t) 1519 return err 1520 } 1521 printcontext(t, state) 1522 return continueUntilCompleteNext(t, state, "stepout", true) 1523 } 1524 1525 func (c *Commands) call(t *Term, ctx callContext, args string) error { 1526 if err := scopePrefixSwitch(t, ctx); err != nil { 1527 return err 1528 } 1529 const unsafePrefix = "-unsafe " 1530 unsafe := false 1531 if strings.HasPrefix(args, unsafePrefix) { 1532 unsafe = true 1533 args = args[len(unsafePrefix):] 1534 } 1535 state, err := exitedToError(t.client.Call(ctx.Scope.GoroutineID, args, unsafe)) 1536 c.frame = 0 1537 if err != nil { 1538 printcontextNoState(t) 1539 return err 1540 } 1541 printcontext(t, state) 1542 return continueUntilCompleteNext(t, state, "call", true) 1543 } 1544 1545 func clear(t *Term, ctx callContext, args string) error { 1546 if len(args) == 0 { 1547 return fmt.Errorf("not enough arguments") 1548 } 1549 id, err := strconv.Atoi(args) 1550 var bp *api.Breakpoint 1551 if err == nil { 1552 bp, err = t.client.ClearBreakpoint(id) 1553 } else { 1554 bp, err = t.client.ClearBreakpointByName(args) 1555 } 1556 if err != nil { 1557 return err 1558 } 1559 fmt.Fprintf(t.stdout, "%s cleared at %s\n", formatBreakpointName(bp, true), t.formatBreakpointLocation(bp)) 1560 return nil 1561 } 1562 1563 func clearAll(t *Term, ctx callContext, args string) error { 1564 breakPoints, err := t.client.ListBreakpoints(false) 1565 if err != nil { 1566 return err 1567 } 1568 1569 var locPCs map[uint64]struct{} 1570 if args != "" { 1571 locs, err := t.client.FindLocation(api.EvalScope{GoroutineID: -1, Frame: 0}, args, true, t.substitutePathRules()) 1572 if err != nil { 1573 return err 1574 } 1575 locPCs = make(map[uint64]struct{}) 1576 for _, loc := range locs { 1577 for _, pc := range loc.PCs { 1578 locPCs[pc] = struct{}{} 1579 } 1580 locPCs[loc.PC] = struct{}{} 1581 } 1582 } 1583 1584 for _, bp := range breakPoints { 1585 if locPCs != nil { 1586 if _, ok := locPCs[bp.Addr]; !ok { 1587 continue 1588 } 1589 } 1590 1591 if bp.ID < 0 { 1592 continue 1593 } 1594 1595 _, err := t.client.ClearBreakpoint(bp.ID) 1596 if err != nil { 1597 fmt.Fprintf(t.stdout, "Couldn't delete %s at %s: %s\n", formatBreakpointName(bp, false), t.formatBreakpointLocation(bp), err) 1598 } 1599 fmt.Fprintf(t.stdout, "%s cleared at %s\n", formatBreakpointName(bp, true), t.formatBreakpointLocation(bp)) 1600 } 1601 return nil 1602 } 1603 1604 func toggle(t *Term, ctx callContext, args string) error { 1605 if args == "" { 1606 return fmt.Errorf("not enough arguments") 1607 } 1608 id, err := strconv.Atoi(args) 1609 var bp *api.Breakpoint 1610 if err == nil { 1611 bp, err = t.client.ToggleBreakpoint(id) 1612 } else { 1613 bp, err = t.client.ToggleBreakpointByName(args) 1614 } 1615 if err != nil { 1616 return err 1617 } 1618 fmt.Fprintf(t.stdout, "%s toggled at %s\n", formatBreakpointName(bp, true), t.formatBreakpointLocation(bp)) 1619 return nil 1620 } 1621 1622 // byID sorts breakpoints by ID. 1623 type byID []*api.Breakpoint 1624 1625 func (a byID) Len() int { return len(a) } 1626 func (a byID) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 1627 func (a byID) Less(i, j int) bool { return a[i].ID < a[j].ID } 1628 1629 func breakpoints(t *Term, ctx callContext, args string) error { 1630 breakPoints, err := t.client.ListBreakpoints(args == "-a") 1631 if err != nil { 1632 return err 1633 } 1634 sort.Sort(byID(breakPoints)) 1635 for _, bp := range breakPoints { 1636 enabled := "(enabled)" 1637 if bp.Disabled { 1638 enabled = "(disabled)" 1639 } 1640 fmt.Fprintf(t.stdout, "%s %s at %v (%d)\n", formatBreakpointName(bp, true), enabled, t.formatBreakpointLocation(bp), bp.TotalHitCount) 1641 1642 attrs := formatBreakpointAttrs("\t", bp, false) 1643 1644 if len(attrs) > 0 { 1645 fmt.Fprintf(t.stdout, "%s\n", strings.Join(attrs, "\n")) 1646 } 1647 } 1648 return nil 1649 } 1650 1651 func formatBreakpointAttrs(prefix string, bp *api.Breakpoint, includeTrace bool) []string { 1652 var attrs []string 1653 if bp.Cond != "" { 1654 attrs = append(attrs, fmt.Sprintf("%scond %s", prefix, bp.Cond)) 1655 } 1656 if bp.HitCond != "" { 1657 attrs = append(attrs, fmt.Sprintf("%scond -hitcount %s", prefix, bp.HitCond)) 1658 } 1659 if bp.Stacktrace > 0 { 1660 attrs = append(attrs, fmt.Sprintf("%sstack %d", prefix, bp.Stacktrace)) 1661 } 1662 if bp.Goroutine { 1663 attrs = append(attrs, fmt.Sprintf("%sgoroutine", prefix)) 1664 } 1665 if bp.LoadArgs != nil { 1666 if *(bp.LoadArgs) == longLoadConfig { 1667 attrs = append(attrs, fmt.Sprintf("%sargs -v", prefix)) 1668 } else { 1669 attrs = append(attrs, fmt.Sprintf("%sargs", prefix)) 1670 } 1671 } 1672 if bp.LoadLocals != nil { 1673 if *(bp.LoadLocals) == longLoadConfig { 1674 attrs = append(attrs, fmt.Sprintf("%slocals -v", prefix)) 1675 } else { 1676 attrs = append(attrs, fmt.Sprintf("%slocals", prefix)) 1677 } 1678 } 1679 for i := range bp.Variables { 1680 attrs = append(attrs, fmt.Sprintf("%sprint %s", prefix, bp.Variables[i])) 1681 } 1682 if includeTrace && bp.Tracepoint { 1683 attrs = append(attrs, fmt.Sprintf("%strace", prefix)) 1684 } 1685 for i := range bp.VerboseDescr { 1686 attrs = append(attrs, fmt.Sprintf("%s%s", prefix, bp.VerboseDescr[i])) 1687 } 1688 return attrs 1689 } 1690 1691 func setBreakpoint(t *Term, ctx callContext, tracepoint bool, argstr string) ([]*api.Breakpoint, error) { 1692 args := config.Split2PartsBySpace(argstr) 1693 1694 requestedBp := &api.Breakpoint{} 1695 spec := "" 1696 switch len(args) { 1697 case 1: 1698 if len(args[0]) != 0 { 1699 spec = argstr 1700 } else { 1701 // no arg specified 1702 spec = "+0" 1703 } 1704 case 2: 1705 if api.ValidBreakpointName(args[0]) == nil { 1706 requestedBp.Name = args[0] 1707 spec = args[1] 1708 } else { 1709 spec = argstr 1710 } 1711 default: 1712 return nil, fmt.Errorf("address required") 1713 } 1714 1715 requestedBp.Tracepoint = tracepoint 1716 locs, err := t.client.FindLocation(ctx.Scope, spec, true, t.substitutePathRules()) 1717 if err != nil { 1718 if requestedBp.Name == "" { 1719 return nil, err 1720 } 1721 requestedBp.Name = "" 1722 spec = argstr 1723 var err2 error 1724 locs, err2 = t.client.FindLocation(ctx.Scope, spec, true, t.substitutePathRules()) 1725 if err2 != nil { 1726 return nil, err 1727 } 1728 } 1729 created := []*api.Breakpoint{} 1730 for _, loc := range locs { 1731 requestedBp.Addr = loc.PC 1732 requestedBp.Addrs = loc.PCs 1733 if tracepoint { 1734 requestedBp.LoadArgs = &ShortLoadConfig 1735 } 1736 1737 bp, err := t.client.CreateBreakpoint(requestedBp) 1738 if err != nil { 1739 return nil, err 1740 } 1741 created = append(created, bp) 1742 1743 fmt.Fprintf(t.stdout, "%s set at %s\n", formatBreakpointName(bp, true), t.formatBreakpointLocation(bp)) 1744 } 1745 1746 var shouldSetReturnBreakpoints bool 1747 loc, err := locspec.Parse(spec) 1748 if err != nil { 1749 return nil, err 1750 } 1751 switch t := loc.(type) { 1752 case *locspec.NormalLocationSpec: 1753 shouldSetReturnBreakpoints = t.LineOffset == -1 && t.FuncBase != nil 1754 case *locspec.RegexLocationSpec: 1755 shouldSetReturnBreakpoints = true 1756 } 1757 if tracepoint && shouldSetReturnBreakpoints && locs[0].Function != nil { 1758 for i := range locs { 1759 if locs[i].Function == nil { 1760 continue 1761 } 1762 addrs, err := t.client.(*rpc2.RPCClient).FunctionReturnLocations(locs[0].Function.Name()) 1763 if err != nil { 1764 return nil, err 1765 } 1766 for j := range addrs { 1767 _, err = t.client.CreateBreakpoint(&api.Breakpoint{ 1768 Addr: addrs[j], 1769 TraceReturn: true, 1770 Line: -1, 1771 LoadArgs: &ShortLoadConfig, 1772 }) 1773 if err != nil { 1774 return nil, err 1775 } 1776 } 1777 } 1778 } 1779 return created, nil 1780 } 1781 1782 func breakpoint(t *Term, ctx callContext, args string) error { 1783 _, err := setBreakpoint(t, ctx, false, args) 1784 return err 1785 } 1786 1787 func tracepoint(t *Term, ctx callContext, args string) error { 1788 if ctx.Prefix == onPrefix { 1789 if args != "" { 1790 return errors.New("too many arguments to trace") 1791 } 1792 ctx.Breakpoint.Tracepoint = true 1793 return nil 1794 } 1795 _, err := setBreakpoint(t, ctx, true, args) 1796 return err 1797 } 1798 1799 func runEditor(args ...string) error { 1800 var editor string 1801 if editor = os.Getenv("DELVE_EDITOR"); editor == "" { 1802 if editor = os.Getenv("EDITOR"); editor == "" { 1803 return fmt.Errorf("Neither DELVE_EDITOR or EDITOR is set") 1804 } 1805 } 1806 1807 cmd := exec.Command(editor, args...) 1808 cmd.Stdin = os.Stdin 1809 cmd.Stdout = os.Stdout 1810 cmd.Stderr = os.Stderr 1811 return cmd.Run() 1812 } 1813 1814 func edit(t *Term, ctx callContext, args string) error { 1815 file, lineno, _, err := getLocation(t, ctx, args, false) 1816 if err != nil { 1817 return err 1818 } 1819 return runEditor(fmt.Sprintf("+%d", lineno), file) 1820 } 1821 1822 func watchpoint(t *Term, ctx callContext, args string) error { 1823 v := strings.SplitN(args, " ", 2) 1824 if len(v) != 2 { 1825 return errors.New("wrong number of arguments: watch [-r|-w|-rw] <expr>") 1826 } 1827 var wtype api.WatchType 1828 switch v[0] { 1829 case "-r": 1830 wtype = api.WatchRead 1831 case "-w": 1832 wtype = api.WatchWrite 1833 case "-rw": 1834 wtype = api.WatchRead | api.WatchWrite 1835 default: 1836 return fmt.Errorf("wrong argument %q to watch", v[0]) 1837 } 1838 bp, err := t.client.CreateWatchpoint(ctx.Scope, v[1], wtype) 1839 if err != nil { 1840 return err 1841 } 1842 fmt.Fprintf(t.stdout, "%s set at %s\n", formatBreakpointName(bp, true), t.formatBreakpointLocation(bp)) 1843 return nil 1844 } 1845 1846 func examineMemoryCmd(t *Term, ctx callContext, argstr string) error { 1847 var ( 1848 address uint64 1849 err error 1850 ok bool 1851 args = strings.Split(argstr, " ") 1852 ) 1853 1854 // Default value 1855 priFmt := byte('x') 1856 count := 1 1857 size := 1 1858 isExpr := false 1859 1860 // nextArg returns the next argument that is not an empty string, if any, and 1861 // advances the args slice to the position after that. 1862 nextArg := func() string { 1863 for len(args) > 0 { 1864 arg := args[0] 1865 args = args[1:] 1866 if arg != "" { 1867 return arg 1868 } 1869 } 1870 return "" 1871 } 1872 1873 loop: 1874 for { 1875 switch cmd := nextArg(); cmd { 1876 case "": 1877 // no more arguments 1878 break loop 1879 case "-fmt": 1880 arg := nextArg() 1881 if arg == "" { 1882 return fmt.Errorf("expected argument after -fmt") 1883 } 1884 fmtMapToPriFmt := map[string]byte{ 1885 "oct": 'o', 1886 "octal": 'o', 1887 "hex": 'x', 1888 "hexadecimal": 'x', 1889 "dec": 'd', 1890 "decimal": 'd', 1891 "bin": 'b', 1892 "binary": 'b', 1893 } 1894 priFmt, ok = fmtMapToPriFmt[arg] 1895 if !ok { 1896 return fmt.Errorf("%q is not a valid format", arg) 1897 } 1898 case "-count", "-len": 1899 arg := nextArg() 1900 if arg == "" { 1901 return fmt.Errorf("expected argument after -count/-len") 1902 } 1903 var err error 1904 count, err = strconv.Atoi(arg) 1905 if err != nil || count <= 0 { 1906 return fmt.Errorf("count/len must be a positive integer") 1907 } 1908 case "-size": 1909 arg := nextArg() 1910 if arg == "" { 1911 return fmt.Errorf("expected argument after -size") 1912 } 1913 var err error 1914 size, err = strconv.Atoi(arg) 1915 if err != nil || size <= 0 || size > 8 { 1916 return fmt.Errorf("size must be a positive integer (<=8)") 1917 } 1918 case "-x": 1919 isExpr = true 1920 break loop // remaining args are going to be interpreted as expression 1921 default: 1922 if len(args) > 0 { 1923 return fmt.Errorf("unknown option %q", args[0]) 1924 } 1925 args = []string{cmd} 1926 break loop // only one arg left to be evaluated as a uint 1927 } 1928 } 1929 1930 // TODO, maybe configured by user. 1931 if count*size > 1000 { 1932 return fmt.Errorf("read memory range (count*size) must be less than or equal to 1000 bytes") 1933 } 1934 1935 if len(args) == 0 { 1936 return fmt.Errorf("no address specified") 1937 } 1938 1939 if isExpr { 1940 expr := strings.Join(args, " ") 1941 val, err := t.client.EvalVariable(ctx.Scope, expr, t.loadConfig()) 1942 if err != nil { 1943 return err 1944 } 1945 1946 // "-x &myVar" or "-x myPtrVar" 1947 if val.Kind == reflect.Ptr { 1948 if len(val.Children) < 1 { 1949 return fmt.Errorf("bug? invalid pointer: %#v", val) 1950 } 1951 address = val.Children[0].Addr 1952 // "-x 0xc000079f20 + 8" or -x 824634220320 + 8 1953 } else if val.Kind == reflect.Int && val.Value != "" { 1954 address, err = strconv.ParseUint(val.Value, 0, 64) 1955 if err != nil { 1956 return fmt.Errorf("bad expression result: %q: %s", val.Value, err) 1957 } 1958 } else { 1959 return fmt.Errorf("unsupported expression type: %s", val.Kind) 1960 } 1961 } else { 1962 address, err = strconv.ParseUint(args[0], 0, 64) 1963 if err != nil { 1964 return fmt.Errorf("convert address into uintptr type failed, %s", err) 1965 } 1966 } 1967 1968 memArea, isLittleEndian, err := t.client.ExamineMemory(address, count*size) 1969 if err != nil { 1970 return err 1971 } 1972 fmt.Fprint(t.stdout, api.PrettyExamineMemory(uintptr(address), memArea, isLittleEndian, priFmt, size)) 1973 return nil 1974 } 1975 1976 func parseFormatArg(args string) (fmtstr, argsOut string) { 1977 if len(args) < 1 || args[0] != '%' { 1978 return "", args 1979 } 1980 v := strings.SplitN(args, " ", 2) 1981 if len(v) == 1 { 1982 return v[0], "" 1983 } 1984 return v[0], v[1] 1985 } 1986 1987 func printVar(t *Term, ctx callContext, args string) error { 1988 if len(args) == 0 { 1989 return fmt.Errorf("not enough arguments") 1990 } 1991 if ctx.Prefix == onPrefix { 1992 ctx.Breakpoint.Variables = append(ctx.Breakpoint.Variables, args) 1993 return nil 1994 } 1995 fmtstr, args := parseFormatArg(args) 1996 val, err := t.client.EvalVariable(ctx.Scope, args, t.loadConfig()) 1997 if err != nil { 1998 return err 1999 } 2000 2001 fmt.Fprintln(t.stdout, val.MultilineString("", fmtstr)) 2002 return nil 2003 } 2004 2005 func whatisCommand(t *Term, ctx callContext, args string) error { 2006 if len(args) == 0 { 2007 return fmt.Errorf("not enough arguments") 2008 } 2009 val, err := t.client.EvalVariable(ctx.Scope, args, ShortLoadConfig) 2010 if err != nil { 2011 return err 2012 } 2013 if val.Flags&api.VariableCPURegister != 0 { 2014 fmt.Fprintln(t.stdout, "CPU Register") 2015 return nil 2016 } 2017 if val.Type != "" { 2018 fmt.Fprintln(t.stdout, val.Type) 2019 } 2020 if val.RealType != val.Type { 2021 fmt.Fprintf(t.stdout, "Real type: %s\n", val.RealType) 2022 } 2023 if val.Kind == reflect.Interface && len(val.Children) > 0 { 2024 fmt.Fprintf(t.stdout, "Concrete type: %s\n", val.Children[0].Type) 2025 } 2026 if t.conf.ShowLocationExpr && val.LocationExpr != "" { 2027 fmt.Fprintf(t.stdout, "location: %s\n", val.LocationExpr) 2028 } 2029 return nil 2030 } 2031 2032 func setVar(t *Term, ctx callContext, args string) error { 2033 // HACK: in go '=' is not an operator, we detect the error and try to recover from it by splitting the input string 2034 _, err := parser.ParseExpr(args) 2035 if err == nil { 2036 return fmt.Errorf("syntax error '=' not found") 2037 } 2038 2039 el, ok := err.(scanner.ErrorList) 2040 if !ok || el[0].Msg != "expected '==', found '='" { 2041 return err 2042 } 2043 2044 lexpr := args[:el[0].Pos.Offset] 2045 rexpr := args[el[0].Pos.Offset+1:] 2046 return t.client.SetVariable(ctx.Scope, lexpr, rexpr) 2047 } 2048 2049 func (t *Term) printFilteredVariables(varType string, vars []api.Variable, filter string, cfg api.LoadConfig) error { 2050 reg, err := regexp.Compile(filter) 2051 if err != nil { 2052 return err 2053 } 2054 match := false 2055 for _, v := range vars { 2056 if reg == nil || reg.Match([]byte(v.Name)) { 2057 match = true 2058 name := v.Name 2059 if v.Flags&api.VariableShadowed != 0 { 2060 name = "(" + name + ")" 2061 } 2062 if cfg == ShortLoadConfig { 2063 fmt.Fprintf(t.stdout, "%s = %s\n", name, v.SinglelineString()) 2064 } else { 2065 fmt.Fprintf(t.stdout, "%s = %s\n", name, v.MultilineString("", "")) 2066 } 2067 } 2068 } 2069 if !match { 2070 fmt.Fprintf(t.stdout, "(no %s)\n", varType) 2071 } 2072 return nil 2073 } 2074 2075 func (t *Term) printSortedStrings(v []string, err error) error { 2076 if err != nil { 2077 return err 2078 } 2079 sort.Strings(v) 2080 for _, d := range v { 2081 fmt.Fprintln(t.stdout, d) 2082 } 2083 return nil 2084 } 2085 2086 func sources(t *Term, ctx callContext, args string) error { 2087 return t.printSortedStrings(t.client.ListSources(args)) 2088 } 2089 2090 func funcs(t *Term, ctx callContext, args string) error { 2091 return t.printSortedStrings(t.client.ListFunctions(args)) 2092 } 2093 2094 func types(t *Term, ctx callContext, args string) error { 2095 return t.printSortedStrings(t.client.ListTypes(args)) 2096 } 2097 2098 func parseVarArguments(args string, t *Term) (filter string, cfg api.LoadConfig) { 2099 if v := config.Split2PartsBySpace(args); len(v) >= 1 && v[0] == "-v" { 2100 if len(v) == 2 { 2101 return v[1], t.loadConfig() 2102 } else { 2103 return "", t.loadConfig() 2104 } 2105 } 2106 return args, ShortLoadConfig 2107 } 2108 2109 func args(t *Term, ctx callContext, args string) error { 2110 filter, cfg := parseVarArguments(args, t) 2111 if ctx.Prefix == onPrefix { 2112 if filter != "" { 2113 return fmt.Errorf("filter not supported on breakpoint") 2114 } 2115 ctx.Breakpoint.LoadArgs = &cfg 2116 return nil 2117 } 2118 vars, err := t.client.ListFunctionArgs(ctx.Scope, cfg) 2119 if err != nil { 2120 return err 2121 } 2122 return t.printFilteredVariables("args", vars, filter, cfg) 2123 } 2124 2125 func locals(t *Term, ctx callContext, args string) error { 2126 filter, cfg := parseVarArguments(args, t) 2127 if ctx.Prefix == onPrefix { 2128 if filter != "" { 2129 return fmt.Errorf("filter not supported on breakpoint") 2130 } 2131 ctx.Breakpoint.LoadLocals = &cfg 2132 return nil 2133 } 2134 locals, err := t.client.ListLocalVariables(ctx.Scope, cfg) 2135 if err != nil { 2136 return err 2137 } 2138 return t.printFilteredVariables("locals", locals, filter, cfg) 2139 } 2140 2141 func vars(t *Term, ctx callContext, args string) error { 2142 filter, cfg := parseVarArguments(args, t) 2143 vars, err := t.client.ListPackageVariables(filter, cfg) 2144 if err != nil { 2145 return err 2146 } 2147 return t.printFilteredVariables("vars", vars, filter, cfg) 2148 } 2149 2150 func regs(t *Term, ctx callContext, args string) error { 2151 includeFp := false 2152 if args == "-a" { 2153 includeFp = true 2154 } 2155 var regs api.Registers 2156 var err error 2157 if ctx.Scope.GoroutineID < 0 && ctx.Scope.Frame == 0 { 2158 regs, err = t.client.ListThreadRegisters(0, includeFp) 2159 } else { 2160 regs, err = t.client.ListScopeRegisters(ctx.Scope, includeFp) 2161 } 2162 if err != nil { 2163 return err 2164 } 2165 fmt.Fprintln(t.stdout, regs) 2166 return nil 2167 } 2168 2169 func stackCommand(t *Term, ctx callContext, args string) error { 2170 sa, err := parseStackArgs(args) 2171 if err != nil { 2172 return err 2173 } 2174 if ctx.Prefix == onPrefix { 2175 ctx.Breakpoint.Stacktrace = sa.depth 2176 return nil 2177 } 2178 var cfg *api.LoadConfig 2179 if sa.full { 2180 cfg = &ShortLoadConfig 2181 } 2182 stack, err := t.client.Stacktrace(ctx.Scope.GoroutineID, sa.depth, sa.opts, cfg) 2183 if err != nil { 2184 return err 2185 } 2186 printStack(t, t.stdout, stack, "", sa.offsets) 2187 if sa.ancestors > 0 { 2188 ancestors, err := t.client.Ancestors(ctx.Scope.GoroutineID, sa.ancestors, sa.ancestorDepth) 2189 if err != nil { 2190 return err 2191 } 2192 for _, ancestor := range ancestors { 2193 fmt.Fprintf(t.stdout, "Created by Goroutine %d:\n", ancestor.ID) 2194 if ancestor.Unreadable != "" { 2195 fmt.Fprintf(t.stdout, "\t%s\n", ancestor.Unreadable) 2196 continue 2197 } 2198 printStack(t, t.stdout, ancestor.Stack, "\t", false) 2199 } 2200 } 2201 return nil 2202 } 2203 2204 type stackArgs struct { 2205 depth int 2206 full bool 2207 offsets bool 2208 opts api.StacktraceOptions 2209 2210 ancestors int 2211 ancestorDepth int 2212 } 2213 2214 func parseStackArgs(argstr string) (stackArgs, error) { 2215 r := stackArgs{ 2216 depth: 50, 2217 full: false, 2218 } 2219 if argstr != "" { 2220 args := strings.Split(argstr, " ") 2221 for i := 0; i < len(args); i++ { 2222 numarg := func(name string) (int, error) { 2223 if i >= len(args) { 2224 return 0, fmt.Errorf("expected number after %s", name) 2225 } 2226 n, err := strconv.Atoi(args[i]) 2227 if err != nil { 2228 return 0, fmt.Errorf("expected number after %s: %v", name, err) 2229 } 2230 return n, nil 2231 2232 } 2233 switch args[i] { 2234 case "-full": 2235 r.full = true 2236 case "-offsets": 2237 r.offsets = true 2238 case "-defer": 2239 r.opts |= api.StacktraceReadDefers 2240 case "-mode": 2241 i++ 2242 if i >= len(args) { 2243 return stackArgs{}, fmt.Errorf("expected normal, simple or fromg after -mode") 2244 } 2245 switch args[i] { 2246 case "normal": 2247 r.opts &^= api.StacktraceSimple 2248 r.opts &^= api.StacktraceG 2249 case "simple": 2250 r.opts |= api.StacktraceSimple 2251 case "fromg": 2252 r.opts |= api.StacktraceG | api.StacktraceSimple 2253 default: 2254 return stackArgs{}, fmt.Errorf("expected normal, simple or fromg after -mode") 2255 } 2256 case "-a": 2257 i++ 2258 n, err := numarg("-a") 2259 if err != nil { 2260 return stackArgs{}, err 2261 } 2262 r.ancestors = n 2263 case "-adepth": 2264 i++ 2265 n, err := numarg("-adepth") 2266 if err != nil { 2267 return stackArgs{}, err 2268 } 2269 r.ancestorDepth = n 2270 default: 2271 n, err := strconv.Atoi(args[i]) 2272 if err != nil { 2273 return stackArgs{}, fmt.Errorf("depth must be a number") 2274 } 2275 r.depth = n 2276 } 2277 } 2278 } 2279 if r.ancestors > 0 && r.ancestorDepth == 0 { 2280 r.ancestorDepth = r.depth 2281 } 2282 return r, nil 2283 } 2284 2285 // getLocation returns the current location or the locations specified by the argument. 2286 // getLocation is used to process the argument of list and edit commands. 2287 func getLocation(t *Term, ctx callContext, args string, showContext bool) (file string, lineno int, showarrow bool, err error) { 2288 switch { 2289 case len(args) == 0 && !ctx.scoped(): 2290 state, err := t.client.GetState() 2291 if err != nil { 2292 return "", 0, false, err 2293 } 2294 if showContext { 2295 printcontext(t, state) 2296 } 2297 if state.SelectedGoroutine != nil { 2298 return state.SelectedGoroutine.CurrentLoc.File, state.SelectedGoroutine.CurrentLoc.Line, true, nil 2299 } 2300 return state.CurrentThread.File, state.CurrentThread.Line, true, nil 2301 2302 case len(args) == 0 && ctx.scoped(): 2303 locs, err := t.client.Stacktrace(ctx.Scope.GoroutineID, ctx.Scope.Frame, 0, nil) 2304 if err != nil { 2305 return "", 0, false, err 2306 } 2307 if ctx.Scope.Frame >= len(locs) { 2308 return "", 0, false, fmt.Errorf("Frame %d does not exist in goroutine %d", ctx.Scope.Frame, ctx.Scope.GoroutineID) 2309 } 2310 loc := locs[ctx.Scope.Frame] 2311 gid := ctx.Scope.GoroutineID 2312 if gid < 0 { 2313 state, err := t.client.GetState() 2314 if err != nil { 2315 return "", 0, false, err 2316 } 2317 if state.SelectedGoroutine != nil { 2318 gid = state.SelectedGoroutine.ID 2319 } 2320 } 2321 if showContext { 2322 fmt.Fprintf(t.stdout, "Goroutine %d frame %d at %s:%d (PC: %#x)\n", gid, ctx.Scope.Frame, loc.File, loc.Line, loc.PC) 2323 } 2324 return loc.File, loc.Line, true, nil 2325 2326 default: 2327 locs, err := t.client.FindLocation(ctx.Scope, args, false, t.substitutePathRules()) 2328 if err != nil { 2329 return "", 0, false, err 2330 } 2331 if len(locs) > 1 { 2332 return "", 0, false, locspec.AmbiguousLocationError{Location: args, CandidatesLocation: locs} 2333 } 2334 loc := locs[0] 2335 if showContext { 2336 fmt.Fprintf(t.stdout, "Showing %s:%d (PC: %#x)\n", loc.File, loc.Line, loc.PC) 2337 } 2338 return loc.File, loc.Line, false, nil 2339 } 2340 } 2341 2342 func listCommand(t *Term, ctx callContext, args string) error { 2343 file, lineno, showarrow, err := getLocation(t, ctx, args, true) 2344 if err != nil { 2345 return err 2346 } 2347 return printfile(t, file, lineno, showarrow) 2348 } 2349 2350 func (c *Commands) sourceCommand(t *Term, ctx callContext, args string) error { 2351 if len(args) == 0 { 2352 return fmt.Errorf("wrong number of arguments: source <filename>") 2353 } 2354 2355 if filepath.Ext(args) == ".star" { 2356 _, err := t.starlarkEnv.Execute(args, nil, "main", nil) 2357 return err 2358 } 2359 2360 if args == "-" { 2361 return t.starlarkEnv.REPL() 2362 } 2363 2364 return c.executeFile(t, args) 2365 } 2366 2367 var errDisasmUsage = errors.New("wrong number of arguments: disassemble [-a <start> <end>] [-l <locspec>]") 2368 2369 func disassCommand(t *Term, ctx callContext, args string) error { 2370 var cmd, rest string 2371 2372 if args != "" { 2373 argv := config.Split2PartsBySpace(args) 2374 if len(argv) != 2 { 2375 return errDisasmUsage 2376 } 2377 cmd = argv[0] 2378 rest = argv[1] 2379 } 2380 2381 flavor := api.IntelFlavour 2382 if t.conf != nil && t.conf.DisassembleFlavor != nil { 2383 switch *t.conf.DisassembleFlavor { 2384 case "go": 2385 flavor = api.GoFlavour 2386 case "gnu": 2387 flavor = api.GNUFlavour 2388 default: 2389 flavor = api.IntelFlavour 2390 } 2391 } 2392 2393 var disasm api.AsmInstructions 2394 var disasmErr error 2395 2396 switch cmd { 2397 case "": 2398 locs, err := t.client.FindLocation(ctx.Scope, "+0", true, t.substitutePathRules()) 2399 if err != nil { 2400 return err 2401 } 2402 disasm, disasmErr = t.client.DisassemblePC(ctx.Scope, locs[0].PC, flavor) 2403 case "-a": 2404 v := config.Split2PartsBySpace(rest) 2405 if len(v) != 2 { 2406 return errDisasmUsage 2407 } 2408 startpc, err := strconv.ParseInt(v[0], 0, 64) 2409 if err != nil { 2410 return fmt.Errorf("wrong argument: %q is not a number", v[0]) 2411 } 2412 endpc, err := strconv.ParseInt(v[1], 0, 64) 2413 if err != nil { 2414 return fmt.Errorf("wrong argument: %q is not a number", v[1]) 2415 } 2416 disasm, disasmErr = t.client.DisassembleRange(ctx.Scope, uint64(startpc), uint64(endpc), flavor) 2417 case "-l": 2418 locs, err := t.client.FindLocation(ctx.Scope, rest, true, t.substitutePathRules()) 2419 if err != nil { 2420 return err 2421 } 2422 if len(locs) != 1 { 2423 return errors.New("expression specifies multiple locations") 2424 } 2425 disasm, disasmErr = t.client.DisassemblePC(ctx.Scope, locs[0].PC, flavor) 2426 default: 2427 return errDisasmUsage 2428 } 2429 2430 if disasmErr != nil { 2431 return disasmErr 2432 } 2433 2434 disasmPrint(disasm, t.stdout) 2435 2436 return nil 2437 } 2438 2439 func libraries(t *Term, ctx callContext, args string) error { 2440 libs, err := t.client.ListDynamicLibraries() 2441 if err != nil { 2442 return err 2443 } 2444 d := digits(len(libs)) 2445 for i := range libs { 2446 fmt.Fprintf(t.stdout, "%"+strconv.Itoa(d)+"d. %#x %s\n", i, libs[i].Address, libs[i].Path) 2447 } 2448 return nil 2449 } 2450 2451 func digits(n int) int { 2452 if n <= 0 { 2453 return 1 2454 } 2455 return int(math.Floor(math.Log10(float64(n)))) + 1 2456 } 2457 2458 func printStack(t *Term, out io.Writer, stack []api.Stackframe, ind string, offsets bool) { 2459 api.PrintStack(t.formatPath, out, stack, ind, offsets, func(api.Stackframe) bool { return true }) 2460 } 2461 2462 func printcontext(t *Term, state *api.DebuggerState) { 2463 for i := range state.Threads { 2464 if (state.CurrentThread != nil) && (state.Threads[i].ID == state.CurrentThread.ID) { 2465 continue 2466 } 2467 if state.Threads[i].Breakpoint != nil { 2468 printcontextThread(t, state.Threads[i]) 2469 } 2470 } 2471 2472 if state.CurrentThread == nil { 2473 fmt.Fprintln(t.stdout, "No current thread available") 2474 return 2475 } 2476 2477 var th *api.Thread 2478 if state.SelectedGoroutine == nil { 2479 th = state.CurrentThread 2480 } else { 2481 for i := range state.Threads { 2482 if state.Threads[i].ID == state.SelectedGoroutine.ThreadID { 2483 th = state.Threads[i] 2484 break 2485 } 2486 } 2487 if th == nil { 2488 printcontextLocation(t, state.SelectedGoroutine.CurrentLoc) 2489 return 2490 } 2491 } 2492 2493 if th.File == "" { 2494 fmt.Fprintf(t.stdout, "Stopped at: 0x%x\n", state.CurrentThread.PC) 2495 t.stdout.ColorizePrint("", bytes.NewReader([]byte("no source available")), 1, 10, 1) 2496 return 2497 } 2498 2499 printcontextThread(t, th) 2500 2501 if state.When != "" { 2502 fmt.Fprintln(t.stdout, state.When) 2503 } 2504 2505 for _, watchpoint := range state.WatchOutOfScope { 2506 fmt.Fprintf(t.stdout, "%s went out of scope and was cleared\n", formatBreakpointName(watchpoint, true)) 2507 } 2508 } 2509 2510 func printcontextLocation(t *Term, loc api.Location) { 2511 fmt.Fprintf(t.stdout, "> %s() %s:%d (PC: %#v)\n", loc.Function.Name(), t.formatPath(loc.File), loc.Line, loc.PC) 2512 if loc.Function != nil && loc.Function.Optimized { 2513 fmt.Fprintln(t.stdout, optimizedFunctionWarning) 2514 } 2515 } 2516 2517 func printReturnValues(t *Term, th *api.Thread) { 2518 if th.ReturnValues == nil { 2519 return 2520 } 2521 fmt.Fprintln(t.stdout, "Values returned:") 2522 for _, v := range th.ReturnValues { 2523 fmt.Fprintf(t.stdout, "\t%s: %s\n", v.Name, v.MultilineString("\t", "")) 2524 } 2525 fmt.Fprintln(t.stdout) 2526 } 2527 2528 func printcontextThread(t *Term, th *api.Thread) { 2529 fn := th.Function 2530 2531 if th.Breakpoint == nil { 2532 printcontextLocation(t, api.Location{PC: th.PC, File: th.File, Line: th.Line, Function: th.Function}) 2533 printReturnValues(t, th) 2534 return 2535 } 2536 2537 args := "" 2538 var hasReturnValue bool 2539 if th.BreakpointInfo != nil && th.Breakpoint.LoadArgs != nil && *th.Breakpoint.LoadArgs == ShortLoadConfig { 2540 var arg []string 2541 for _, ar := range th.BreakpointInfo.Arguments { 2542 // For AI compatibility return values are included in the 2543 // argument list. This is a relic of the dark ages when the 2544 // Go debug information did not distinguish between the two. 2545 // Filter them out here instead, so during trace operations 2546 // they are not printed as an argument. 2547 if (ar.Flags & api.VariableArgument) != 0 { 2548 arg = append(arg, ar.SinglelineString()) 2549 } 2550 if (ar.Flags & api.VariableReturnArgument) != 0 { 2551 hasReturnValue = true 2552 } 2553 } 2554 args = strings.Join(arg, ", ") 2555 } 2556 2557 bpname := "" 2558 if th.Breakpoint.WatchExpr != "" { 2559 bpname = fmt.Sprintf("watchpoint on [%s] ", th.Breakpoint.WatchExpr) 2560 } else if th.Breakpoint.Name != "" { 2561 bpname = fmt.Sprintf("[%s] ", th.Breakpoint.Name) 2562 } 2563 2564 if th.Breakpoint.Tracepoint || th.Breakpoint.TraceReturn { 2565 printTracepoint(t, th, bpname, fn, args, hasReturnValue) 2566 return 2567 } 2568 2569 if hitCount, ok := th.Breakpoint.HitCount[strconv.Itoa(th.GoroutineID)]; ok { 2570 fmt.Fprintf(t.stdout, "> %s%s(%s) %s:%d (hits goroutine(%d):%d total:%d) (PC: %#v)\n", 2571 bpname, 2572 fn.Name(), 2573 args, 2574 t.formatPath(th.File), 2575 th.Line, 2576 th.GoroutineID, 2577 hitCount, 2578 th.Breakpoint.TotalHitCount, 2579 th.PC) 2580 } else { 2581 fmt.Fprintf(t.stdout, "> %s%s(%s) %s:%d (hits total:%d) (PC: %#v)\n", 2582 bpname, 2583 fn.Name(), 2584 args, 2585 t.formatPath(th.File), 2586 th.Line, 2587 th.Breakpoint.TotalHitCount, 2588 th.PC) 2589 } 2590 if th.Function != nil && th.Function.Optimized { 2591 fmt.Fprintln(t.stdout, optimizedFunctionWarning) 2592 } 2593 2594 printReturnValues(t, th) 2595 printBreakpointInfo(t, th, false) 2596 } 2597 2598 func printBreakpointInfo(t *Term, th *api.Thread, tracepointOnNewline bool) { 2599 if th.BreakpointInfo == nil { 2600 return 2601 } 2602 bp := th.Breakpoint 2603 bpi := th.BreakpointInfo 2604 2605 if bp.TraceReturn { 2606 return 2607 } 2608 2609 didprintnl := tracepointOnNewline 2610 tracepointnl := func() { 2611 if !bp.Tracepoint || didprintnl { 2612 return 2613 } 2614 didprintnl = true 2615 fmt.Fprintln(t.stdout) 2616 } 2617 2618 if bpi.Goroutine != nil { 2619 tracepointnl() 2620 writeGoroutineLong(t, t.stdout, bpi.Goroutine, "\t") 2621 } 2622 2623 for _, v := range bpi.Variables { 2624 tracepointnl() 2625 fmt.Fprintf(t.stdout, "\t%s: %s\n", v.Name, v.MultilineString("\t", "")) 2626 } 2627 2628 for _, v := range bpi.Locals { 2629 tracepointnl() 2630 if *bp.LoadLocals == longLoadConfig { 2631 fmt.Fprintf(t.stdout, "\t%s: %s\n", v.Name, v.MultilineString("\t", "")) 2632 } else { 2633 fmt.Fprintf(t.stdout, "\t%s: %s\n", v.Name, v.SinglelineString()) 2634 } 2635 } 2636 2637 if bp.LoadArgs != nil && *bp.LoadArgs == longLoadConfig { 2638 for _, v := range bpi.Arguments { 2639 tracepointnl() 2640 fmt.Fprintf(t.stdout, "\t%s: %s\n", v.Name, v.MultilineString("\t", "")) 2641 } 2642 } 2643 2644 if bpi.Stacktrace != nil { 2645 tracepointnl() 2646 fmt.Fprintf(t.stdout, "\tStack:\n") 2647 printStack(t, t.stdout, bpi.Stacktrace, "\t\t", false) 2648 } 2649 } 2650 2651 func printTracepoint(t *Term, th *api.Thread, bpname string, fn *api.Function, args string, hasReturnValue bool) { 2652 if th.Breakpoint.Tracepoint { 2653 fmt.Fprintf(t.stdout, "> goroutine(%d): %s%s(%s)", th.GoroutineID, bpname, fn.Name(), args) 2654 if !hasReturnValue { 2655 fmt.Fprintln(t.stdout) 2656 } 2657 printBreakpointInfo(t, th, !hasReturnValue) 2658 } 2659 if th.Breakpoint.TraceReturn { 2660 retVals := make([]string, 0, len(th.ReturnValues)) 2661 for _, v := range th.ReturnValues { 2662 retVals = append(retVals, v.SinglelineString()) 2663 } 2664 fmt.Fprintf(t.stdout, " => (%s)\n", strings.Join(retVals, ",")) 2665 } 2666 if th.Breakpoint.TraceReturn || !hasReturnValue { 2667 if th.BreakpointInfo != nil && th.BreakpointInfo.Stacktrace != nil { 2668 fmt.Fprintf(t.stdout, "\tStack:\n") 2669 printStack(t, t.stdout, th.BreakpointInfo.Stacktrace, "\t\t", false) 2670 } 2671 } 2672 } 2673 2674 func printfile(t *Term, filename string, line int, showArrow bool) error { 2675 if filename == "" { 2676 return nil 2677 } 2678 2679 lineCount := t.conf.GetSourceListLineCount() 2680 arrowLine := 0 2681 if showArrow { 2682 arrowLine = line 2683 } 2684 2685 var file *os.File 2686 path := t.substitutePath(filename) 2687 if _, err := os.Stat(path); os.IsNotExist(err) { 2688 foundPath, err := debuginfod.GetSource(t.client.BuildID(), filename) 2689 if err == nil { 2690 path = foundPath 2691 } 2692 } 2693 file, err := os.OpenFile(path, 0, os.ModePerm) 2694 if err != nil { 2695 return err 2696 } 2697 defer file.Close() 2698 2699 fi, _ := file.Stat() 2700 lastModExe := t.client.LastModified() 2701 if fi.ModTime().After(lastModExe) { 2702 fmt.Fprintln(t.stdout, "Warning: listing may not match stale executable") 2703 } 2704 2705 return t.stdout.ColorizePrint(file.Name(), file, line-lineCount, line+lineCount+1, arrowLine) 2706 } 2707 2708 // ExitRequestError is returned when the user 2709 // exits Delve. 2710 type ExitRequestError struct{} 2711 2712 func (ere ExitRequestError) Error() string { 2713 return "" 2714 } 2715 2716 func exitCommand(t *Term, ctx callContext, args string) error { 2717 if args == "-c" { 2718 if !t.client.IsMulticlient() { 2719 return errors.New("not connected to an --accept-multiclient server") 2720 } 2721 t.quitContinue = true 2722 } 2723 return ExitRequestError{} 2724 } 2725 2726 func getBreakpointByIDOrName(t *Term, arg string) (*api.Breakpoint, error) { 2727 if id, err := strconv.Atoi(arg); err == nil { 2728 return t.client.GetBreakpoint(id) 2729 } 2730 return t.client.GetBreakpointByName(arg) 2731 } 2732 2733 func (c *Commands) onCmd(t *Term, ctx callContext, argstr string) error { 2734 args := config.Split2PartsBySpace(argstr) 2735 2736 if len(args) < 2 { 2737 return errors.New("not enough arguments") 2738 } 2739 2740 bp, err := getBreakpointByIDOrName(t, args[0]) 2741 if err != nil { 2742 return err 2743 } 2744 2745 ctx.Prefix = onPrefix 2746 ctx.Breakpoint = bp 2747 2748 if args[1] == "-edit" { 2749 f, err := ioutil.TempFile("", "dlv-on-cmd-") 2750 if err != nil { 2751 return err 2752 } 2753 defer func() { 2754 _ = os.Remove(f.Name()) 2755 }() 2756 attrs := formatBreakpointAttrs("", ctx.Breakpoint, true) 2757 _, err = f.Write([]byte(strings.Join(attrs, "\n"))) 2758 if err != nil { 2759 return err 2760 } 2761 err = f.Close() 2762 if err != nil { 2763 return err 2764 } 2765 2766 err = runEditor(f.Name()) 2767 if err != nil { 2768 return err 2769 } 2770 2771 fin, err := os.Open(f.Name()) 2772 if err != nil { 2773 return err 2774 } 2775 defer fin.Close() 2776 2777 err = c.parseBreakpointAttrs(t, ctx, fin) 2778 if err != nil { 2779 return err 2780 } 2781 } else { 2782 err = c.CallWithContext(args[1], t, ctx) 2783 if err != nil { 2784 return err 2785 } 2786 } 2787 return t.client.AmendBreakpoint(ctx.Breakpoint) 2788 } 2789 2790 func (c *Commands) parseBreakpointAttrs(t *Term, ctx callContext, r io.Reader) error { 2791 ctx.Breakpoint.Tracepoint = false 2792 ctx.Breakpoint.Goroutine = false 2793 ctx.Breakpoint.Stacktrace = 0 2794 ctx.Breakpoint.Variables = ctx.Breakpoint.Variables[:0] 2795 ctx.Breakpoint.Cond = "" 2796 ctx.Breakpoint.HitCond = "" 2797 2798 scan := bufio.NewScanner(r) 2799 lineno := 0 2800 for scan.Scan() { 2801 lineno++ 2802 err := c.CallWithContext(scan.Text(), t, ctx) 2803 if err != nil { 2804 fmt.Fprintf(t.stdout, "%d: %s\n", lineno, err.Error()) 2805 } 2806 } 2807 return scan.Err() 2808 } 2809 2810 func conditionCmd(t *Term, ctx callContext, argstr string) error { 2811 args := config.Split2PartsBySpace(argstr) 2812 2813 if len(args) < 2 { 2814 return fmt.Errorf("not enough arguments") 2815 } 2816 2817 if args[0] == "-hitcount" { 2818 // hitcount breakpoint 2819 2820 if ctx.Prefix == onPrefix { 2821 ctx.Breakpoint.HitCond = args[1] 2822 return nil 2823 } 2824 2825 args = config.Split2PartsBySpace(args[1]) 2826 if len(args) < 2 { 2827 return fmt.Errorf("not enough arguments") 2828 } 2829 2830 bp, err := getBreakpointByIDOrName(t, args[0]) 2831 if err != nil { 2832 return err 2833 } 2834 2835 bp.HitCond = args[1] 2836 2837 return t.client.AmendBreakpoint(bp) 2838 } 2839 2840 if args[0] == "-clear" { 2841 bp, err := getBreakpointByIDOrName(t, args[1]) 2842 if err != nil { 2843 return err 2844 } 2845 bp.Cond = "" 2846 return t.client.AmendBreakpoint(bp) 2847 } 2848 2849 if ctx.Prefix == onPrefix { 2850 ctx.Breakpoint.Cond = argstr 2851 return nil 2852 } 2853 2854 bp, err := getBreakpointByIDOrName(t, args[0]) 2855 if err != nil { 2856 return err 2857 } 2858 bp.Cond = args[1] 2859 2860 return t.client.AmendBreakpoint(bp) 2861 } 2862 2863 func (c *Commands) executeFile(t *Term, name string) error { 2864 fh, err := os.Open(name) 2865 if err != nil { 2866 return err 2867 } 2868 defer fh.Close() 2869 2870 scanner := bufio.NewScanner(fh) 2871 lineno := 0 2872 for scanner.Scan() { 2873 line := strings.TrimSpace(scanner.Text()) 2874 lineno++ 2875 2876 if line == "" || line[0] == '#' { 2877 continue 2878 } 2879 2880 if err := c.Call(line, t); err != nil { 2881 if _, isExitRequest := err.(ExitRequestError); isExitRequest { 2882 return err 2883 } 2884 fmt.Fprintf(t.stdout, "%s:%d: %v\n", name, lineno, err) 2885 } 2886 } 2887 2888 return scanner.Err() 2889 } 2890 2891 func (c *Commands) rewind(t *Term, ctx callContext, args string) error { 2892 c.frame = 0 2893 stateChan := t.client.Rewind() 2894 var state *api.DebuggerState 2895 for state = range stateChan { 2896 if state.Err != nil { 2897 return state.Err 2898 } 2899 printcontext(t, state) 2900 } 2901 printfile(t, state.CurrentThread.File, state.CurrentThread.Line, true) 2902 return nil 2903 } 2904 2905 func checkpoint(t *Term, ctx callContext, args string) error { 2906 if args == "" { 2907 state, err := t.client.GetState() 2908 if err != nil { 2909 return err 2910 } 2911 var loc api.Location = api.Location{PC: state.CurrentThread.PC, File: state.CurrentThread.File, Line: state.CurrentThread.Line, Function: state.CurrentThread.Function} 2912 if state.SelectedGoroutine != nil { 2913 loc = state.SelectedGoroutine.CurrentLoc 2914 } 2915 args = fmt.Sprintf("%s() %s:%d (%#x)", loc.Function.Name(), loc.File, loc.Line, loc.PC) 2916 } 2917 2918 cpid, err := t.client.Checkpoint(args) 2919 if err != nil { 2920 return err 2921 } 2922 2923 fmt.Fprintf(t.stdout, "Checkpoint c%d created.\n", cpid) 2924 return nil 2925 } 2926 2927 func checkpoints(t *Term, ctx callContext, args string) error { 2928 cps, err := t.client.ListCheckpoints() 2929 if err != nil { 2930 return err 2931 } 2932 w := new(tabwriter.Writer) 2933 w.Init(t.stdout, 4, 4, 2, ' ', 0) 2934 fmt.Fprintln(w, "ID\tWhen\tNote") 2935 for _, cp := range cps { 2936 fmt.Fprintf(w, "c%d\t%s\t%s\n", cp.ID, cp.When, cp.Where) 2937 } 2938 w.Flush() 2939 return nil 2940 } 2941 2942 func clearCheckpoint(t *Term, ctx callContext, args string) error { 2943 if len(args) == 0 { 2944 return errors.New("not enough arguments to clear-checkpoint") 2945 } 2946 if args[0] != 'c' { 2947 return errors.New("clear-checkpoint argument must be a checkpoint ID") 2948 } 2949 id, err := strconv.Atoi(args[1:]) 2950 if err != nil { 2951 return errors.New("clear-checkpoint argument must be a checkpoint ID") 2952 } 2953 return t.client.ClearCheckpoint(id) 2954 } 2955 2956 func display(t *Term, ctx callContext, args string) error { 2957 const ( 2958 addOption = "-a " 2959 delOption = "-d " 2960 ) 2961 switch { 2962 case args == "": 2963 t.printDisplays() 2964 2965 case strings.HasPrefix(args, addOption): 2966 args = strings.TrimSpace(args[len(addOption):]) 2967 fmtstr, args := parseFormatArg(args) 2968 if args == "" { 2969 return fmt.Errorf("not enough arguments") 2970 } 2971 t.addDisplay(args, fmtstr) 2972 t.printDisplay(len(t.displays) - 1) 2973 2974 case strings.HasPrefix(args, delOption): 2975 args = strings.TrimSpace(args[len(delOption):]) 2976 n, err := strconv.Atoi(args) 2977 if err != nil { 2978 return fmt.Errorf("%q is not a number", args) 2979 } 2980 return t.removeDisplay(n) 2981 2982 default: 2983 return fmt.Errorf("wrong arguments") 2984 } 2985 return nil 2986 } 2987 2988 func dump(t *Term, ctx callContext, args string) error { 2989 if args == "" { 2990 return fmt.Errorf("not enough arguments") 2991 } 2992 dumpState, err := t.client.CoreDumpStart(args) 2993 if err != nil { 2994 return err 2995 } 2996 for { 2997 if dumpState.ThreadsDone != dumpState.ThreadsTotal { 2998 fmt.Fprintf(t.stdout, "\rDumping threads %d / %d...", dumpState.ThreadsDone, dumpState.ThreadsTotal) 2999 } else { 3000 fmt.Fprintf(t.stdout, "\rDumping memory %d / %d...", dumpState.MemDone, dumpState.MemTotal) 3001 } 3002 if !dumpState.Dumping { 3003 break 3004 } 3005 dumpState = t.client.CoreDumpWait(1000) 3006 } 3007 fmt.Fprintf(t.stdout, "\n") 3008 if dumpState.Err != "" { 3009 fmt.Fprintf(t.stdout, "error dumping: %s\n", dumpState.Err) 3010 } else if !dumpState.AllDone { 3011 fmt.Fprintf(t.stdout, "canceled\n") 3012 } else if dumpState.MemDone != dumpState.MemTotal { 3013 fmt.Fprintf(t.stdout, "Core dump could be incomplete\n") 3014 } 3015 return nil 3016 } 3017 3018 func transcript(t *Term, ctx callContext, args string) error { 3019 argv := strings.SplitN(args, " ", -1) 3020 truncate := false 3021 fileOnly := false 3022 disable := false 3023 path := "" 3024 for _, arg := range argv { 3025 switch arg { 3026 case "-x": 3027 fileOnly = true 3028 case "-t": 3029 truncate = true 3030 case "-off": 3031 disable = true 3032 default: 3033 if path != "" || strings.HasPrefix(arg, "-") { 3034 return fmt.Errorf("unrecognized option %q", arg) 3035 } else { 3036 path = arg 3037 } 3038 } 3039 } 3040 3041 if disable { 3042 if path != "" { 3043 return errors.New("-o option specified with an output path") 3044 } 3045 return t.stdout.CloseTranscript() 3046 } 3047 3048 if path == "" { 3049 return errors.New("no output path specified") 3050 } 3051 3052 flags := os.O_APPEND | os.O_WRONLY | os.O_CREATE 3053 if truncate { 3054 flags |= os.O_TRUNC 3055 } 3056 fh, err := os.OpenFile(path, flags, 0660) 3057 if err != nil { 3058 return err 3059 } 3060 3061 if err := t.stdout.CloseTranscript(); err != nil { 3062 return err 3063 } 3064 3065 t.stdout.TranscribeTo(fh, fileOnly) 3066 return nil 3067 } 3068 3069 func formatBreakpointName(bp *api.Breakpoint, upcase bool) string { 3070 thing := "breakpoint" 3071 if bp.Tracepoint { 3072 thing = "tracepoint" 3073 } 3074 if bp.WatchExpr != "" { 3075 thing = "watchpoint" 3076 } 3077 if upcase { 3078 thing = strings.Title(thing) 3079 } 3080 id := bp.Name 3081 if id == "" { 3082 id = strconv.Itoa(bp.ID) 3083 } 3084 if bp.WatchExpr != "" && bp.WatchExpr != bp.Name { 3085 return fmt.Sprintf("%s %s on [%s]", thing, id, bp.WatchExpr) 3086 } 3087 return fmt.Sprintf("%s %s", thing, id) 3088 } 3089 3090 func (t *Term) formatBreakpointLocation(bp *api.Breakpoint) string { 3091 var out bytes.Buffer 3092 if len(bp.Addrs) > 0 { 3093 for i, addr := range bp.Addrs { 3094 if i == 0 { 3095 fmt.Fprintf(&out, "%#x", addr) 3096 } else { 3097 fmt.Fprintf(&out, ",%#x", addr) 3098 } 3099 } 3100 } else { 3101 // In case we are connecting to an older version of delve that does not return the Addrs field. 3102 fmt.Fprintf(&out, "%#x", bp.Addr) 3103 } 3104 if bp.WatchExpr == "" { 3105 fmt.Fprintf(&out, " for ") 3106 p := t.formatPath(bp.File) 3107 if bp.FunctionName != "" { 3108 fmt.Fprintf(&out, "%s() ", bp.FunctionName) 3109 } 3110 fmt.Fprintf(&out, "%s:%d", p, bp.Line) 3111 } 3112 return out.String() 3113 }