Skip to content

Commit

Permalink
cmd/dlv: Improve dlv trace REPL command
Browse files Browse the repository at this point in the history
This patch fixes the `dlv trace` REPL command to behave like the
subcommand in certain situations. If the tracepoint is for a function,
we now show function arguements and return values properly.

Also makes the overall output of the trace subcommand clearer.
  • Loading branch information
derekparker committed Apr 30, 2020
1 parent 9bdac48 commit 260089b
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 23 deletions.
56 changes: 38 additions & 18 deletions pkg/terminal/command.go
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/go-delve/delve/service"
"github.com/go-delve/delve/service/api"
"github.com/go-delve/delve/service/debugger"
"github.com/go-delve/delve/service/rpc2"
)

const optimizedFunctionWarning = "Warning: debugging optimized function"
Expand Down Expand Up @@ -661,7 +662,7 @@ func printGoroutines(t *Term, gs []*api.Goroutine, fgl formatGoroutineLoc, flags
if err != nil {
return err
}
printStack(stack, "\t", false)
printStack(os.Stdout, stack, "\t", false)
}
}
return nil
Expand Down Expand Up @@ -1411,6 +1412,7 @@ func setBreakpoint(t *Term, ctx callContext, tracepoint bool, argstr string) err
for _, loc := range locs {
requestedBp.Addr = loc.PC
requestedBp.Addrs = loc.PCs
requestedBp.LoadArgs = &ShortLoadConfig

bp, err := t.client.CreateBreakpoint(requestedBp)
if err != nil {
Expand All @@ -1419,6 +1421,24 @@ func setBreakpoint(t *Term, ctx callContext, tracepoint bool, argstr string) err

fmt.Printf("%s set at %s\n", formatBreakpointName(bp, true), formatBreakpointLocation(bp))
}

if tracepoint && locs[0].Function != nil {
addrs, err := t.client.(*rpc2.RPCClient).FunctionReturnLocations(locs[0].Function.Name())
if err != nil {
return err
}
for i := range addrs {
_, err = t.client.CreateBreakpoint(&api.Breakpoint{
Addr: addrs[i],
TraceReturn: true,
Line: -1,
LoadArgs: &ShortLoadConfig,
})
if err != nil {
return err
}
}
}
return nil
}

