You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
garble/map.go

148 lines
3.6 KiB
Go

4 months ago
// Copyright (c) 2019, The Garble Authors.
// See LICENSE for licensing information.
package main
import (
"encoding/json"
"flag"
"fmt"
"go/ast"
"go/types"
"os"
"golang.org/x/tools/go/types/objectpath"
)
// commandMap implements "garble map".
func commandMap(args []string) error {
flags, pkgs := splitFlagsFromArgs(args)
if hasHelpFlag(flags) || len(args) == 0 {
fmt.Fprint(os.Stderr, `
usage: garble [garble flags] map [build flags] packages...
For example, after building an obfuscated program as follows:
garble -literals build -tags=mytag ./cmd/mycmd
One can obtain an obfuscation map as follows:
garble -literals map -tags=mytag ./cmd/mycmd
`[1:])
return errJustExit(2)
}
listArgs := []string{
"-json",
"-deps",
"-export",
}
listArgs = append(listArgs, flags...)
listArgs = append(listArgs, pkgs...)
// TODO: We most likely no longer need this "list -toolexec" call, since
// we use the original build IDs.
_, err := toolexecCmd("list", listArgs)
defer os.RemoveAll(os.Getenv("GARBLE_SHARED"))
if err != nil {
return err
}
// We don't actually run a main Go command with all flags,
// so if the user gave a non-build flag,
// we need this check to not silently ignore it.
if _, firstUnknown := filterForwardBuildFlags(flags); firstUnknown != "" {
// A bit of a hack to get a normal flag.Parse error.
// Longer term, "map" might have its own FlagSet.
return flag.NewFlagSet("", flag.ContinueOnError).Parse([]string{firstUnknown})
}
// A package's names are generally hashed with the action ID of its
// obfuscated build. We recorded those action IDs above.
// Note that we parse Go files directly to obtain the names, since the
// export data only exposes exported names. Parsing Go files is cheap,
// so it's unnecessary to try to avoid this cost.
type obfuscatedPackageInfo struct {
Path string `json:"path"`
Objects map[objectpath.Path]string `json:"objects"`
}
result := make(map[string]obfuscatedPackageInfo, len(sharedCache.ListedPackages))
for _, lpkg := range sharedCache.ListedPackages {
if !lpkg.ToObfuscate {
continue
}
tf := transformer{
curPkg: lpkg,
origImporter: importerForPkg(lpkg),
}
objectMap := make(map[objectpath.Path]string)
result[lpkg.ImportPath] = obfuscatedPackageInfo{
Path: hashWithPackage(&tf, lpkg, lpkg.ImportPath),
Objects: objectMap,
}
files, err := parseFiles(lpkg.Dir, lpkg.CompiledGoFiles)
if err != nil {
return err
}
tf.pkg, tf.info, err = typecheck(lpkg.ImportPath, files, tf.origImporter)
if err != nil {
return err
}
tf.curPkgCache, err = loadPkgCache(lpkg, tf.pkg, files, tf.info, nil)
if err != nil {
return err
}
tf.fieldToStruct = computeFieldToStruct(tf.info)
var encoder objectpath.Encoder
visited := make(map[types.Object]bool) // Avoid duplicated work.
for _, file := range files {
ast.Inspect(file, func(node ast.Node) bool {
switch node := node.(type) {
case ast.Stmt:
// Skip statements as local objects have no object path.
return false
case *ast.Ident:
obj := tf.info.ObjectOf(node)
if obj == nil || obj.Pkg() != tf.pkg || visited[obj] {
return true
}
visited[obj] = true
obfuscated := tf.obfuscateObjectName(obj)
if obfuscated == obj.Name() {
return true
}
// This is probably costlier than obfuscation:
// run it only when necessary.
path, err := encoder.For(obj)
if err != nil {
return true
}
objectMap[path] = obfuscated
default:
return true
}
return true
})
}
}
return json.NewEncoder(os.Stdout).Encode(result)
}