diff --git a/Documentation/cli/starlark.md b/Documentation/cli/starlark.md index f163eac1..6e173f30 100644 --- a/Documentation/cli/starlark.md +++ b/Documentation/cli/starlark.md @@ -43,6 +43,7 @@ functions(Filter) | Equivalent to API call [ListFunctions](https://godoc.org/git goroutines(Start, Count) | Equivalent to API call [ListGoroutines](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListGoroutines) local_vars(Scope, Cfg) | Equivalent to API call [ListLocalVars](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListLocalVars) package_vars(Filter, Cfg) | Equivalent to API call [ListPackageVars](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListPackageVars) +packages_build_info(IncludeFiles) | Equivalent to API call [ListPackagesBuildInfo](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListPackagesBuildInfo) registers(ThreadID, IncludeFp) | Equivalent to API call [ListRegisters](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListRegisters) sources(Filter) | Equivalent to API call [ListSources](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListSources) threads() | Equivalent to API call [ListThreads](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListThreads) diff --git a/pkg/dwarf/line/state_machine.go b/pkg/dwarf/line/state_machine.go index d83d1422..cb041544 100644 --- a/pkg/dwarf/line/state_machine.go +++ b/pkg/dwarf/line/state_machine.go @@ -385,13 +385,28 @@ func (lineInfo *DebugLineInfo) FirstStmtForLine(start, end uint64) (pc uint64, f } if err := sm.next(); err != nil { if lineInfo.Logf != nil { - lineInfo.Logf("StmtAfter error: %v", err) + lineInfo.Logf("FirstStmtForLine error: %v", err) } return 0, "", 0, false } } } +func (lineInfo *DebugLineInfo) FirstFile() string { + sm := newStateMachine(lineInfo, lineInfo.Instructions) + for { + if sm.valid { + return sm.file + } + if err := sm.next(); err != nil { + if lineInfo.Logf != nil { + lineInfo.Logf("FirstFile error: %v", err) + } + return "" + } + } +} + func (sm *StateMachine) next() error { sm.started = true if sm.valid { diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go index 4cf1fd62..732505aa 100644 --- a/pkg/proc/bininfo.go +++ b/pkg/proc/bininfo.go @@ -351,7 +351,7 @@ func (bi *BinaryInfo) LineToPC(filename string, lineno int) (pcs []uint64, err e fileFound := false var pc uint64 for _, cu := range bi.compileUnits { - if cu.lineInfo.Lookup[filename] == nil { + if cu.lineInfo != nil && cu.lineInfo.Lookup[filename] == nil { continue } fileFound = true @@ -411,7 +411,7 @@ func (bi *BinaryInfo) AllPCsForFileLines(filename string, linenos []int) map[int r[line] = make([]uint64, 0, 1) } for _, cu := range bi.compileUnits { - if cu.lineInfo.Lookup[filename] != nil { + if cu.lineInfo != nil && cu.lineInfo.Lookup[filename] != nil { cu.lineInfo.AllPCsForFileLines(filename, r) } } @@ -1317,8 +1317,8 @@ func (bi *BinaryInfo) loadDebugInfoMaps(image *Image, debugLineBytes []byte, wg if len(cu.ranges) >= 1 { cu.lowPC = cu.ranges[0][0] } - lineInfoOffset, _ := entry.Val(dwarf.AttrStmtList).(int64) - if lineInfoOffset >= 0 && lineInfoOffset < int64(len(debugLineBytes)) { + lineInfoOffset, hasLineInfo := entry.Val(dwarf.AttrStmtList).(int64) + if hasLineInfo && lineInfoOffset >= 0 && lineInfoOffset < int64(len(debugLineBytes)) { var logfn func(string, ...interface{}) if logflags.DebugLineErrors() { logger := logrus.New().WithFields(logrus.Fields{"layer": "dwarf-line"}) @@ -1756,3 +1756,52 @@ func (bi *BinaryInfo) symLookup(addr uint64) (string, uint64) { } return "", 0 } + +type PackageBuildInfo struct { + ImportPath string + DirectoryPath string + Files map[string]struct{} +} + +// ListPackagesBuildInfo returns the list of packages used by the program along with +// the directory where each package was compiled and optionally the list of +// files constituting the package. +func (bi *BinaryInfo) ListPackagesBuildInfo(includeFiles bool) []*PackageBuildInfo { + m := make(map[string]*PackageBuildInfo) + for _, cu := range bi.compileUnits { + if cu.image != bi.Images[0] || !cu.isgo || cu.lineInfo == nil { + //TODO(aarzilli): what's the correct thing to do for plugins? + continue + } + + ip := strings.Replace(cu.name, "\\", "/", -1) + if _, ok := m[ip]; !ok { + path := cu.lineInfo.FirstFile() + if ext := filepath.Ext(path); ext != ".go" && ext != ".s" { + continue + } + dp := filepath.Dir(path) + m[ip] = &PackageBuildInfo{ + ImportPath: ip, + DirectoryPath: dp, + Files: make(map[string]struct{}), + } + } + + if includeFiles { + pbi := m[ip] + + for _, file := range cu.lineInfo.FileNames { + pbi.Files[file.Path] = struct{}{} + } + } + } + + r := make([]*PackageBuildInfo, 0, len(m)) + for _, pbi := range m { + r = append(r, pbi) + } + + sort.Slice(r, func(i, j int) bool { return r[i].ImportPath < r[j].ImportPath }) + return r +} diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index bd8c2ccd..4f9282a1 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -4525,3 +4525,24 @@ func TestIssue1817(t *testing.T) { setFileBreakpoint(p, t, fixture.Source, 16) }) } + +func TestListPackagesBuildInfo(t *testing.T) { + withTestProcess("pkgrenames", t, func(p proc.Process, fixture protest.Fixture) { + pkgs := p.BinInfo().ListPackagesBuildInfo(true) + t.Logf("returned %d", len(pkgs)) + if len(pkgs) < 10 { + t.Errorf("very few packages returned") + } + for _, pkg := range pkgs { + t.Logf("%q %q", pkg.ImportPath, pkg.DirectoryPath) + const _fixtures = "_fixtures" + fidx := strings.Index(pkg.ImportPath, _fixtures) + if fidx < 0 { + continue + } + if !strings.HasSuffix(strings.Replace(pkg.DirectoryPath, "\\", "/", -1), pkg.ImportPath[fidx:]) { + t.Errorf("unexpected suffix: %q %q", pkg.ImportPath, pkg.DirectoryPath) + } + } + }) +} diff --git a/pkg/terminal/starbind/starlark_mapping.go b/pkg/terminal/starbind/starlark_mapping.go index 3cae3aca..f00059a4 100644 --- a/pkg/terminal/starbind/starlark_mapping.go +++ b/pkg/terminal/starbind/starlark_mapping.go @@ -848,6 +848,36 @@ func (env *Env) starlarkPredeclare() starlark.StringDict { } return env.interfaceToStarlarkValue(rpcRet), nil }) + r["packages_build_info"] = starlark.NewBuiltin("packages_build_info", func(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { + if err := isCancelled(thread); err != nil { + return starlark.None, decorateError(thread, err) + } + var rpcArgs rpc2.ListPackagesBuildInfoIn + var rpcRet rpc2.ListPackagesBuildInfoOut + if len(args) > 0 && args[0] != starlark.None { + err := unmarshalStarlarkValue(args[0], &rpcArgs.IncludeFiles, "IncludeFiles") + if err != nil { + return starlark.None, decorateError(thread, err) + } + } + for _, kv := range kwargs { + var err error + switch kv[0].(starlark.String) { + case "IncludeFiles": + err = unmarshalStarlarkValue(kv[1], &rpcArgs.IncludeFiles, "IncludeFiles") + default: + err = fmt.Errorf("unknown argument %q", kv[0]) + } + if err != nil { + return starlark.None, decorateError(thread, err) + } + } + err := env.ctx.Client().CallAPI("ListPackagesBuildInfo", &rpcArgs, &rpcRet) + if err != nil { + return starlark.None, err + } + return env.interfaceToStarlarkValue(rpcRet), nil + }) r["registers"] = starlark.NewBuiltin("registers", func(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { if err := isCancelled(thread); err != nil { return starlark.None, decorateError(thread, err) diff --git a/service/api/types.go b/service/api/types.go index 33e0dffa..db06c7b8 100644 --- a/service/api/types.go +++ b/service/api/types.go @@ -509,3 +509,10 @@ const ( // values saved in the runtime.g structure. StacktraceG ) + +// ImportPathToDirectoryPath maps an import path to a directory path. +type PackageBuildInfo struct { + ImportPath string + DirectoryPath string + Files []string +} diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index 7eafe5d7..56ec1645 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -1337,6 +1337,35 @@ func (d *Debugger) GetVersion(out *api.GetVersionOut) error { return nil } +// ListPackagesBuildInfo returns the list of packages used by the program along with +// the directory where each package was compiled and optionally the list of +// files constituting the package. +func (d *Debugger) ListPackagesBuildInfo(includeFiles bool) []api.PackageBuildInfo { + d.processMutex.Lock() + defer d.processMutex.Unlock() + pkgs := d.target.BinInfo().ListPackagesBuildInfo(includeFiles) + r := make([]api.PackageBuildInfo, 0, len(pkgs)) + for _, pkg := range pkgs { + var files []string + + if len(pkg.Files) > 0 { + files = make([]string, 0, len(pkg.Files)) + for file := range pkg.Files { + files = append(files, file) + } + } + + sort.Strings(files) + + r = append(r, api.PackageBuildInfo{ + ImportPath: pkg.ImportPath, + DirectoryPath: pkg.DirectoryPath, + Files: files, + }) + } + return r +} + func go11DecodeErrorCheck(err error) error { if _, isdecodeerr := err.(dwarf.DecodeError); !isdecodeerr { return err diff --git a/service/rpc2/server.go b/service/rpc2/server.go index 730f1e9c..68b84c6d 100644 --- a/service/rpc2/server.go +++ b/service/rpc2/server.go @@ -731,3 +731,23 @@ func (s *RPCServer) ListDynamicLibraries(in ListDynamicLibrariesIn, out *ListDyn out.List = s.debugger.ListDynamicLibraries() return nil } + +// ListPackagesBuildInfoIn holds the arguments of ListPackages. +type ListPackagesBuildInfoIn struct { + IncludeFiles bool +} + +// ListPackagesBuildInfoOut holds the return values of ListPackages. +type ListPackagesBuildInfoOut struct { + List []api.PackageBuildInfo +} + +// ListPackagesBuildInfo returns the list of packages used by the program along with +// the directory where each package was compiled and optionally the list of +// files constituting the package. +// Note that the directory path is a best guess and may be wrong is a tool +// other than cmd/go is used to perform the build. +func (s *RPCServer) ListPackagesBuildInfo(in ListPackagesBuildInfoIn, out *ListPackagesBuildInfoOut) error { + out.List = s.debugger.ListPackagesBuildInfo(in.IncludeFiles) + return nil +}