Skip to content

Commit 89123a0

Browse files
pkg/terminal,service/debugger: Support to add a new suboption --follow-calls to trace subcommand (#3594)
* rebasing on master to implement --followcalls * in progress changes to enable --followcalls * rebase to master: modified function to add children to funcs array * modify main traversal loop * added tests to check different scenarios * added tests to check different scenarios * added tests to check different scenarios * add test to check for overlapping regular expression * modified type of strings array as a return only * changed depth to a simple integer instead of a global map * avoid calling traverse on recursive calls * Added tests for various call graphs to test trace followfuncs * Added tests for various call graphs to test trace followfuncs * Added tests for various call graphs to test trace followfuncs * made auxillary changes for build to go through for new option follow-calls * Add support to print depth of the function calls as well * Added two sample output files for checking * Bypass morestack_noctxt in output for verification testing * Corrected newline error by adding newlines only if the line does not match morestack_noctxt * Added more tests * Cleanup * Updated documentation * fixed error message in fmt.Errorf * Fixed result of Errorf not used error * Addressing review comments to fix depth reporting and other issues * dont invoke stacktrace if tracefollowcalls is enabled, compute depth from main regex root symbol than main.main * Addressing a part of review comments * Added changes to allow deferred functions to be picked up for tracing * Fix issue to avoid printing stack for a simple trace option * Moving most tests to integration2_test.go and keeping only one in dlv_test.go * Moving most tests to integration2_test.go and keeping only one in dlv_test.go * Adding panic-defer test case * Moved rest of the tests to integration2_test.go * addressing review comments: folding Functions and FunctionsDeep, reducing branches by using depth prefix, wrap using %w and other comments * Optimize traversal and parts of printing trace point function and modify trace output layout and adjust tests accordingly * Resolved error occurring due to staticcheck * Implemented traversal algorithm using breadth first search * Addressing review comments on the breadth first search implementation and other comments * Inline filterRuntimeFuncs and remove duplicate initialization
1 parent 15a9f9d commit 89123a0

File tree

23 files changed

+498
-55
lines changed

23 files changed

+498
-55
lines changed

Documentation/cli/starlark.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ breakpoints(All) | Equivalent to API call [ListBreakpoints](https://godoc.org/gi
5050
checkpoints() | Equivalent to API call [ListCheckpoints](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListCheckpoints)
5151
dynamic_libraries() | Equivalent to API call [ListDynamicLibraries](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListDynamicLibraries)
5252
function_args(Scope, Cfg) | Equivalent to API call [ListFunctionArgs](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListFunctionArgs)
53-
functions(Filter) | Equivalent to API call [ListFunctions](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListFunctions)
53+
functions(Filter, FollowCalls) | Equivalent to API call [ListFunctions](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListFunctions)
5454
goroutines(Start, Count, Filters, GoroutineGroupingOptions, EvalScope) | Equivalent to API call [ListGoroutines](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListGoroutines)
5555
local_vars(Scope, Cfg) | Equivalent to API call [ListLocalVars](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListLocalVars)
5656
package_vars(Filter, Cfg) | Equivalent to API call [ListPackageVars](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListPackageVars)

Documentation/usage/dlv_trace.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,15 @@ dlv trace [package] regexp [flags]
2121
### Options
2222

2323
```
24-
--ebpf Trace using eBPF (experimental).
25-
-e, --exec string Binary file to exec and trace.
26-
-h, --help help for trace
27-
--output string Output path for the binary.
28-
-p, --pid int Pid to attach to.
29-
-s, --stack int Show stack trace with given depth. (Ignored with --ebpf)
30-
-t, --test Trace a test binary.
31-
--timestamp Show timestamp in the output
24+
--ebpf Trace using eBPF (experimental).
25+
-e, --exec string Binary file to exec and trace.
26+
--follow-calls int Trace all children of the function to the required depth
27+
-h, --help help for trace
28+
--output string Output path for the binary.
29+
-p, --pid int Pid to attach to.
30+
-s, --stack int Show stack trace with given depth. (Ignored with --ebpf)
31+
-t, --test Trace a test binary.
32+
--timestamp Show timestamp in the output
3233
```
3334

3435
### Options inherited from parent commands

_fixtures/leaf4.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package main
2+
3+
import "fmt"
4+
5+
func D(i int) int {
6+
return i * i * i
7+
}
8+
func C(i int) int {
9+
10+
return i + 20
11+
}
12+
func B(i int) int {
13+
d := C(i) + 40
14+
return d + D(i)
15+
}
16+
func A(i int) int {
17+
return 10 + B(i)
18+
}
19+
func main() {
20+
j := 0
21+
j += A(2)
22+
fmt.Println(j)
23+
}

_fixtures/leafcommon.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package main
2+
3+
import "fmt"
4+
5+
func D(i int) int {
6+
return i * i * i
7+
}
8+
func C(i int) int {
9+
10+
return D(i+10) + 20
11+
}
12+
func B(i int) int {
13+
return i * D(i)
14+
}
15+
func A(i int) int {
16+
d := 10 + B(i)
17+
return d + C(i)
18+
}
19+
func main() {
20+
j := 0
21+
j += A(2)
22+
fmt.Println(j)
23+
}

_fixtures/leafindrec.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package main
2+
3+
import "fmt"
4+
5+
func B(i int) int {
6+
if i > 0 {
7+
return A(i - 1)
8+
} else {
9+
return 0
10+
}
11+
}
12+
func A(n int) int {
13+
if n <= 1 {
14+
return n
15+
} else {
16+
return B(n - 3)
17+
}
18+
}
19+
func main() {
20+
j := 0
21+
j += B(12)
22+
fmt.Println(j)
23+
}

_fixtures/leafrec.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package main
2+
3+
import "fmt"
4+
5+
func A(i int, n int) int {
6+
if n == 1 {
7+
return i
8+
} else {
9+
n--
10+
return (i * A(i-1, n))
11+
}
12+
}
13+
func main() {
14+
j := 0
15+
j += A(5, 5)
16+
fmt.Println(j)
17+
}

_fixtures/leafregex.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package main
2+
3+
import "fmt"
4+
5+
func callmed(i int) int {
6+
return i * i * i
7+
}
8+
func callmee(i int) int {
9+
10+
return i + 20
11+
}
12+
func callme2(i int) int {
13+
d := callmee(i) + 40
14+
return d + callmed(i)
15+
}
16+
func callme(i int) int {
17+
return 10 + callme2(i)
18+
}
19+
func main() {
20+
j := 0
21+
j += callme(2)
22+
fmt.Println(j)
23+
}

_fixtures/panicex.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package main
2+
3+
func F0() {
4+
defer func() {
5+
recover()
6+
}()
7+
F1()
8+
}
9+
10+
func F1() {
11+
F2()
12+
}
13+
14+
func F2() {
15+
F3()
16+
}
17+
18+
func F3() {
19+
F4()
20+
}
21+
22+
func F4() {
23+
panic("blah")
24+
}
25+
26+
func main() {
27+
F0()
28+
}

_fixtures/testtracefns.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package main
2+
3+
import "fmt"
4+
5+
func D(i int) int {
6+
return i * i * i
7+
}
8+
func C(i int) int {
9+
10+
return D(i+10) + 20
11+
}
12+
func B(i int) int {
13+
return i * D(i)
14+
}
15+
func A(i int) int {
16+
d := 10 + B(i)
17+
return d + C(i)
18+
}
19+
func second(i int) int {
20+
if i > 0 {
21+
return first(i - 1)
22+
} else {
23+
return 0
24+
}
25+
}
26+
func first(n int) int {
27+
if n <= 1 {
28+
return n
29+
} else {
30+
return second(n - 3)
31+
}
32+
}
33+
34+
func callmed(i int) int {
35+
return i * i * i
36+
}
37+
func callmee(i int) int {
38+
39+
return i + 20
40+
}
41+
func callme2(i int) int {
42+
d := callmee(i) + 40
43+
return d + callmed(i)
44+
}
45+
func callme(i int) int {
46+
return 10 + callme2(i)
47+
}
48+
49+
func F0() {
50+
defer func() {
51+
recover()
52+
}()
53+
F1()
54+
}
55+
56+
func F1() {
57+
F2()
58+
}
59+
60+
func F2() {
61+
F3()
62+
}
63+
64+
func F3() {
65+
F4()
66+
}
67+
68+
func F4() {
69+
panic("blah")
70+
}
71+
72+
func main() {
73+
j := 0
74+
j += A(2)
75+
76+
j += first(6)
77+
j += callme(2)
78+
fmt.Println(j)
79+
F0()
80+
81+
}

cmd/dlv/cmds/commands.go

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ var (
8888
traceStackDepth int
8989
traceUseEBPF bool
9090
traceShowTimestamp bool
91+
traceFollowCalls int
9192

9293
// redirect specifications for target process
9394
redirects []string
@@ -363,6 +364,7 @@ only see the output of the trace operations you can redirect stdout.`,
363364
must(traceCommand.RegisterFlagCompletionFunc("stack", cobra.NoFileCompletions))
364365
traceCommand.Flags().String("output", "", "Output path for the binary.")
365366
must(traceCommand.MarkFlagFilename("output"))
367+
traceCommand.Flags().IntVarP(&traceFollowCalls, "follow-calls", "", 0, "Trace all children of the function to the required depth")
366368
rootCommand.AddCommand(traceCommand)
367369

368370
coreCommand := &cobra.Command{
@@ -702,6 +704,10 @@ func traceCmd(cmd *cobra.Command, args []string, conf *config.Config) int {
702704

703705
processArgs = append([]string{debugname}, targetArgs...)
704706
}
707+
if dlvArgsLen >= 3 && traceFollowCalls <= 0 {
708+
fmt.Fprintln(os.Stderr, "Need to specify a trace depth of atleast 1")
709+
return 1
710+
}
705711

706712
// Make a local in-memory connection that client and server use to communicate
707713
listener, clientConn := service.ListenerPipe()
@@ -738,8 +744,7 @@ func traceCmd(cmd *cobra.Command, args []string, conf *config.Config) int {
738744
<-ch
739745
client.Halt()
740746
}()
741-
742-
funcs, err := client.ListFunctions(regexp)
747+
funcs, err := client.ListFunctions(regexp, traceFollowCalls)
743748
if err != nil {
744749
fmt.Fprintln(os.Stderr, err)
745750
return 1
@@ -755,13 +760,22 @@ func traceCmd(cmd *cobra.Command, args []string, conf *config.Config) int {
755760
}
756761
} else {
757762
// Fall back to breakpoint based tracing if we get an error.
763+
var stackdepth int
764+
// Default size of stackdepth to trace function calls and descendants=20
765+
stackdepth = traceStackDepth
766+
if traceFollowCalls > 0 && stackdepth == 0 {
767+
stackdepth = 20
768+
}
758769
_, err = client.CreateBreakpoint(&api.Breakpoint{
759-
FunctionName: funcs[i],
760-
Tracepoint: true,
761-
Line: -1,
762-
Stacktrace: traceStackDepth,
763-
LoadArgs: &terminal.ShortLoadConfig,
770+
FunctionName: funcs[i],
771+
Tracepoint: true,
772+
Line: -1,
773+
Stacktrace: stackdepth,
774+
LoadArgs: &terminal.ShortLoadConfig,
775+
TraceFollowCalls: traceFollowCalls,
776+
RootFuncName: regexp,
764777
})
778+
765779
if err != nil && !isBreakpointExistsErr(err) {
766780
fmt.Fprintf(os.Stderr, "unable to set tracepoint on function %s: %#v\n", funcs[i], err)
767781
continue
@@ -775,11 +789,13 @@ func traceCmd(cmd *cobra.Command, args []string, conf *config.Config) int {
775789
}
776790
for i := range addrs {
777791
_, err = client.CreateBreakpoint(&api.Breakpoint{
778-
Addr: addrs[i],
779-
TraceReturn: true,
780-
Stacktrace: traceStackDepth,
781-
Line: -1,
782-
LoadArgs: &terminal.ShortLoadConfig,
792+
Addr: addrs[i],
793+
TraceReturn: true,
794+
Stacktrace: stackdepth,
795+
Line: -1,
796+
LoadArgs: &terminal.ShortLoadConfig,
797+
TraceFollowCalls: traceFollowCalls,
798+
RootFuncName: regexp,
783799
})
784800
if err != nil && !isBreakpointExistsErr(err) {
785801
fmt.Fprintf(os.Stderr, "unable to set tracepoint on function %s: %#v\n", funcs[i], err)

cmd/dlv/dlv_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -970,6 +970,39 @@ func TestTrace2(t *testing.T) {
970970
assertNoError(cmd.Wait(), t, "cmd.Wait()")
971971
}
972972

973+
func TestTraceDirRecursion(t *testing.T) {
974+
dlvbin := getDlvBin(t)
975+
976+
expected := []byte("> goroutine(1):frame(1) main.A(5, 5)\n > goroutine(1):frame(2) main.A(4, 4)\n > goroutine(1):frame(3) main.A(3, 3)\n > goroutine(1):frame(4) main.A(2, 2)\n > goroutine(1):frame(5) main.A(1, 1)\n >> goroutine(1):frame(5) main.A => (1)\n >> goroutine(1):frame(4) main.A => (2)\n >> goroutine(1):frame(3) main.A => (6)\n >> goroutine(1):frame(2) main.A => (24)\n>> goroutine(1):frame(1) main.A => (120)\n")
977+
978+
fixtures := protest.FindFixturesDir()
979+
cmd := exec.Command(dlvbin, "trace", "--output", filepath.Join(t.TempDir(), "__debug"), filepath.Join(fixtures, "leafrec.go"), "main.A", "--follow-calls", "4")
980+
rdr, err := cmd.StderrPipe()
981+
assertNoError(err, t, "stderr pipe")
982+
defer rdr.Close()
983+
984+
cmd.Dir = filepath.Join(fixtures, "buildtest")
985+
986+
assertNoError(cmd.Start(), t, "running trace")
987+
// Parse output to ignore calls to morestack_noctxt for comparison
988+
scan := bufio.NewScanner(rdr)
989+
text := ""
990+
outputtext := ""
991+
for scan.Scan() {
992+
text = scan.Text()
993+
if !strings.Contains(text, "morestack_noctxt") {
994+
outputtext += text
995+
outputtext += "\n"
996+
}
997+
}
998+
output := []byte(outputtext)
999+
1000+
if !bytes.Contains(output, expected) {
1001+
t.Fatalf("expected:\n%s\ngot:\n%s", string(expected), string(output))
1002+
}
1003+
assertNoError(cmd.Wait(), t, "cmd.Wait()")
1004+
}
1005+
9731006
func TestTraceMultipleGoroutines(t *testing.T) {
9741007
dlvbin := getDlvBin(t)
9751008

0 commit comments

Comments
 (0)