service,terminal,cmd/dlv: automatically guessing substitute-path config (#3781)

Add command, API calls and launch.json option to automatically guess
substitute-path configuration.
This commit is contained in:
Alessandro Arzilli
2024-10-31 18:19:08 +01:00
committed by GitHub
parent ac14553fda
commit 822014b8e8
20 changed files with 650 additions and 95 deletions

View File

@ -12,6 +12,7 @@ import (
"io"
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
"runtime"
@ -2551,3 +2552,157 @@ func (d *Debugger) maybePrintUnattendedBreakpointWarning(stopReason proc.StopRea
}
api.PrintStack(formatPathFunc, os.Stderr, apiFrames, "", false, api.StackTraceColors{}, includeFunc)
}
// GuessSubstitutePath returns a substitute-path configuration that maps
// server paths to client paths by examining the executable file and a map
// of module paths to client directories (clientMod2Dir) passed as input.
func (d *Debugger) GuessSubstitutePath(args *api.GuessSubstitutePathIn) map[string]string {
bis := []*proc.BinaryInfo{}
bins := [][]proc.Function{}
tgt := proc.ValidTargets{Group: d.target}
for tgt.Next() {
bi := tgt.BinInfo()
bis = append(bis, bi)
bins = append(bins, bi.Functions)
}
return guessSubstitutePath(args, bins, func(biIdx int, fn *proc.Function) string {
file, _ := bis[biIdx].EntryLineForFunc(fn)
return file
})
}
func guessSubstitutePath(args *api.GuessSubstitutePathIn, bins [][]proc.Function, fileForFunc func(int, *proc.Function) string) map[string]string {
serverMod2Dir := map[string]string{}
serverMod2DirCandidate := map[string]map[string]int{}
pkg2mod := map[string]string{}
for mod := range args.ClientModuleDirectories {
serverMod2DirCandidate[mod] = make(map[string]int)
}
const minEvidence = 10
const decisionThreshold = 0.8
totCandidates := func(mod string) int {
r := 0
for _, cnt := range serverMod2DirCandidate[mod] {
r += cnt
}
return r
}
bestCandidate := func(mod string) string {
best := ""
for dir, cnt := range serverMod2DirCandidate[mod] {
if cnt > serverMod2DirCandidate[mod][best] {
best = dir
}
}
return best
}
slashes := func(s string) int {
r := 0
for _, ch := range s {
if ch == '/' {
r++
}
}
return r
}
serverGoroot := ""
logger := logflags.DebuggerLogger()
for binIdx, bin := range bins {
for i := range bin {
fn := &bin[i]
if fn.Name == "runtime.main" && serverGoroot == "" {
file := fileForFunc(binIdx, fn)
serverGoroot = path.Dir(path.Dir(path.Dir(file)))
continue
}
fnpkg := fn.PackageName()
if fn.CompilationUnitName() != "" && strings.ReplaceAll(fn.CompilationUnitName(), "\\", "/") != fnpkg {
// inlined
continue
}
if fnpkg == "main" && binIdx == 0 && args.ImportPathOfMainPackage != "" {
fnpkg = args.ImportPathOfMainPackage
}
fnmod := ""
if mod, ok := pkg2mod[fnpkg]; ok {
fnmod = mod
} else {
for mod := range args.ClientModuleDirectories {
if strings.HasPrefix(fnpkg, mod) {
fnmod = mod
break
}
}
pkg2mod[fnpkg] = fnmod
if fnmod == "" {
logger.Debugf("No module detected for server package %q", fnpkg)
}
}
if fnmod == "" {
// not in any module we are interested in
continue
}
if serverMod2Dir[fnmod] != "" {
// already decided
continue
}
elems := slashes(fnpkg[len(fnmod):])
file := fileForFunc(binIdx, fn)
if file == "" || file == "<autogenerated>" {
continue
}
logger.Debugf("considering %s pkg:%s compile unit:%s file:%s", fn.Name, fnpkg, fn.CompilationUnitName(), file)
dir := path.Dir(file) // note: paths are normalized to always use '/' as a separator by pkg/dwarf/line
if slashes(dir) < elems {
continue
}
for i := 0; i < elems; i++ {
dir = path.Dir(dir)
}
serverMod2DirCandidate[fnmod][dir]++
n := totCandidates(fnmod)
best := bestCandidate(fnmod)
if n > minEvidence && float64(serverMod2DirCandidate[fnmod][best])/float64(n) > decisionThreshold {
serverMod2Dir[fnmod] = best
}
}
}
for mod := range args.ClientModuleDirectories {
if serverMod2Dir[mod] == "" {
serverMod2Dir[mod] = bestCandidate(mod)
}
}
server2Client := make(map[string]string)
for mod, clientDir := range args.ClientModuleDirectories {
if serverMod2Dir[mod] != "" {
server2Client[serverMod2Dir[mod]] = clientDir
}
}
if serverGoroot != "" && args.ClientGOROOT != "" {
server2Client[serverGoroot] = args.ClientGOROOT
}
return server2Client
}