Skip to content

Commit

Permalink
Optimize traversal and parts of printing trace point function and mod…
Browse files Browse the repository at this point in the history
…ify trace output layout

and adjust tests accordingly
  • Loading branch information
archanaravindar committed Apr 25, 2024
1 parent 0751612 commit 1adec73
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 69 deletions.
42 changes: 1 addition & 41 deletions cmd/dlv/dlv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -944,7 +944,7 @@ func TestTrace2(t *testing.T) {
func TestTraceDirRecursion(t *testing.T) {
dlvbin := getDlvBin(t)

expected := []byte("1> goroutine(1): main.A(5, 5)\n 2> goroutine(1): main.A(4, 4)\n 3> goroutine(1): main.A(3, 3)\n 4> goroutine(1): main.A(2, 2)\n 4>> goroutine(1):(main.A) => (2)\n 3>> goroutine(1):(main.A) => (6)\n 2>> goroutine(1):(main.A) => (24)\n1>> goroutine(1):(main.A) => (120)\n")
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")

fixtures := protest.FindFixturesDir()
cmd := exec.Command(dlvbin, "trace", "--output", filepath.Join(t.TempDir(), "__debug"), filepath.Join(fixtures, "leafrec.go"), "main.A", "--follow-calls", "4")
Expand Down Expand Up @@ -974,46 +974,6 @@ func TestTraceDirRecursion(t *testing.T) {
assertNoError(cmd.Wait(), t, "cmd.Wait()")
}

func TestTracePanicDefer(t *testing.T) {
dlvbin := getDlvBin(t)

expected1 := []byte("1> goroutine(1): main.F0()\n 2> goroutine(1): runtime.deferprocStack")
expected2 := []byte(" 2>> goroutine(1):(runtime.deferprocStack) => ()\n 2> goroutine(1): main.F1()\n 3> goroutine(1): main.F2()\n 4> goroutine(1): main.F3()\n1>> goroutine(1):(main.F0) => ()\n 2> goroutine(1): runtime.deferreturn()\n 2>> goroutine(1):(runtime.deferreturn) => ()\n1>> goroutine(1):(main.F0) => ()\n")

fixtures := protest.FindFixturesDir()
cmd := exec.Command(dlvbin, "trace", "--output", filepath.Join(t.TempDir(), "__debug"), filepath.Join(fixtures, "panicex.go"), "main.F0", "--follow-calls", "4")
rdr, err := cmd.StderrPipe()
assertNoError(err, t, "stderr pipe")
defer rdr.Close()

cmd.Dir = filepath.Join(fixtures, "buildtest")

assertNoError(cmd.Start(), t, "running trace")

// Parse output to ignore calls to morestack_noctxt for comparison
scan := bufio.NewScanner(rdr)
text := ""
outputtext := ""
for scan.Scan() {
text = scan.Text()
if !strings.Contains(text, "morestack_noctxt") {
outputtext += text
outputtext += "\n"
}
}
output := []byte(outputtext)

// Split expected into two to work around comparison of actual address parameter in deferprocStack
if !bytes.Contains(output, expected1) {
t.Fatalf("expected:\n%s\ngot:\n%s", string(expected1), string(output))
}
if !bytes.Contains(output, expected2) {
t.Fatalf("expected:\n%s\ngot:\n%s", string(expected2), string(output))
}
assertNoError(cmd.Wait(), t, "cmd.Wait()")

}

func TestTraceMultipleGoroutines(t *testing.T) {
dlvbin := getDlvBin(t)

Expand Down
153 changes: 125 additions & 28 deletions service/debugger/debugger.go
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,6 @@ func (d *Debugger) FunctionReturnLocations(fnName string) ([]uint64, error) {
for _, instruction := range instructions {
if instruction.IsRet() {
addrs = append(addrs, instruction.Loc.PC)
//fmt.Printf("appending PC %x to return breakpoint list\n",instruction.Loc.PC)
}
}
addrs = append(addrs, proc.FindDeferReturnCalls(instructions)...)
Expand Down Expand Up @@ -1487,52 +1486,150 @@ func (d *Debugger) Functions(filter string, followCalls int) ([]string, error) {
return funcs, nil
}

type TraceFunc struct {
// Function to be traced
Func *proc.Function
// To keep track of depth in call graph relative to root function
Depth int
// To ensure we traverse only if needed
Weight int
// To ensure we dont repeatedly add the same function to the function list
visited bool
// To ensure we dont repeatedly disassemble the same function
cached bool
// To keep track of child functions
children []*proc.Function
}
type TraceFuncptr *TraceFunc

var TraceMap = make(map[string]TraceFuncptr)

func filter(fname string) bool {
idx := strings.Index(fname, "runtime.")
idx2 := strings.Index(fname, "runtime.defer")
idx3 := strings.Index(fname, "runtime.gorecover")
idx4 := strings.Index(fname, "runtime.gopanic")
// Avoid runtime/internal functions to figure in the trace function list as they dont have a stack associated
idx5 := strings.Index(fname, "runtime/internal")
if (idx != -1 || idx5 != -1) && idx2 == -1 && idx3 == -1 && idx4 == -1 {
// Except few select functions do not traverse runtime.* functions
return false
}
return true
}
func traverse(t proc.ValidTargets, f *proc.Function, depth int, FollowCalls int) ([]string, error) {

idx := strings.Index(f.Name, "runtime.")
idx2 := strings.Index(f.Name, "runtime.defer")
if idx != -1 && idx2 == -1 {
// Except defer functions do not traverse runtime.* functions
if filter(f.Name) == false {
return nil, nil
}

funcs := []string{}
if depth > FollowCalls {
// If we already reached desired depth, return
return nil, nil
}
funcs := []string{}
funcs = append(funcs, f.Name)
text, err := proc.Disassemble(t.Memory(), nil, t.Breakpoints(), t.BinInfo(), f.Entry, f.End)
if err != nil {
return nil, fmt.Errorf("disassemble failed with error %w", err)

mapnode := TraceMap[f.Name]
if mapnode == nil {
mapnode = &TraceFunc{Func: new(proc.Function), Weight: FollowCalls - depth, Depth: depth, children: []*proc.Function{}, visited: false, cached: false}
mapnode.Func = f
TraceMap[f.Name] = mapnode
}
depth++
for _, instr := range text {
if instr.IsCall() && instr.DestLoc != nil && instr.DestLoc.Fn != nil {
cf := instr.DestLoc.Fn
if cf.Name == f.Name {
continue
}
if depth <= FollowCalls {
children, err := traverse(t, cf, depth, FollowCalls)
if err != nil {
return nil, fmt.Errorf("traverse failed with error %w", err)
if mapnode.visited == false {
// Condition to avoid adding duplicates to funcs list
funcs = append(funcs, f.Name)
mapnode.visited = true
}

// no point in disassembling if we are not traversing child
if depth+1 > FollowCalls {
return funcs, nil
}
Budget := FollowCalls - (depth + 1)
deferdepth := 0
if TraceMap[f.Name].cached == false {
// Condition to avoid repeated disassembly of the same functions
text, err := proc.Disassemble(t.Memory(), nil, t.Breakpoints(), t.BinInfo(), f.Entry, f.End)
if err != nil {
return nil, fmt.Errorf("disassemble failed with error %w", err)
}
for _, instr := range text {
if instr.IsCall() && instr.DestLoc != nil && instr.DestLoc.Fn != nil {
cf := instr.DestLoc.Fn
if filter(cf.Name) == false {
continue
}
childnode := TraceMap[cf.Name]
if childnode == nil {
childnode = &TraceFunc{Func: new(proc.Function), Weight: Budget, Depth: mapnode.Depth + 1, children: []*proc.Function{}, visited: false, cached: false}
childnode.Func = cf
TraceMap[cf.Name] = childnode
TraceMap[f.Name].children = append(TraceMap[f.Name].children, cf)
} else {
if childnode.Weight > Budget || childnode.Depth < mapnode.Depth+1 {
// No more budget left for traversal, go to the next child
continue
}
if Budget > childnode.Weight {
// We have more budget left, can traverse more
if childnode.Depth > mapnode.Depth+1 {
childnode.Depth = mapnode.Depth + 1
}
childnode.Weight = Budget
TraceMap[f.Name].children = append(TraceMap[f.Name].children, cf)
}
}

if strings.Index(cf.Name, "runtime.deferreturn") != -1 {
if childnode.Depth+1 <= FollowCalls {
// flag to track defer functions
deferdepth = childnode.Depth + 1
}
}
funcs = append(funcs, children...)
}
}
TraceMap[f.Name].cached = true
}
// The following code is needed to include defer function calls as they are invoked via funcname.func1 type
// naming, so check if funcname is a prefix in candidate function to include such functions
for _, fbinary := range t.BinInfo().Functions {
if strings.HasPrefix(fbinary.Name, f.Name) && fbinary.Name != f.Name {
children, err := traverse(t, &fbinary, depth, FollowCalls)
for _, childFunc := range TraceMap[f.Name].children {
if childFunc.Name == f.Name {
// Avoid recursion
continue
}
childnode := TraceMap[childFunc.Name]
if childnode.Weight > Budget || childnode.Depth < mapnode.Depth+1 {
// No more budget left for traversal, go to the next child
continue
}
if Budget > childnode.Weight {
// We have more budget left, can traverse more
if childnode.Depth > mapnode.Depth+1 {
childnode.Depth = mapnode.Depth + 1
}
childnode.Weight = Budget

}
if childnode.Depth <= FollowCalls {
children, err := traverse(t, childnode.Func, childnode.Depth, FollowCalls)
if err != nil {
return nil, fmt.Errorf("traverse failed with error %w", err)
}
funcs = append(funcs, children...)
}
}

// The following code is needed to include defer function calls as they are invoked via funcname.func1 type
// naming, so check if funcname is a prefix in candidate function to include such functions
if deferdepth > 0 {
for _, fbinary := range t.BinInfo().Functions {
if depth <= FollowCalls && strings.HasPrefix(fbinary.Name, f.Name) && fbinary.Name != f.Name {
children, err := traverse(t, &fbinary, deferdepth, FollowCalls)
if err != nil {
return nil, fmt.Errorf("traverse failed with error %w", err)
}
funcs = append(funcs, children...)
}
}
deferdepth = 0
}
return funcs, nil

}
Expand Down
6 changes: 6 additions & 0 deletions service/test/integration2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,12 @@ func TestTraceFollowCallsCommand(t *testing.T) {
assertNoError(err, t, "ListFunctions()")
expected = []string{"fmt.Printf", "main.callA", "main.callB", "main.callC", "main.callC.func1", "main.callD", "runtime.deferprocStack", "runtime.deferreturn"}
matchFunctions(t, functions, expected, depth)

depth = 6
functions, err = c.ListFunctions("main.F0", depth)
assertNoError(err, t, "ListFunctions()")
expected = []string{"main.F0", "main.F0.func1", "main.F1", "main.F2", "main.F3", "main.F4", "runtime.deferprocStack", "runtime.deferreturn", "runtime.gopanic", "runtime.gorecover"}
matchFunctions(t, functions, expected, depth)
})
}

Expand Down

0 comments on commit 1adec73

Please sign in to comment.