proc,service: return build informations for each package
Adds an API call that returns a list of packages contained in the program and the files that were used to build them, and also a best guess at which filesystem directory contained the package when it was built. This can be used by IDEs to map file paths if the debugging environment doesn't match the build environment exactly.
This commit is contained in:
parent
0e0d689246
commit
a8606afb0b
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user