support reversing stack trace positions (#287)

In particular, the positions within function declarations, including the
positions of call sites to other functions.

Note that this isn't well tested just yet, particularly not with other
features like -literals. We can extend the tests and code over time.
This gets us the core basics.

The issue will be closed once the feature is documented for users, in a
follow-up PR.

Updates #5.
pull/288/head
Daniel Martí 3 years ago committed by GitHub
parent ce2c45440a
commit a8c5d534d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -21,11 +21,11 @@ func isDirective(text string) bool {
// printFile prints a Go file to a buffer, while also removing non-directive
// comments and adding extra compiler directives to obfuscate position
// information.
func printFile(file *ast.File) ([]byte, error) {
func printFile(file1 *ast.File) ([]byte, error) {
printConfig := printer.Config{Mode: printer.RawFormat}
var buf1 bytes.Buffer
if err := printConfig.Fprint(&buf1, fset, file); err != nil {
if err := printConfig.Fprint(&buf1, fset, file1); err != nil {
return nil, err
}
src := buf1.Bytes()
@ -36,8 +36,9 @@ func printFile(file *ast.File) ([]byte, error) {
return src, nil
}
filename := fset.Position(file.Pos()).Filename
if strings.HasPrefix(filepath.Base(filename), "_cgo_") {
absFilename := fset.Position(file1.Pos()).Filename
filename := filepath.Base(absFilename)
if strings.HasPrefix(filename, "_cgo_") {
// cgo-generated files don't need changed line numbers.
// Plus, the compiler can complain rather easily.
return src, nil
@ -48,7 +49,7 @@ func printFile(file *ast.File) ([]byte, error) {
// and those are the only source of truth that go/printer uses.
// So the positions of the comments in the given file are wrong.
// The only way we can get the final ones is to parse again.
file, err := parser.ParseFile(fset, filename, src, parser.ParseComments)
file2, err := parser.ParseFile(fset, absFilename, src, parser.ParseComments)
if err != nil {
return nil, err
}
@ -63,7 +64,7 @@ func printFile(file *ast.File) ([]byte, error) {
toAdd = append(toAdd, commentToAdd{offset, text})
}
addComment(0, "/*line :1*/")
for _, group := range file.Comments {
for _, group := range file2.Comments {
for _, comment := range group.List {
if isDirective(comment.Text) {
// TODO(mvdan): merge with the zeroing below
@ -73,7 +74,9 @@ func printFile(file *ast.File) ([]byte, error) {
}
}
// Remove all existing comments by making them whitespace.
for _, group := range file.Comments {
// This is superior to removing the comments before printing,
// because then the final source would have different line numbers.
for _, group := range file2.Comments {
for _, comment := range group.List {
start := fset.Position(comment.Pos()).Offset
end := fset.Position(comment.End()).Offset
@ -83,9 +86,10 @@ func printFile(file *ast.File) ([]byte, error) {
}
}
for _, decl := range file.Decls {
for i, decl := range file2.Decls {
newName := ""
if !opts.Tiny {
decl := file1.Decls[i]
origPos := fmt.Sprintf("%s:%d", filename, fset.Position(decl.Pos()).Offset)
newName = hashWith(curPkg.GarbleActionID, origPos) + ".go"
// log.Printf("%q hashed with %x to %q", origPos, curPkg.GarbleActionID, newName)

@ -5,6 +5,7 @@ package main
import (
"bufio"
"fmt"
"go/ast"
"go/parser"
"go/token"
@ -56,12 +57,29 @@ func commandReverse(args []string) error {
addReplace(lpkg.ImportPath)
for _, goFile := range lpkg.GoFiles {
goFile = filepath.Join(lpkg.Dir, goFile)
file, err := parser.ParseFile(fset, goFile, nil, 0)
fullGoFile := filepath.Join(lpkg.Dir, goFile)
file, err := parser.ParseFile(fset, fullGoFile, nil, 0)
if err != nil {
return err
}
for _, decl := range file.Decls {
// Reverse position information.
pos := fset.Position(decl.Pos())
origPos := fmt.Sprintf("%s:%d", goFile, pos.Offset)
newFilename := hashWith(lpkg.GarbleActionID, origPos) + ".go"
// For each line number within this function,
// replace the obfuscated position with its original form.
// Remember that the "/*line" directive starts at 1.
numLines := fset.Position(decl.End()).Line - pos.Line
for i := 0; i < numLines; i++ {
replaces = append(replaces,
fmt.Sprintf("%s:%d", newFilename, 1+i),
fmt.Sprintf("%s/%s:%d", lpkg.ImportPath, goFile, pos.Line+i),
)
}
// Reverse the name itself.
// TODO: Probably do type names too. What else?
switch decl := decl.(type) {
case *ast.FuncDecl:

@ -13,14 +13,13 @@ cp stderr main.stderr
# This output is not reproducible between 'go test' runs,
# so we can't use a static golden file.
grep 'goroutine 1 \[running\]' main.stderr
! grep 'ExportedLibFunc|unexportedMainFunc|test/main|main.go|lib.go' main.stderr
! grep 'ExportedLibFunc|unexportedMainFunc|test/main|main\.go|lib\.go' main.stderr
stdin main.stderr
garble reverse
stdout -count=1 'test/main/lib\.ExportedLibFunc'
stdout -count=1 'main\.unexportedMainFunc'
# TODO: this is what we want when "reverse" is finished
# cmp stdout reverse.stdout
cmp stdout reverse.stdout
# TODO: test with -literals too, as it modifies the source.
# Ensure that the reversed output matches the non-garbled output.
go build -trimpath
@ -64,6 +63,8 @@ func ExportedLibFunc(w io.Writer) error {
// Strip them out here, to have portable static stdout files.
rxVariableSuffix := regexp.MustCompile(`0x[0-9a-f]+`)
// Keep this comment here, because comments affect line numbers.
stack := debug.Stack()
stack = rxVariableSuffix.ReplaceAll(stack, []byte("0x??"))
_, err := w.Write(stack)
@ -74,7 +75,7 @@ goroutine 1 [running]:
runtime/debug.Stack(0x??, 0x??, 0x??)
runtime/debug/stack.go:24 +0x??
test/main/lib.ExportedLibFunc(0x??, 0x??, 0x??, 0x??)
test/main/lib/lib.go:15 +0x??
test/main/lib/lib.go:17 +0x??
main.unexportedMainFunc(...)
test/main/main.go:14
main.main()

Loading…
Cancel
Save