diff --git a/_fixtures/ebpf_trace.go b/_fixtures/ebpf_trace.go new file mode 100644 index 00000000..be4d3924 --- /dev/null +++ b/_fixtures/ebpf_trace.go @@ -0,0 +1,14 @@ +package main + +import "fmt" + +func callme(i int) int { + if i == 0 { + return 100 + } + return callme(i - 1) +} + +func main() { + fmt.Println(callme(10)) +} diff --git a/cmd/dlv/cmds/commands.go b/cmd/dlv/cmds/commands.go index 89cb799e..70d62c15 100644 --- a/cmd/dlv/cmds/commands.go +++ b/cmd/dlv/cmds/commands.go @@ -691,7 +691,6 @@ func traceCmd(cmd *cobra.Command, args []string) { done := make(chan struct{}) defer close(done) go func() { - gFnEntrySeen := map[int]struct{}{} for { select { case <-done: @@ -713,14 +712,11 @@ func traceCmd(cmd *cobra.Command, args []string) { params.WriteString(p.Value) } } - _, seen := gFnEntrySeen[t.GoroutineID] - if seen { + if t.IsRet { for _, p := range t.ReturnParams { fmt.Fprintf(os.Stderr, "=> %#v\n", p.Value) } - delete(gFnEntrySeen, t.GoroutineID) } else { - gFnEntrySeen[t.GoroutineID] = struct{}{} fmt.Fprintf(os.Stderr, "> (%d) %s(%s)\n", t.GoroutineID, t.FunctionName, params.String()) } } diff --git a/cmd/dlv/dlv_test.go b/cmd/dlv/dlv_test.go index 2bb3a44e..6ee7177d 100644 --- a/cmd/dlv/dlv_test.go +++ b/cmd/dlv/dlv_test.go @@ -1146,6 +1146,67 @@ func TestTraceEBPF(t *testing.T) { cmd.Wait() } +func TestTraceEBPF2(t *testing.T) { + if os.Getenv("CI") == "true" { + t.Skip("cannot run test in CI, requires kernel compiled with btf support") + } + if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" { + t.Skip("not implemented on non linux/amd64 systems") + } + if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 16) { + t.Skip("requires at least Go 1.16 to run test") + } + usr, err := user.Current() + if err != nil { + t.Fatal(err) + } + if usr.Uid != "0" { + t.Skip("test must be run as root") + } + + dlvbin, tmpdir := getDlvBinEBPF(t) + defer os.RemoveAll(tmpdir) + + expected := []byte(`> (1) main.callme(10) +> (1) main.callme(9) +> (1) main.callme(8) +> (1) main.callme(7) +> (1) main.callme(6) +> (1) main.callme(5) +> (1) main.callme(4) +> (1) main.callme(3) +> (1) main.callme(2) +> (1) main.callme(1) +> (1) main.callme(0) +=> "100" +=> "100" +=> "100" +=> "100" +=> "100" +=> "100" +=> "100" +=> "100" +=> "100" +=> "100" +=> "100"`) + + fixtures := protest.FindFixturesDir() + cmd := exec.Command(dlvbin, "trace", "--ebpf", "--output", filepath.Join(tmpdir, "__debug"), filepath.Join(fixtures, "ebpf_trace.go"), "main.callme") + rdr, err := cmd.StderrPipe() + assertNoError(err, t, "stderr pipe") + defer rdr.Close() + + assertNoError(cmd.Start(), t, "running trace") + + output, err := ioutil.ReadAll(rdr) + assertNoError(err, t, "ReadAll") + + if !bytes.Contains(output, expected) { + t.Fatalf("expected:\n%s\ngot:\n%s", string(expected), string(output)) + } + cmd.Wait() +} + func TestDlvTestChdir(t *testing.T) { dlvbin, tmpdir := getDlvBin(t) defer os.RemoveAll(tmpdir) diff --git a/pkg/proc/internal/ebpf/bpf/trace.bpf.c b/pkg/proc/internal/ebpf/bpf/trace.bpf.c index d4b27760..584c71b9 100644 --- a/pkg/proc/internal/ebpf/bpf/trace.bpf.c +++ b/pkg/proc/internal/ebpf/bpf/trace.bpf.c @@ -219,6 +219,7 @@ int uprobe__dlv_trace(struct pt_regs *ctx) { parsed_args->fn_addr = args->fn_addr; parsed_args->n_parameters = args->n_parameters; parsed_args->n_ret_parameters = args->n_ret_parameters; + parsed_args->is_ret = args->is_ret; memcpy(parsed_args->params, args->params, sizeof(args->params)); memcpy(parsed_args->ret_params, args->ret_params, sizeof(args->ret_params)); diff --git a/pkg/proc/internal/ebpf/context.go b/pkg/proc/internal/ebpf/context.go index 695b8105..10ae4619 100644 --- a/pkg/proc/internal/ebpf/context.go +++ b/pkg/proc/internal/ebpf/context.go @@ -29,6 +29,7 @@ type RawUProbeParam struct { type RawUProbeParams struct { FnAddr int GoroutineID int + IsRet bool InputParams []*RawUProbeParam ReturnParams []*RawUProbeParam } diff --git a/pkg/proc/internal/ebpf/helpers.go b/pkg/proc/internal/ebpf/helpers.go index 669552e0..d9b5e7df 100644 --- a/pkg/proc/internal/ebpf/helpers.go +++ b/pkg/proc/internal/ebpf/helpers.go @@ -153,6 +153,7 @@ func parseFunctionParameterList(rawParamBytes []byte) RawUProbeParams { var rawParams RawUProbeParams rawParams.FnAddr = int(params.fn_addr) rawParams.GoroutineID = int(params.goroutine_id) + rawParams.IsRet = params.is_ret parseParam := func(param function_parameter_t) *RawUProbeParam { iparam := &RawUProbeParam{} diff --git a/pkg/proc/internal/ebpf/trace_bpfel_x86.o b/pkg/proc/internal/ebpf/trace_bpfel_x86.o index fe887d15..cadd8902 100644 Binary files a/pkg/proc/internal/ebpf/trace_bpfel_x86.o and b/pkg/proc/internal/ebpf/trace_bpfel_x86.o differ diff --git a/pkg/proc/target.go b/pkg/proc/target.go index c65de767..5dc5d2ef 100644 --- a/pkg/proc/target.go +++ b/pkg/proc/target.go @@ -436,6 +436,7 @@ func (t *Target) CurrentThread() Thread { type UProbeTraceResult struct { FnAddr int GoroutineID int + IsRet bool InputParams []*Variable ReturnParams []*Variable } @@ -465,6 +466,7 @@ func (t *Target) GetBufferedTracepoints() []*UProbeTraceResult { r := &UProbeTraceResult{} r.FnAddr = tp.FnAddr r.GoroutineID = tp.GoroutineID + r.IsRet = tp.IsRet for _, ip := range tp.InputParams { v := convertInputParamToVariable(ip) r.InputParams = append(r.InputParams, v) diff --git a/service/api/types.go b/service/api/types.go index 258e82ca..852a3e7e 100644 --- a/service/api/types.go +++ b/service/api/types.go @@ -57,7 +57,8 @@ type TracepointResult struct { // File is the source file for the breakpoint. File string `json:"file"` // Line is a line in File for the breakpoint. - Line int `json:"line"` + Line int `json:"line"` + IsRet bool `json:"is_ret"` // FunctionName is the name of the function at the current breakpoint, and // may not always be available. FunctionName string `json:"functionName,omitempty"` diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index 85d8b83d..5159ba35 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -2272,6 +2272,8 @@ func (d *Debugger) GetBufferedTracepoints() []api.TracepointResult { } results := make([]api.TracepointResult, len(traces)) for i, trace := range traces { + results[i].IsRet = trace.IsRet + f, l, fn := d.target.Selected.BinInfo().PCToLine(uint64(trace.FnAddr)) results[i].FunctionName = fn.Name