avoid one more call to 'go tool buildid' (#253)

We use it to get the content ID of garble's binary, which is used for
both the garble action IDs, as well as 'go tool compile -V=full'.

Since those two happen in separate processes, both used to call 'go tool
buildid' separately. Store it in the gob cache the first time, and reuse
it the second time.

Since each call to cmd/go costs about 10ms (new process, running its
many init funcs, etc), this results in a nice speed-up for our small
benchmark. Most builds will take many seconds though, so note that a
~15ms speedup there will likely not be noticeable.

While at it, simplify the buildInfo global, as now it just contains a
map representation of the -importcfg contents. It now has better names,
docs, and a simpler representation.

We also stop using the term "garbled import", as it was a bit confusing.
"obfuscated types.Package" is a much better description.

	name     old time/op       new time/op       delta
	Build-8        106ms ± 1%         92ms ± 0%  -14.07%  (p=0.010 n=6+4)

	name     old bin-B         new bin-B         delta
	Build-8        6.60M ± 0%        6.60M ± 0%   -0.01%  (p=0.002 n=6+6)

	name     old sys-time/op   new sys-time/op   delta
	Build-8        208ms ± 5%        149ms ± 3%  -28.27%  (p=0.004 n=6+5)

	name     old user-time/op  new user-time/op  delta
	Build-8        433ms ± 3%        384ms ± 3%  -11.35%  (p=0.002 n=6+6)
pull/257/head
Daniel Martí 3 years ago committed by GitHub
parent c0c5a75454
commit b03cd08c09
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -18,11 +18,7 @@ const buildIDSeparator = "/"
// splitActionID returns the action ID half of a build ID, the first component. // splitActionID returns the action ID half of a build ID, the first component.
func splitActionID(buildID string) string { func splitActionID(buildID string) string {
i := strings.Index(buildID, buildIDSeparator) return buildID[:strings.Index(buildID, buildIDSeparator)]
if i < 0 {
return buildID
}
return buildID[:i]
} }
// splitContentID returns the content ID half of a build ID, the last component. // splitContentID returns the content ID half of a build ID, the last component.
@ -80,24 +76,15 @@ func alterToolVersion(tool string, args []string) error {
} }
func ownContentID(toolID []byte) ([]byte, error) { func ownContentID(toolID []byte) ([]byte, error) {
// We can't rely on the module version to exist, because it's
// missing in local builds without 'go get'.
// For now, use 'go tool buildid' on the garble binary.
// Just like Go's own cache, we use hex-encoded sha256 sums.
// Once https://github.com/golang/go/issues/37475 is fixed, we
// can likely just use that.
binaryBuildID, err := buildidOf(cache.ExecPath)
if err != nil {
return nil, err
}
binaryContentID := decodeHash(splitContentID(binaryBuildID))
// Join the two content IDs together into a single base64-encoded sha256 // Join the two content IDs together into a single base64-encoded sha256
// sum. This includes the original tool's content ID, and garble's own // sum. This includes the original tool's content ID, and garble's own
// content ID. // content ID.
h := sha256.New() h := sha256.New()
h.Write(toolID) h.Write(toolID)
h.Write(binaryContentID) if len(cache.BinaryContentID) == 0 {
panic("missing binary content ID")
}
h.Write(cache.BinaryContentID)
// We also need to add the selected options to the full version string, // We also need to add the selected options to the full version string,
// because all of them result in different output. We use spaces to // because all of them result in different output. We use spaces to

@ -102,13 +102,12 @@ var (
// Basic information about the package being currently compiled or linked. // Basic information about the package being currently compiled or linked.
curPkg *listedPackage curPkg *listedPackage
buildInfo = struct { // These are pulled from -importcfg in the current obfuscated build.
// TODO: do we still need imports? // As such, they contain export data for the dependencies which might be
imports map[string]importedPkg // parsed importCfg plus cached info // themselves obfuscated, depending on GOPRIVATE.
}{imports: make(map[string]importedPkg)} importCfgEntries map[string]*importCfgEntry
garbledImporter = importer.ForCompiler(fset, "gc", func(path string) (io.ReadCloser, error) { garbledImporter = importer.ForCompiler(fset, "gc", func(path string) (io.ReadCloser, error) {
return os.Open(buildInfo.imports[path].packagefile) return os.Open(importCfgEntries[path].packagefile)
}).(types.ImporterFrom) }).(types.ImporterFrom)
opts *flagOptions opts *flagOptions
@ -116,29 +115,26 @@ var (
envGoPrivate = os.Getenv("GOPRIVATE") // complemented by 'go env' later envGoPrivate = os.Getenv("GOPRIVATE") // complemented by 'go env' later
) )
func garbledImport(path string) (*types.Package, error) { func obfuscatedTypesPackage(path string) *types.Package {
ipkg, ok := buildInfo.imports[path] entry, ok := importCfgEntries[path]
if !ok { if !ok {
return nil, fmt.Errorf("could not find imported package %q", path) return nil
}
if ipkg.pkg != nil {
return ipkg.pkg, nil // cached
} }
if opts.GarbleDir == "" { if entry.cachedPkg != nil {
return nil, fmt.Errorf("$GARBLE_DIR unset; did you run via 'garble build'?") return entry.cachedPkg
} }
pkg, err := garbledImporter.ImportFrom(path, opts.GarbleDir, 0) pkg, err := garbledImporter.ImportFrom(path, opts.GarbleDir, 0)
if err != nil { if err != nil {
return nil, err return nil
} }
ipkg.pkg = pkg // cache for later use entry.cachedPkg = pkg // cache for later use
return pkg, nil return pkg
} }
type importedPkg struct { type importCfgEntry struct {
packagefile string packagefile string
pkg *types.Package cachedPkg *types.Package
} }
func main1() int { func main1() int {
@ -462,7 +458,7 @@ func transformCompile(args []string) ([]string, error) {
} }
curPkg = cache.ListedPackages[curPkgPathFull] curPkg = cache.ListedPackages[curPkgPathFull]
newImportCfg, err := fillBuildInfo(flags) newImportCfg, err := processImportCfg(flags)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -515,6 +511,7 @@ func transformCompile(args []string) ([]string, error) {
origImporter = importer.Default() origImporter = importer.Default()
} }
// TODO(mvdan): can we use IgnoreFuncBodies=true?
origTypesConfig := types.Config{Importer: origImporter} origTypesConfig := types.Config{Importer: origImporter}
tf.pkg, err = origTypesConfig.Check(curPkgPathFull, fset, files, tf.info) tf.pkg, err = origTypesConfig.Check(curPkgPathFull, fset, files, tf.info)
if err != nil { if err != nil {
@ -557,7 +554,6 @@ func transformCompile(args []string) ([]string, error) {
newPkgPath := curPkgPath newPkgPath := curPkgPath
if curPkgPath != "main" && isPrivate(curPkgPath) { if curPkgPath != "main" && isPrivate(curPkgPath) {
newPkgPath = hashWith(curPkg.GarbleActionID, curPkgPath) newPkgPath = hashWith(curPkg.GarbleActionID, curPkgPath)
// println("compile -p:", curPkgPath, newPkgPath)
flags = flagSetValue(flags, "-p", newPkgPath) flags = flagSetValue(flags, "-p", newPkgPath)
} }
@ -730,8 +726,8 @@ func (tf *transformer) handleDirectives(comments []string) {
if err != nil { if err != nil {
continue // probably a made up symbol name continue // probably a made up symbol name
} }
garbledPkg, _ := garbledImport(pkgPath) obfPkg := obfuscatedTypesPackage(pkgPath)
if garbledPkg != nil && garbledPkg.Scope().Lookup(name) != nil { if obfPkg != nil && obfPkg.Scope().Lookup(name) != nil {
continue // the name exists and was not garbled continue // the name exists and was not garbled
} }
@ -855,10 +851,10 @@ func isPrivate(path string) bool {
return module.MatchPrefixPatterns(envGoPrivate, path) return module.MatchPrefixPatterns(envGoPrivate, path)
} }
// fillBuildInfo initializes the global buildInfo struct via the supplied flags, // processImportCfg initializes importCfgEntries via the supplied flags, and
// and constructs a new importcfg with the obfuscated import paths changed as // constructs a new importcfg with the obfuscated import paths changed as
// necessary. // necessary.
func fillBuildInfo(flags []string) (newImportCfg string, _ error) { func processImportCfg(flags []string) (newImportCfg string, _ error) {
importCfg := flagValue(flags, "-importcfg") importCfg := flagValue(flags, "-importcfg")
if importCfg == "" { if importCfg == "" {
return "", fmt.Errorf("could not find -importcfg argument") return "", fmt.Errorf("could not find -importcfg argument")
@ -868,7 +864,9 @@ func fillBuildInfo(flags []string) (newImportCfg string, _ error) {
return "", err return "", err
} }
importCfgEntries = make(map[string]*importCfgEntry)
importMap := make(map[string]string) importMap := make(map[string]string)
for _, line := range strings.SplitAfter(string(data), "\n") { for _, line := range strings.SplitAfter(string(data), "\n") {
line = strings.TrimSpace(line) line = strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, "#") { if line == "" || strings.HasPrefix(line, "#") {
@ -896,11 +894,11 @@ func fillBuildInfo(flags []string) (newImportCfg string, _ error) {
} }
importPath, objectPath := args[:j], args[j+1:] importPath, objectPath := args[:j], args[j+1:]
impPkg := importedPkg{packagefile: objectPath} impPkg := &importCfgEntry{packagefile: objectPath}
buildInfo.imports[importPath] = impPkg importCfgEntries[importPath] = impPkg
if otherPath, ok := importMap[importPath]; ok { if otherPath, ok := importMap[importPath]; ok {
buildInfo.imports[otherPath] = impPkg importCfgEntries[otherPath] = impPkg
} }
} }
} }
@ -916,7 +914,6 @@ func fillBuildInfo(flags []string) (newImportCfg string, _ error) {
} }
for beforePath, afterPath := range importMap { for beforePath, afterPath := range importMap {
if isPrivate(beforePath) { if isPrivate(beforePath) {
println(beforePath, afterPath)
pkg, err := listPackage(beforePath) pkg, err := listPackage(beforePath)
if err != nil { if err != nil {
panic(err) // shouldn't happen panic(err) // shouldn't happen
@ -925,7 +922,7 @@ func fillBuildInfo(flags []string) (newImportCfg string, _ error) {
} }
fmt.Fprintf(newCfg, "importmap %s=%s\n", beforePath, afterPath) fmt.Fprintf(newCfg, "importmap %s=%s\n", beforePath, afterPath)
} }
for impPath, pkg := range buildInfo.imports { for impPath, pkg := range importCfgEntries {
if isPrivate(impPath) { if isPrivate(impPath) {
pkg, err := listPackage(impPath) pkg, err := listPackage(impPath)
if err != nil { if err != nil {
@ -1095,8 +1092,8 @@ func (tf *transformer) transformGo(file *ast.File) *ast.File {
// We're not obfuscating cgo names. // We're not obfuscating cgo names.
return true return true
} }
if garbledPkg, _ := garbledImport(path); garbledPkg != nil { if obfPkg := obfuscatedTypesPackage(path); obfPkg != nil {
if garbledPkg.Scope().Lookup(named.Obj().Name()) != nil { if obfPkg.Scope().Lookup(named.Obj().Name()) != nil {
recordStruct(named, tf.ignoreObjects) recordStruct(named, tf.ignoreObjects)
return true return true
} }
@ -1115,8 +1112,8 @@ func (tf *transformer) transformGo(file *ast.File) *ast.File {
if named == nil { if named == nil {
break break
} }
if garbledPkg, _ := garbledImport(path); garbledPkg != nil { if obfPkg := obfuscatedTypesPackage(path); obfPkg != nil {
if garbledPkg.Scope().Lookup(x.Name()) != nil { if obfPkg.Scope().Lookup(x.Name()) != nil {
recordStruct(named, tf.ignoreObjects) recordStruct(named, tf.ignoreObjects)
return true return true
} }
@ -1145,22 +1142,20 @@ func (tf *transformer) transformGo(file *ast.File) *ast.File {
if err != nil { if err != nil {
panic(err) // shouldn't happen panic(err) // shouldn't happen
} }
// TODO: Make this check less prone to bugs, like the one we had
// with indirect dependencies. If "path" is not our current obfPkg := obfuscatedTypesPackage(path)
// package, then it must exist in buildInfo.imports. Otherwise
// we should panic.
if buildInfo.imports[path].packagefile != "" {
garbledPkg, err := garbledImport(path)
if err != nil {
panic(err) // shouldn't happen
}
// Check if the imported name wasn't garbled, e.g. if it's assembly. // Check if the imported name wasn't garbled, e.g. if it's assembly.
// If the object returned from the garbled package's scope has a different type as the object // If the object returned from the garbled package's scope has a
// we're searching for, they are most likely two separate objects with the same name, so ok to garble // different type as the object we're searching for, they are
if o := garbledPkg.Scope().Lookup(obj.Name()); o != nil && reflect.TypeOf(o) == reflect.TypeOf(obj) { // most likely two separate objects with the same name, so ok to
// garble
if obfPkg == nil {
// TODO(mvdan): This is probably a bug.
// Add a test case where an indirect package has a name
// that we did not obfuscate.
} else if o := obfPkg.Scope().Lookup(obj.Name()); o != nil && reflect.TypeOf(o) == reflect.TypeOf(obj) {
return true return true
} }
}
origName := node.Name origName := node.Name
_ = origName // used for debug prints below _ = origName // used for debug prints below
@ -1236,7 +1231,7 @@ func transformLink(args []string) ([]string, error) {
curPkg = cache.ListedPackages[cache.MainImportPath] curPkg = cache.ListedPackages[cache.MainImportPath]
newImportCfg, err := fillBuildInfo(flags) newImportCfg, err := processImportCfg(flags)
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -33,6 +33,8 @@ func commandReverse(args []string) error {
} }
listArgs = append(listArgs, flags...) listArgs = append(listArgs, flags...)
listArgs = append(listArgs, mainPkg) listArgs = append(listArgs, mainPkg)
// TODO: We most likely no longer need this "list -toolexec" call, since
// we use the original build IDs.
cmd, err := toolexecCmd("list", listArgs) cmd, err := toolexecCmd("list", listArgs)
if err != nil { if err != nil {
return err return err
@ -70,9 +72,6 @@ func commandReverse(args []string) error {
if isPrivate(pkg.ImportPath) { if isPrivate(pkg.ImportPath) {
privatePkgPaths = append(privatePkgPaths, pkg.ImportPath) privatePkgPaths = append(privatePkgPaths, pkg.ImportPath)
} }
// The action ID, and possibly the export file, will be used
// later to reconstruct the mapping of obfuscated names.
buildInfo.imports[pkg.ImportPath] = importedPkg{packagefile: pkg.Export}
} }
if err := cmd.Wait(); err != nil { if err := cmd.Wait(); err != nil {

@ -15,7 +15,8 @@ import (
"strings" "strings"
) )
// sharedCache this data is sharedCache between the different garble processes. // sharedCache is shared as a read-only cache between the many garble toolexec
// sub-processes.
// //
// Note that we fill this cache once from the root process in saveListedPackages, // Note that we fill this cache once from the root process in saveListedPackages,
// store it into a temporary file via gob encoding, and then reuse that file // store it into a temporary file via gob encoding, and then reuse that file
@ -30,6 +31,15 @@ type sharedCache struct {
// allows us to obtain the non-garbled export data of all dependencies, useful // allows us to obtain the non-garbled export data of all dependencies, useful
// for type checking of the packages as we obfuscate them. // for type checking of the packages as we obfuscate them.
ListedPackages map[string]*listedPackage ListedPackages map[string]*listedPackage
// We can't rely on the module version to exist, because it's
// missing in local builds without 'go get'.
// For now, use 'go tool buildid' on the garble binary.
// Just like Go's own cache, we use hex-encoded sha256 sums.
// Once https://github.com/golang/go/issues/37475 is fixed, we
// can likely just use that.
BinaryContentID []byte
MainImportPath string // TODO: remove with TOOLEXEC_IMPORTPATH MainImportPath string // TODO: remove with TOOLEXEC_IMPORTPATH
} }
@ -190,7 +200,7 @@ func setListedPackages(patterns []string) error {
if err != nil { if err != nil {
return err return err
} }
binaryContentID := decodeHash(splitContentID(binaryBuildID)) cache.BinaryContentID = decodeHash(splitContentID(binaryBuildID))
dec := json.NewDecoder(stdout) dec := json.NewDecoder(stdout)
cache.ListedPackages = make(map[string]*listedPackage) cache.ListedPackages = make(map[string]*listedPackage)
@ -211,7 +221,7 @@ func setListedPackages(patterns []string) error {
actionID := decodeHash(splitActionID(buildID)) actionID := decodeHash(splitActionID(buildID))
h := sha256.New() h := sha256.New()
h.Write(actionID) h.Write(actionID)
h.Write(binaryContentID) h.Write(cache.BinaryContentID)
pkg.GarbleActionID = h.Sum(nil)[:buildIDComponentLength] pkg.GarbleActionID = h.Sum(nil)[:buildIDComponentLength]
} }

Loading…
Cancel
Save