Skip to content

Commit

Permalink
Implemented traversal algorithm using breadth first search
Browse files Browse the repository at this point in the history
  • Loading branch information
archanaravindar committed May 24, 2024
1 parent 72afa6e commit 38e26eb
Showing 1 changed file with 109 additions and 102 deletions.
211 changes: 109 additions & 102 deletions service/debugger/debugger.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package debugger

import (
"container/list"
"debug/dwarf"
"debug/elf"
"debug/macho"
Expand Down Expand Up @@ -494,6 +495,7 @@ func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs []

if !resetArgs && (d.config.Stdout.File != nil || d.config.Stderr.File != nil) {
return nil, ErrCanNotRestart

}

if err := d.detach(true); err != nil {
Expand Down Expand Up @@ -1487,16 +1489,14 @@ func (d *Debugger) Functions(filter string, followCalls int) ([]string, error) {

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
Func *proc.Function
// To keep track of depth in callgraph relative to root function
Depth int
// To guard against adding duplicates to list of functions to be traced
visited bool
// To avoid disassembling function multiple times
dis bool
// To quickly access child functions
children []*proc.Function
}
type TraceFuncptr *TraceFunc
Expand All @@ -1509,128 +1509,134 @@ func filter(fname string) bool {
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")
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
// Except certain special functions do not traverse runtime.* functions
return false
}
return true
}
func traverse(t proc.ValidTargets, f *proc.Function, depth int, FollowCalls int) ([]string, error) {

if !filter(f.Name) {
return nil, nil
func dequeue(queue *list.List) TraceFuncptr {
element := queue.Front()
queue.Remove(element)
data := element.Value
F, ok := data.(TraceFuncptr)
if !ok {
fmt.Printf("Type error\n")
os.Exit(1)
}
return F
}

func traverse(t proc.ValidTargets, f *proc.Function, depth int, FollowCalls int) ([]string, error) {

var queue *list.List
queue = list.New()
queue.Init()

funcs := []string{}
if depth > FollowCalls {
return nil, nil
}

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
}
if !mapnode.visited {
// Condition to avoid adding duplicates to funcs list
funcs = append(funcs, f.Name)
mapnode.visited = true
}
rootnode := &TraceFunc{Func: new(proc.Function), Depth: depth, children: []*proc.Function{}, visited: false, dis: false}
rootnode.Func = f

// 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 {
// 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)
TraceMap[f.Name] = rootnode
queue.PushBack(TraceMap[f.Name])
for queue.Len() > 0 {
parent := dequeue(queue)
if parent.Depth > FollowCalls {
continue
}
for _, instr := range text {
if instr.IsCall() && instr.DestLoc != nil && instr.DestLoc.Fn != nil {
cf := instr.DestLoc.Fn
if !filter(cf.Name) {
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
if !parent.visited {
// Guard to avoid adding duplicates to funcs
funcs = append(funcs, parent.Func.Name)
parent.visited = true
}
if parent.Depth+1 > FollowCalls {
// Skip disassembling and computing children if we are going to cross FollowCalls
continue
}
deferdepth := 0
var deferparent TraceFuncptr = nil
if !parent.dis {
f := parent.Func
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) {
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 := TraceMap[cf.Name]
if childnode == nil {
childnode = &TraceFunc{Func: new(proc.Function), Depth: parent.Depth + 1, children: []*proc.Function{}, visited: false, dis: false}
childnode.Func = cf
TraceMap[cf.Name] = childnode
parent.children = append(parent.children, cf)
} else {
if childnode.Depth > parent.Depth+1 {
// can go even further deep
childnode.Depth = parent.Depth + 1
parent.children = append(parent.children, cf)
}
childnode.Weight = Budget
TraceMap[f.Name].children = append(TraceMap[f.Name].children, cf)
}
}

if strings.Contains(cf.Name, "runtime.deferreturn") {
if childnode.Depth+1 <= FollowCalls {
// flag to track defer functions
deferdepth = childnode.Depth + 1
if strings.Contains(cf.Name, "runtime.deferreturn") {
if deferparent == nil && childnode.Depth+1 <= FollowCalls {
// keep track of defer function inorder to trace defer calls
deferdepth = childnode.Depth
deferparent = parent
}
}

}
}
parent.dis = true
}
TraceMap[f.Name].cached = true
}
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
for _, cf := range parent.children {
if cf.Name == parent.Func.Name {
//recursive functions
continue
}
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)
childnode := TraceMap[cf.Name]
if childnode == nil {
// childnodes should have already been computed in the above loop
fmt.Printf("unexpected error child nil \n")
os.Exit(1)
}
if childnode.Depth < parent.Depth+1 {
// This path has already been explored no need to revisit
continue
}
if childnode.Depth <= FollowCalls {
queue.PushBack(TraceMap[cf.Name])
}
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)
if deferdepth > 0 {
for _, fbinary := range t.BinInfo().Functions {
if deferdepth <= FollowCalls && strings.HasPrefix(fbinary.Name, deferparent.Func.Name) && fbinary.Name != deferparent.Func.Name {
if filter(fbinary.Name) {
defernode := TraceMap[fbinary.Name]
if defernode == nil {
defernode = &TraceFunc{Func: new(proc.Function), Depth: deferdepth, children: []*proc.Function{}, visited: false, dis: false}
*(defernode.Func) = fbinary
TraceMap[fbinary.Name] = defernode
} else if defernode.visited {
continue
}
// Enqueueing defernode to traverse deferred function call
queue.PushBack(defernode)
}
}
funcs = append(funcs, children...)
}
deferdepth = 0
}
deferdepth = 0
}
return funcs, nil

}

// Types returns all type information in the binary.
Expand Down Expand Up @@ -2077,6 +2083,7 @@ func (d *Debugger) convertDefers(defers []*proc.Defer) []api.Defer {
SP: defers[i].SP,
}
}

}

return r
Expand Down

0 comments on commit 38e26eb

Please sign in to comment.