Expand Down Expand Up @@ -1719,7 +1739,7 @@ func stackCommand(t *Term, ctx callContext, args string) error {
if err != nil {
return err
}
printStack(stack, "", sa.offsets)
printStack(os.Stdout, stack, "", sa.offsets)
if sa.ancestors > 0 {
ancestors, err := t.client.Ancestors(ctx.Scope.GoroutineID, sa.ancestors, sa.ancestorDepth)
if err != nil {
Expand All @@ -1731,7 +1751,7 @@ func stackCommand(t *Term, ctx callContext, args string) error {
fmt.Printf("\t%s\n", ancestor.Unreadable)
continue
}
printStack(ancestor.Stack, "\t", false)
printStack(os.Stdout, ancestor.Stack, "\t", false)
}
}
return nil
Expand Down Expand Up @@ -1993,7 +2013,7 @@ func digits(n int) int {

const stacktraceTruncatedMessage = "(truncated)"

func printStack(stack []api.Stackframe, ind string, offsets bool) {
func printStack(out io.Writer, stack []api.Stackframe, ind string, offsets bool) {
if len(stack) == 0 {
return
}
Expand All @@ -2012,42 +2032,42 @@ func printStack(stack []api.Stackframe, ind string, offsets bool) {

for i := range stack {
if stack[i].Err != "" {
fmt.Printf("%serror: %s\n", s, stack[i].Err)
fmt.Fprintf(out, "%serror: %s\n", s, stack[i].Err)
continue
}
fmt.Printf(fmtstr, ind, i, stack[i].PC, stack[i].Function.Name())
fmt.Printf("%sat %s:%d\n", s, shortenFilePath(stack[i].File), stack[i].Line)
fmt.Fprintf(out, fmtstr, ind, i, stack[i].PC, stack[i].Function.Name())
fmt.Fprintf(out, "%sat %s:%d\n", s, shortenFilePath(stack[i].File), stack[i].Line)

if offsets {
fmt.Printf("%sframe: %+#x frame pointer %+#x\n", s, stack[i].FrameOffset, stack[i].FramePointerOffset)
fmt.Fprintf(out, "%sframe: %+#x frame pointer %+#x\n", s, stack[i].FrameOffset, stack[i].FramePointerOffset)
}

for j, d := range stack[i].Defers {
deferHeader := fmt.Sprintf("%s defer %d: ", s, j+1)
s2 := strings.Repeat(" ", len(deferHeader))
if d.Unreadable != "" {
fmt.Printf("%s(unreadable defer: %s)\n", deferHeader, d.Unreadable)
fmt.Fprintf(out, "%s(unreadable defer: %s)\n", deferHeader, d.Unreadable)
continue
}
fmt.Printf("%s%#016x in %s\n", deferHeader, d.DeferredLoc.PC, d.DeferredLoc.Function.Name())
fmt.Printf("%sat %s:%d\n", s2, d.DeferredLoc.File, d.DeferredLoc.Line)
fmt.Printf("%sdeferred by %s at %s:%d\n", s2, d.DeferLoc.Function.Name(), d.DeferLoc.File, d.DeferLoc.Line)
fmt.Fprintf(out, "%s%#016x in %s\n", deferHeader, d.DeferredLoc.PC, d.DeferredLoc.Function.Name())
fmt.Fprintf(out, "%sat %s:%d\n", s2, d.DeferredLoc.File, d.DeferredLoc.Line)
fmt.Fprintf(out, "%sdeferred by %s at %s:%d\n", s2, d.DeferLoc.Function.Name(), d.DeferLoc.File, d.DeferLoc.Line)
}

for j := range stack[i].Arguments {
fmt.Printf("%s %s = %s\n", s, stack[i].Arguments[j].Name, stack[i].Arguments[j].SinglelineString())
fmt.Fprintf(out, "%s %s = %s\n", s, stack[i].Arguments[j].Name, stack[i].Arguments[j].SinglelineString())
}
for j := range stack[i].Locals {
fmt.Printf("%s %s = %s\n", s, stack[i].Locals[j].Name, stack[i].Locals[j].SinglelineString())
fmt.Fprintf(out, "%s %s = %s\n", s, stack[i].Locals[j].Name, stack[i].Locals[j].SinglelineString())
}

if extranl {
fmt.Println()
fmt.Fprintln(out)
}
}

if len(stack) > 0 && !stack[len(stack)-1].Bottom {
fmt.Printf("%s"+stacktraceTruncatedMessage+"\n", ind)
fmt.Fprintf(out, "%s"+stacktraceTruncatedMessage+"\n", ind)
}
}

Expand Down Expand Up @@ -2208,7 +2228,7 @@ func printcontextThread(t *Term, th *api.Thread) {

if bpi.Stacktrace != nil {
fmt.Printf("\tStack:\n")
printStack(bpi.Stacktrace, "\t\t", false)
printStack(os.Stdout, bpi.Stacktrace, "\t\t", false)
}
}
}
Expand All @@ -2230,7 +2250,7 @@ func printTracepoint(th *api.Thread, bpname string, fn *api.Function, args strin
if th.Breakpoint.TraceReturn || !hasReturnValue {
if th.BreakpointInfo.Stacktrace != nil {
fmt.Fprintf(os.Stderr, "\tStack:\n")
printStack(th.BreakpointInfo.Stacktrace, "\t\t", false)
printStack(os.Stderr, th.BreakpointInfo.Stacktrace, "\t\t", false)
}
}
}
Expand Down
44 changes: 41 additions & 3 deletions pkg/terminal/command_test.go
Expand Up @@ -247,8 +247,8 @@ func TestExecuteFile(t *testing.T) {
}

func TestIssue354(t *testing.T) {
printStack([]api.Stackframe{}, "", false)
printStack([]api.Stackframe{
printStack(os.Stdout, []api.Stackframe{}, "", false)
printStack(os.Stdout, []api.Stackframe{
{Location: api.Location{PC: 0, File: "irrelevant.go", Line: 10, Function: nil},
Bottom: true}}, "", false)
}
Expand All @@ -263,12 +263,50 @@ func TestIssue411(t *testing.T) {
term.MustExec("trace _fixtures/math.go:9")
term.MustExec("continue")
out := term.MustExec("next")
if !strings.HasPrefix(out, "> main.main()") {
if !strings.HasPrefix(out, "> goroutine(1): main.main()") {
t.Fatalf("Wrong output for next: <%s>", out)
}
})
}

func TestTrace(t *testing.T) {
if runtime.GOARCH == "arm64" {
t.Skip("test is not valid on ARM64")
}
test.AllowRecording(t)
withTestTerminal("issue573", t, func(term *FakeTerminal) {
term.MustExec("trace foo")
out, _ := term.Exec("continue")
// The output here is a little strange, but we don't filter stdout vs stderr so it gets jumbled.
// Therefore we assert about the call and return values separately.
if !strings.Contains(out, "> goroutine(1): main.foo(99, 9801)") {
t.Fatalf("Wrong output for tracepoint: %s", out)
}
if !strings.Contains(out, "=> (9900)") {
t.Fatalf("Wrong output for tracepoint return value: %s", out)
}
})
}

func TestTraceWithName(t *testing.T) {
if runtime.GOARCH == "arm64" {
t.Skip("test is not valid on ARM64")
}
test.AllowRecording(t)
withTestTerminal("issue573", t, func(term *FakeTerminal) {
term.MustExec("trace foobar foo")
out, _ := term.Exec("continue")
// The output here is a little strange, but we don't filter stdout vs stderr so it gets jumbled.
// Therefore we assert about the call and return values separately.
if !strings.Contains(out, "> goroutine(1): [foobar] main.foo(99, 9801)") {
t.Fatalf("Wrong output for tracepoint: %s", out)
}
if !strings.Contains(out, "=> (9900)") {
t.Fatalf("Wrong output for tracepoint return value: %s", out)
}
})
}

func TestExitStatus(t *testing.T) {
withTestTerminal("continuetestprog", t, func(term *FakeTerminal) {
term.Exec("continue")
Expand Down
4 changes: 2 additions & 2 deletions service/api/types.go
Expand Up @@ -252,8 +252,8 @@ type Variable struct {

Kind reflect.Kind `json:"kind"`

//Strings have their length capped at proc.maxArrayValues, use Len for the real length of a string
//Function variables will store the name of the function in this field
// Strings have their length capped at proc.maxArrayValues, use Len for the real length of a string
// Function variables will store the name of the function in this field
Value string `json:"value"`

// Number of elements in an array or a slice, number of keys for a map, number of struct members for a struct, length of strings
Expand Down

0 comments on commit 260089b

Please sign in to comment.