aboutsummaryrefslogtreecommitdiff
path: root/src/runtime/debug
diff options
context:
space:
mode:
authorJay Conrod <jayconrod@google.com>2021-09-30 10:25:49 -0700
committerJay Conrod <jayconrod@google.com>2021-10-14 18:44:08 +0000
commit434cdd0337b9e6c7e0c369c9293cc14fd38dc80d (patch)
treec3bed1de3166cdfbd3cbb7ea86858c62a5a8945d /src/runtime/debug
parent765c9116be44641854f580c19e3589d7b86a3d28 (diff)
downloadgo-434cdd0337b9e6c7e0c369c9293cc14fd38dc80d.tar.xz
debug/buildinfo: new package with Read and ReadFile
These functions provide access to module information stamped into Go binaries. In the future, they'll provide access to other information (like VCS info). These functions are added in a new package instead of runtime/debug since they use binary parsing packages like debug/elf, which would make runtime/debug an unacceptably heavy dependency. The types in runtime/debug are still used; debug/buildinfo uses them via type aliases. This information is already available for the running binary through debug.ReadBuildInfo and for other binaries with 'go version -m', but until now, there hasn't been a way to get it for other binaries without installing cmd/go. This change copies most of the code in cmd/go/internal/version. A later CL will migrate 'go version -m' to use this package. For #37475 Fixes #39301 Change-Id: I0fbe0896e04f12ef81c6d79fb61b20daede86159 Reviewed-on: https://go-review.googlesource.com/c/go/+/353887 Trust: Jay Conrod <jayconrod@google.com> Run-TryBot: Jay Conrod <jayconrod@google.com> Reviewed-by: Bryan C. Mills <bcmills@google.com>
Diffstat (limited to 'src/runtime/debug')
-rw-r--r--src/runtime/debug/mod.go104
1 files changed, 58 insertions, 46 deletions
diff --git a/src/runtime/debug/mod.go b/src/runtime/debug/mod.go
index 11f995ba75..8c6c48089b 100644
--- a/src/runtime/debug/mod.go
+++ b/src/runtime/debug/mod.go
@@ -7,7 +7,6 @@ package debug
import (
"bytes"
"fmt"
- "strings"
)
// exported from runtime
@@ -17,11 +16,19 @@ func modinfo() string
// in the running binary. The information is available only
// in binaries built with module support.
func ReadBuildInfo() (info *BuildInfo, ok bool) {
- return readBuildInfo(modinfo())
+ data := modinfo()
+ if len(data) < 32 {
+ return nil, false
+ }
+ data = data[16 : len(data)-16]
+ bi := &BuildInfo{}
+ if err := bi.UnmarshalText([]byte(data)); err != nil {
+ return nil, false
+ }
+ return bi, true
}
-// BuildInfo represents the build information read from
-// the running binary.
+// BuildInfo represents the build information read from a Go binary.
type BuildInfo struct {
Path string // The main package path
Main Module // The module containing the main package
@@ -71,80 +78,85 @@ func (bi *BuildInfo) MarshalText() ([]byte, error) {
return buf.Bytes(), nil
}
-func readBuildInfo(data string) (*BuildInfo, bool) {
- if len(data) < 32 {
- return nil, false
- }
- data = data[16 : len(data)-16]
+func (bi *BuildInfo) UnmarshalText(data []byte) (err error) {
+ *bi = BuildInfo{}
+ lineNum := 1
+ defer func() {
+ if err != nil {
+ err = fmt.Errorf("could not parse Go build info: line %d: %w", lineNum, err)
+ }
+ }()
- const (
- pathLine = "path\t"
- modLine = "mod\t"
- depLine = "dep\t"
- repLine = "=>\t"
+ var (
+ pathLine = []byte("path\t")
+ modLine = []byte("mod\t")
+ depLine = []byte("dep\t")
+ repLine = []byte("=>\t")
+ newline = []byte("\n")
+ tab = []byte("\t")
)
- readEntryFirstLine := func(elem []string) (Module, bool) {
+ readModuleLine := func(elem [][]byte) (Module, error) {
if len(elem) != 2 && len(elem) != 3 {
- return Module{}, false
+ return Module{}, fmt.Errorf("expected 2 or 3 columns; got %d", len(elem))
}
sum := ""
if len(elem) == 3 {
- sum = elem[2]
+ sum = string(elem[2])
}
return Module{
- Path: elem[0],
- Version: elem[1],
+ Path: string(elem[0]),
+ Version: string(elem[1]),
Sum: sum,
- }, true
+ }, nil
}
var (
- info = &BuildInfo{}
last *Module
- line string
+ line []byte
ok bool
)
- // Reverse of cmd/go/internal/modload.PackageBuildInfo
+ // Reverse of BuildInfo.String()
for len(data) > 0 {
- line, data, ok = strings.Cut(data, "\n")
+ line, data, ok = bytes.Cut(data, newline)
if !ok {
break
}
switch {
- case strings.HasPrefix(line, pathLine):
+ case bytes.HasPrefix(line, pathLine):
elem := line[len(pathLine):]
- info.Path = elem
- case strings.HasPrefix(line, modLine):
- elem := strings.Split(line[len(modLine):], "\t")
- last = &info.Main
- *last, ok = readEntryFirstLine(elem)
- if !ok {
- return nil, false
+ bi.Path = string(elem)
+ case bytes.HasPrefix(line, modLine):
+ elem := bytes.Split(line[len(modLine):], tab)
+ last = &bi.Main
+ *last, err = readModuleLine(elem)
+ if err != nil {
+ return err
}
- case strings.HasPrefix(line, depLine):
- elem := strings.Split(line[len(depLine):], "\t")
+ case bytes.HasPrefix(line, depLine):
+ elem := bytes.Split(line[len(depLine):], tab)
last = new(Module)
- info.Deps = append(info.Deps, last)
- *last, ok = readEntryFirstLine(elem)
- if !ok {
- return nil, false
+ bi.Deps = append(bi.Deps, last)
+ *last, err = readModuleLine(elem)
+ if err != nil {
+ return err
}
- case strings.HasPrefix(line, repLine):
- elem := strings.Split(line[len(repLine):], "\t")
+ case bytes.HasPrefix(line, repLine):
+ elem := bytes.Split(line[len(repLine):], tab)
if len(elem) != 3 {
- return nil, false
+ return fmt.Errorf("expected 3 columns for replacement; got %d", len(elem))
}
if last == nil {
- return nil, false
+ return fmt.Errorf("replacement with no module on previous line")
}
last.Replace = &Module{
- Path: elem[0],
- Version: elem[1],
- Sum: elem[2],
+ Path: string(elem[0]),
+ Version: string(elem[1]),
+ Sum: string(elem[2]),
}
last = nil
}
+ lineNum++
}
- return info, true
+ return nil
}