delve/vendor/github.com/goretk/gore/package.go
Derek Parker 6e8e1cee9b
pkg/proc: use gore to obtain info from stripped binaries (#3577)
This patch switches from using a forked version of one of the libraries
of goretk/gore to using the module directly. This is possible now that
certain functionality has been exposed / fixed within that module making
it usable for Delve.
2023-11-23 09:12:10 +01:00

301 lines
8.6 KiB
Go

// This file is part of GoRE.
//
// Copyright (C) 2019-2021 GoRE Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package gore
import (
"fmt"
"path/filepath"
"runtime/debug"
"sort"
"strings"
)
//go:generate go run gen.go
var (
knownRepos = []string{"golang.org", "github.com", "gitlab.com"}
)
// Package is a representation of a Go package.
type Package struct {
// Name is the name of the package.
Name string `json:"name"`
// Filepath is the extracted file path for the package.
Filepath string `json:"path"`
// Functions is a list of functions that are part of the package.
Functions []*Function `json:"functions"`
// Methods a list of methods that are part of the package.
Methods []*Method `json:"methods"`
}
// GetSourceFiles returns a slice of source files within the package.
// The source files are a representations of the source code files in the package.
func (f *GoFile) GetSourceFiles(p *Package) []*SourceFile {
tmp := make(map[string]*SourceFile)
getSourceFile := func(fileName string) *SourceFile {
sf, ok := tmp[fileName]
if !ok {
return &SourceFile{Name: filepath.Base(fileName)}
}
return sf
}
// Sort functions and methods by source file.
for _, fn := range p.Functions {
fileName, _, _ := f.pclntab.PCToLine(fn.Offset)
start, end := findSourceLines(fn.Offset, fn.End, f.pclntab)
e := FileEntry{Name: fn.Name, Start: start, End: end}
sf := getSourceFile(fileName)
sf.entries = append(sf.entries, e)
tmp[fileName] = sf
}
for _, m := range p.Methods {
fileName, _, _ := f.pclntab.PCToLine(m.Offset)
start, end := findSourceLines(m.Offset, m.End, f.pclntab)
e := FileEntry{Name: fmt.Sprintf("%s%s", m.Receiver, m.Name), Start: start, End: end}
sf := getSourceFile(fileName)
sf.entries = append(sf.entries, e)
tmp[fileName] = sf
}
// Create final slice and populate it.
files := make([]*SourceFile, len(tmp))
i := 0
for _, sf := range tmp {
files[i] = sf
i++
}
// Sort the file list.
sort.Slice(files, func(i, j int) bool {
return files[i].Name < files[j].Name
})
return files
}
// PackageClass is a type used to indicate the package kind.
type PackageClass uint8
const (
// ClassUnknown is used for packages that could not be classified.
ClassUnknown PackageClass = iota
// ClassSTD is used for packages that are part of the standard library.
ClassSTD
// ClassMain is used for the main package and its subpackages.
ClassMain
// ClassVendor is used for vendor packages.
ClassVendor
// ClassGenerated are used for packages generated by the compiler.
ClassGenerated
)
// PackageClassifier classifies a package to the correct class type.
type PackageClassifier interface {
// Classify performs the classification.
Classify(pkg *Package) PackageClass
}
// NewPathPackageClassifier constructs a new classifier based on the main package's filepath.
func NewPathPackageClassifier(mainPkgFilepath string) *PathPackageClassifier {
return &PathPackageClassifier{
mainFilepath: mainPkgFilepath, mainFolders: []string{
filepath.Dir(mainPkgFilepath),
mainPkgFilepath,
},
}
}
// PathPackageClassifier can classify the class of a go package.
type PathPackageClassifier struct {
mainFilepath string
mainFolders []string
}
// Classify returns the package class for the package.
func (c *PathPackageClassifier) Classify(pkg *Package) PackageClass {
if pkg.Name == "type" || strings.HasPrefix(pkg.Name, "type..") {
return ClassGenerated
}
if IsStandardLibrary(pkg.Name) {
return ClassSTD
}
if isGeneratedPackage(pkg) {
return ClassGenerated
}
// Detect internal/golang.org/x/net/http2/hpack type/
tmp := strings.Split(pkg.Name, "/golang.org")[0]
if len(tmp) < len(pkg.Name) && IsStandardLibrary(tmp) {
return ClassSTD
}
// cgo packages.
if strings.HasPrefix(pkg.Name, "_cgo_") || strings.HasPrefix(pkg.Name, "x_cgo_") {
return ClassSTD
}
// If the file path contains "@v", it's a 3rd party package.
if strings.Contains(pkg.Filepath, "@v") {
return ClassVendor
}
parentFolder := filepath.Dir(pkg.Filepath)
if strings.HasPrefix(pkg.Filepath, c.mainFilepath+"/vendor/") ||
strings.HasPrefix(pkg.Filepath, filepath.Dir(c.mainFilepath)+"/vendor/") ||
strings.HasPrefix(pkg.Filepath, filepath.Dir(filepath.Dir(c.mainFilepath))+"/vendor/") {
return ClassVendor
}
for _, folder := range c.mainFolders {
if parentFolder == folder {
return ClassMain
}
}
// If the package name starts with "vendor/" assume it's a vendor package.
if strings.HasPrefix(pkg.Name, "vendor/") {
return ClassVendor
}
// Start with repo url.and has it in the path.
for _, url := range knownRepos {
if strings.HasPrefix(pkg.Name, url) && strings.Contains(pkg.Filepath, url) {
return ClassVendor
}
}
// If the path does not contain the "vendor" in path but has the main package folder name, assume part of main.
if !strings.Contains(pkg.Filepath, "vendor/") &&
(filepath.Base(filepath.Dir(pkg.Filepath)) == filepath.Base(c.mainFilepath)) {
return ClassMain
}
// Special case for entry point package.
if pkg.Name == "" && filepath.Base(pkg.Filepath) == "runtime" {
return ClassSTD
}
// At this point, if it's a subpackage of the main assume main.
if strings.HasPrefix(pkg.Filepath, c.mainFilepath) {
return ClassMain
}
// Check if it's the main parent package.
if pkg.Name != "" && !strings.Contains(pkg.Name, "/") && strings.Contains(c.mainFilepath, pkg.Name) {
return ClassMain
}
// At this point, if the main package has a file path of "command-line-arguments" and we haven't figured out
// what class it is. We assume it being part of the main package.
if c.mainFilepath == "command-line-arguments" {
return ClassMain
}
return ClassUnknown
}
// IsStandardLibrary returns true if the package is from the standard library.
// Otherwise, false is retuned.
func IsStandardLibrary(pkg string) bool {
_, ok := stdPkgs[pkg]
return ok
}
func isGeneratedPackage(pkg *Package) bool {
// Detect regexp.(*onePassInst).regexp/syntax type packages
tmp := strings.Split(pkg.Name, ".")[0]
if len(tmp) < len(pkg.Name) && IsStandardLibrary(tmp) {
return true
}
// Special case for no package name and path of ".".
if pkg.Name == "" && pkg.Filepath == "." {
return true
}
// Some internal stuff, classify it as Generated
if pkg.Filepath == "." && (pkg.Name == "__x86" || pkg.Name == "__i686") {
return true
}
return false
}
// NewModPackageClassifier creates a new mod based package classifier.
func NewModPackageClassifier(buildInfo *debug.BuildInfo) *ModPackageClassifier {
return &ModPackageClassifier{modInfo: buildInfo}
}
// ModPackageClassifier uses the mod info extracted from the binary to classify packages.
type ModPackageClassifier struct {
modInfo *debug.BuildInfo
}
// Classify performs the classification.
func (c *ModPackageClassifier) Classify(pkg *Package) PackageClass {
if IsStandardLibrary(pkg.Name) {
return ClassSTD
}
// Main package.
if pkg.Name == "main" {
return ClassMain
}
// If the build info path is not an empty string and the package has the path as a substring, it is part of the main module.
if c.modInfo.Path != "" && (strings.HasPrefix(pkg.Filepath, c.modInfo.Path) || strings.HasPrefix(pkg.Name, c.modInfo.Path)) {
return ClassMain
}
// If the main module path is not an empty string and the package has the path as a substring, it is part of the main module.
if c.modInfo.Main.Path != "" && (strings.HasPrefix(pkg.Filepath, c.modInfo.Main.Path) || strings.HasPrefix(pkg.Name, c.modInfo.Main.Path)) {
return ClassMain
}
// Check if the package is a direct dependency.
for _, dep := range c.modInfo.Deps {
if strings.HasPrefix(pkg.Filepath, dep.Path) || strings.HasPrefix(pkg.Name, dep.Path) {
// If the vendor it matched on has the version of "(devel)", it is treated as part of
// the main module.
if dep.Version == "(devel)" {
return ClassMain
}
return ClassVendor
}
}
if isGeneratedPackage(pkg) {
return ClassGenerated
}
// cgo packages.
if strings.HasPrefix(pkg.Name, "_cgo_") || strings.HasPrefix(pkg.Name, "x_cgo_") {
return ClassSTD
}
// Only indirect dependencies should be left.
return ClassVendor
}