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/main.go

220 lines
5.0 KiB
Go

// Copyright (c) 2019, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package main
import (
"crypto/sha256"
"encoding/base64"
"flag"
"fmt"
"go/ast"
"go/parser"
"go/printer"
"go/token"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
)
var flagSet = flag.NewFlagSet("garble", flag.ContinueOnError)
func init() { flagSet.Usage = usage }
func usage() {
fmt.Fprintf(os.Stderr, `
Usage of garble:
go build -toolexec=garble [build flags] [packages]
`[1:])
flagSet.PrintDefaults()
os.Exit(2)
}
func main() { os.Exit(main1()) }
var (
workingDir string
deferred []func() error
fset = token.NewFileSet()
b64 = base64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_z")
printerConfig = printer.Config{Mode: printer.RawFormat}
)
func main1() int {
if err := flagSet.Parse(os.Args[1:]); err != nil {
return 2
}
log.SetPrefix("[garble] ")
args := flagSet.Args()
if len(args) < 1 {
flagSet.Usage()
}
var err error
workingDir, err = os.Getwd()
if err != nil {
fmt.Fprintln(os.Stderr, err)
return 1
}
_, tool := filepath.Split(args[0])
// TODO: trim ".exe" for windows?
transformed := args[1:]
// log.Println(tool, transformed)
if transform := transformFuncs[tool]; transform != nil {
if transformed, err = transform(transformed); err != nil {
fmt.Fprintln(os.Stderr, err)
return 1
}
}
defer func() {
for _, fn := range deferred {
if err := fn(); err != nil {
fmt.Fprintln(os.Stderr, err)
}
}
}()
cmd := exec.Command(args[0], transformed...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
fmt.Fprintln(os.Stderr, err)
return 1
}
return 0
}
var transformFuncs = map[string]func([]string) ([]string, error){
"compile": transformCompile,
"link": transformLink,
}
func transformCompile(args []string) ([]string, error) {
flags, files := splitFlagsFromFiles(args, ".go")
if len(files) == 0 {
// Nothing to transform; probably just ["-V=full"].
return args, nil
}
trimpath := flagValue(flags, "-trimpath")
if !strings.Contains(trimpath, workingDir) {
return nil, fmt.Errorf("-toolexec=garble should be used alongside -trimpath")
}
std := flagValue(flags, "-std") == "true"
buildid := flagValue(flags, "-buildid")
for i, file := range files {
if std {
continue
}
var err error
files[i], err = transformGoFile(buildid, file)
if err != nil {
return nil, err
}
}
return append(flags, files...), nil
}
func hashWith(salt, value string) string {
const length = 8
d := sha256.New()
io.WriteString(d, salt)
io.WriteString(d, value)
sum := b64.EncodeToString(d.Sum(nil))
for i := 0; i < len(sum)-length; i++ {
if '0' <= sum[i] && sum[i] <= '9' {
continue
}
return sum[i : i+length]
}
return "_" + sum[:length-1]
}
// transformGoFile creates a garbled copy of the Go file at path, and returns
// the path to the copy.
func transformGoFile(buildid, path string) (string, error) {
file, err := parser.ParseFile(fset, path, nil, 0)
if err != nil {
return "", err
}
ast.Inspect(file, func(node ast.Node) bool {
switch node := node.(type) {
case *ast.Ident:
switch {
case node.Obj == nil:
// a builtin name, or the package name
case node.Name == "main":
// possibly the main func
default:
node.Name = hashWith(buildid, node.Name)
}
}
return true
})
f, err := ioutil.TempFile("", "garble")
if err != nil {
return "", err
}
defer f.Close()
printerConfig.Fprint(os.Stderr, fset, file)
if err := printerConfig.Fprint(f, fset, file); err != nil {
return "", err
}
if err := f.Close(); err != nil {
return "", err
}
return f.Name(), nil
}
func transformLink(args []string) ([]string, error) {
flags, files := splitFlagsFromFiles(args, ".a")
if len(files) == 0 {
// Nothing to transform; probably just ["-V=full"].
return args, nil
}
flags = append(flags, "-w", "-s")
return append(flags, files...), nil
}
// splitFlagsFromFiles splits args into a list of flag and file arguments. Since
// we can't rely on "--" being present, and we don't parse all flags upfront, we
// rely on finding the first argument that doesn't begin with "-" and that has
// the extension we expect for the list of files.
func splitFlagsFromFiles(args []string, ext string) (flags, files []string) {
for i, arg := range args {
if !strings.HasPrefix(arg, "-") && strings.HasSuffix(arg, ext) {
return args[:i:i], args[i:]
}
}
return args, nil
}
// flagValue retrieves the value of a flag such as "-foo", from strings in the
// list of arguments like "-foo=bar" or "-foo" "bar".
func flagValue(flags []string, name string) string {
for i, arg := range flags {
if val := strings.TrimPrefix(arg, name+"="); val != arg {
// -name=value
return val
}
if arg == name {
if i+1 < len(flags) {
if val := flags[i+1]; !strings.HasPrefix(val, "-") {
// -name value
return flags[i+1]
}
}
// -name, equivalent to -name=true
return "true"
}
}
return ""
}