diff --git a/Documentation/cli/README.md b/Documentation/cli/README.md index a8dec03d..c23c282b 100644 --- a/Documentation/cli/README.md +++ b/Documentation/cli/README.md @@ -39,6 +39,7 @@ Command | Description Command | Description --------|------------ [args](#args) | Print function arguments. +[display](#display) | Print value of an expression every time the program stops. [examinemem](#examinemem) | Examine memory: [locals](#locals) | Print local variables. [print](#print) | Evaluate an expression. @@ -225,6 +226,17 @@ If no argument is specified the function being executed in the selected stack fr Aliases: disass +## display +Print value of an expression every time the program stops. + + display -a + display -d + +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. + +If display is called without arguments it will print the value of all expression in the list. + + ## down Move the current frame down. @@ -246,7 +258,7 @@ Aliases: ed ## examinemem Examine memory: - examinemem [-fmt ] [-len ]
+ examinemem [-fmt ] [-len ]
Format represents the data format and the value is one of this list (default hex): bin(binary), oct(octal), dec(decimal), hex(hexadecimal),. Length is the number of bytes (default 1) and must be less than or equal to 1000. diff --git a/pkg/terminal/command.go b/pkg/terminal/command.go index fd485c1b..07ce2285 100644 --- a/pkg/terminal/command.go +++ b/pkg/terminal/command.go @@ -375,7 +375,7 @@ If locspec is omitted edit will open the current source file in the editor, othe {aliases: []string{"examinemem", "x"}, group: dataCmds, cmdFn: examineMemoryCmd, helpMsg: `Examine memory: - examinemem [-fmt ] [-len ]
+ examinemem [-fmt ] [-len ]
Format represents the data format and the value is one of this list (default hex): bin(binary), oct(octal), dec(decimal), hex(hexadecimal),. Length is the number of bytes (default 1) and must be less than or equal to 1000. @@ -384,6 +384,15 @@ Address is the memory location of the target to examine. For example: x -fmt hex -len 20 0xc00008af38`}, + + {aliases: []string{"display"}, group: dataCmds, cmdFn: display, helpMsg: `Print value of an expression every time the program stops. + + display -a + display -d + +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. + +If display is called without arguments it will print the value of all expression in the list.`}, } if client == nil || client.Recorded() { @@ -981,6 +990,7 @@ func restartRecorded(t *Term, ctx callContext, args string) error { } printcontext(t, state) printfile(t, state.CurrentThread.File, state.CurrentThread.Line, true) + t.onStop() return nil } @@ -1057,6 +1067,7 @@ func (c *Commands) cont(t *Term, ctx callContext, args string) error { if ctx.Prefix == revPrefix { return c.rewind(t, ctx, args) } + defer t.onStop() c.frame = 0 stateChan := t.client.Continue() var state *api.DebuggerState @@ -1072,6 +1083,7 @@ func (c *Commands) cont(t *Term, ctx callContext, args string) error { } func continueUntilCompleteNext(t *Term, state *api.DebuggerState, op string, shouldPrintFile bool) error { + defer t.onStop() if !state.NextInProgress { if shouldPrintFile { printfile(t, state.CurrentThread.File, state.CurrentThread.Line, true) @@ -1141,6 +1153,8 @@ func (c *Commands) stepInstruction(t *Term, ctx callContext, args string) error return notOnFrameZeroErr } + defer t.onStop() + var fn func() (*api.DebuggerState, error) if ctx.Prefix == revPrefix { fn = t.client.ReverseStepInstruction @@ -2386,6 +2400,37 @@ func clearCheckpoint(t *Term, ctx callContext, args string) error { return t.client.ClearCheckpoint(id) } +func display(t *Term, ctx callContext, args string) error { + const ( + addOption = "-a " + delOption = "-d " + ) + switch { + case args == "": + t.printDisplays() + + case strings.HasPrefix(args, addOption): + args = strings.TrimSpace(args[len(addOption):]) + if args == "" { + return fmt.Errorf("not enough arguments") + } + t.addDisplay(args) + t.printDisplay(len(t.displays) - 1) + + case strings.HasPrefix(args, delOption): + args = strings.TrimSpace(args[len(delOption):]) + n, err := strconv.Atoi(args) + if err != nil { + return fmt.Errorf("%q is not a number", args) + } + return t.removeDisplay(n) + + default: + return fmt.Errorf("wrong arguments") + } + return nil +} + func formatBreakpointName(bp *api.Breakpoint, upcase bool) string { thing := "breakpoint" if bp.Tracepoint { diff --git a/pkg/terminal/terminal.go b/pkg/terminal/terminal.go index d802cdd8..27b65ddb 100644 --- a/pkg/terminal/terminal.go +++ b/pkg/terminal/terminal.go @@ -54,6 +54,7 @@ type Term struct { dumb bool stdout io.Writer InitFile string + displays []string starlarkEnv *starbind.Env @@ -432,6 +433,50 @@ func (t *Term) loadConfig() api.LoadConfig { return r } +func (t *Term) removeDisplay(n int) error { + if n < 0 || n >= len(t.displays) { + return fmt.Errorf("%d is out of range", n) + } + t.displays[n] = "" + for i := len(t.displays) - 1; i >= 0; i-- { + if t.displays[i] != "" { + t.displays = t.displays[:i+1] + return nil + } + } + t.displays = t.displays[:0] + return nil +} + +func (t *Term) addDisplay(expr string) { + t.displays = append(t.displays, expr) +} + +func (t *Term) printDisplay(i int) { + expr := t.displays[i] + val, err := t.client.EvalVariable(api.EvalScope{GoroutineID: -1}, expr, ShortLoadConfig) + if err != nil { + if isErrProcessExited(err) { + return + } + fmt.Printf("%d: %s = error %v\n", i, expr, err) + return + } + fmt.Printf("%d: %s = %s\n", i, val.Name, val.SinglelineString()) +} + +func (t *Term) printDisplays() { + for i := range t.displays { + if t.displays[i] != "" { + t.printDisplay(i) + } + } +} + +func (t *Term) onStop() { + t.printDisplays() +} + // isErrProcessExited returns true if `err` is an RPC error equivalent of proc.ErrProcessExited func isErrProcessExited(err error) bool { rpcError, ok := err.(rpc.ServerError)