dwtest: add new pseudo-package with DWARF location tests

This patch rolls out a new set of regression tests for verifying the
integrity of DWARF variable location expressions. The tests use a
"scaffold" consisting of harness program 'dwdumploc' that can be run
on a Go binary to dump out the DWARF location expressions for a
function's formal parameters, and then a new set of tests that invoke
the harness on specific canned testcases. The scaffold itself depends
on packages from Delve.

DWARF location expressions can refer to machine registers, making the
output of the dump architecture-dependent. The dumper program
currently supports { Amd64, Arm64 } x { Linux, Macho, Windows }, which
matches up mostly with current Delve support.

Updates golang/go#47354.
Updates golang/go#46845.

Change-Id: I7377c03b19e3cf43aa0f7698fcc636be9c852765
Reviewed-on: https://go-review.googlesource.com/c/debug/+/364674
Trust: Than McIntosh <thanm@google.com>
Trust: Dmitri Shuralyov <dmitshur@golang.org>
Run-TryBot: Than McIntosh <thanm@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: David Chase <drchase@google.com>
diff --git a/dwtest/dwloc_test.go b/dwtest/dwloc_test.go
new file mode 100644
index 0000000..a4e85c9
--- /dev/null
+++ b/dwtest/dwloc_test.go
@@ -0,0 +1,263 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package dwtest_test
+
+// This file contains a set of DWARF variable location generation
+// tests that are intended to compliment the existing linker DWARF
+// tests. The tests make use of a harness / utility program
+// "dwdumploc" that is built during test setup and then
+// invoked (fork+exec) in testpoints. We do things this way (as
+// opposed to just incorporating all of the source code from
+// testdata/dwdumploc.go into this file) so that the dumper code can
+// import packages from Delve without needing to vendor everything
+// into the Go distribution itself.
+//
+// Notes on GOARCH/GOOS support: this test is guarded to execute only
+// on arch/os combinations supported by Delve (see the testpoint
+// below); as Delve evolves we may need to update accordingly.
+//
+// This test requires network support (the harness build has to
+// download packages), so only runs in "long" test mode at the moment,
+// and since we don't currently have longtest builders for every
+// arch/os pair that Delve supports (ex: no linux/arm64 longtest
+// builder, Issue #49649), this is something to keep in mind when
+// running trybots etc.
+//
+
+import (
+	"bytes"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"runtime"
+	"strings"
+	"testing"
+
+	"golang.org/x/debug/internal/testenv"
+)
+
+// copyFilesForHarness copies various files into the build dir for the
+// harness, including the main package, go.mod, and a copy of the
+// dwtest package (the latter is why we are doing an explicit copy as
+// opposed to just building directly from sources in testdata).
+// Return value is the path to a build directory for the harness
+// build.
+func copyFilesForHarness(t *testing.T) string {
+	mkdir := func(d string) {
+		if err := os.Mkdir(d, 0777); err != nil {
+			t.Fatalf("mkdir failed: %v", err)
+		}
+	}
+	cp := func(from, to string) {
+		var payload []byte
+		payload, err := ioutil.ReadFile(from)
+		if err != nil {
+			t.Fatalf("ioutil.ReadFile failed: %v", err)
+		}
+		if err = ioutil.WriteFile(to, payload, 0644); err != nil {
+			t.Fatalf("ioutil.WriteFile failed: %v", err)
+		}
+	}
+	join := filepath.Join
+	bd := join(t.TempDir(), "build")
+	bdt := join(bd, "dwtest")
+	mkdir(bd)
+	mkdir(bdt)
+	cp(join("testdata", "dwdumploc.go"), join(bd, "main.go"))
+	cp(join("testdata", "go.mod.txt"), join(bd, "go.mod"))
+	cp(join("testdata", "go.sum.txt"), join(bd, "go.sum"))
+	cp("dwtest.go", join(bdt, "dwtest.go"))
+	return bd
+}
+
+// buildHarness builds the helper program "dwdumploc.exe".
+func buildHarness(t *testing.T) string {
+
+	// Copy source files into build dir.
+	bd := copyFilesForHarness(t)
+
+	// Run build.
+	harnessPath := filepath.Join(t.TempDir(), "dumpdwloc.exe")
+	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", harnessPath)
+	cmd.Dir = bd
+	if b, err := cmd.CombinedOutput(); err != nil {
+		t.Fatalf("build failed (%v): %s", err, b)
+	}
+	return harnessPath
+}
+
+// runHarness runs our previously built harness exec on a Go binary
+// 'exePath' for function 'fcn' and returns the results. Stderr from
+// the harness is printed to test stderr. Note: to debug the harness,
+// try adding "-v=2" to the exec.Command below.
+func runHarness(t *testing.T, harnessPath string, exePath string, fcn string) string {
+	cmd := exec.Command(harnessPath, "-m", exePath, "-f", fcn)
+	var b bytes.Buffer
+	cmd.Stderr = os.Stderr
+	cmd.Stdout = &b
+	if err := cmd.Run(); err != nil {
+		t.Fatalf("running 'harness -m %s -f %s': %v", exePath, fcn, err)
+	}
+	return strings.TrimSpace(string(b.Bytes()))
+}
+
+// gobuild is a helper to bulid a Go program from source code,
+// so that we can inspect selected bits of DWARF in the resulting binary.
+// Return value is binary path.
+func gobuild(t *testing.T, sourceCode string, pname string) string {
+	spath := filepath.Join(t.TempDir(), pname+".go")
+	if err := ioutil.WriteFile(spath, []byte(sourceCode), 0644); err != nil {
+		t.Fatalf("write to %s failed: %s", spath, err)
+	}
+	epath := filepath.Join(t.TempDir(), pname+".exe")
+
+	// A note on this build: Delve currently has problems digesting
+	// PIE binaries on Windows; until this can be straightened out,
+	// default to "exe" buildmode.
+	cmd := exec.Command(testenv.GoToolPath(t), "build", "-buildmode=exe", "-o", epath, spath)
+	if b, err := cmd.CombinedOutput(); err != nil {
+		t.Logf("%% build output: %s\n", b)
+		t.Fatalf("build failed: %s", err)
+	}
+	return epath
+}
+
+const programSourceCode = `
+package main
+
+import "context"
+
+var G int
+
+//go:noinline
+func another(x int) {
+	println(G)
+}
+
+//go:noinline
+func docall(f func()) {
+	f()
+}
+
+//go:noinline
+func Issue47354(s string) {
+	docall(func() {
+		println("s is", s)
+	})
+	G++
+	another(int(s[0]))
+}
+
+type DB int
+type driverConn int
+type Result interface {
+	Foo()
+}
+
+//go:noinline
+func (db *DB) Issue46845(ctx context.Context, dc *driverConn, release func(error), query string, args []interface{}) (res Result, err error) {
+	defer func() {
+		release(err)
+        println(len(args))
+	}()
+	return nil, nil
+}
+
+func main() {
+	Issue47354("poo")
+	var d DB
+	d.Issue46845(context.Background(), nil, func(error) {}, "foo", nil)
+}
+
+`
+
+func testIssue47354(t *testing.T, harnessPath string, ppath string) {
+	expected := map[string]string{
+		"amd64": "1: in-param \"s\" loc=\"{ [0: S=8 RAX] [1: S=8 RBX] }\"",
+		"arm64": "1: in-param \"s\" loc=\"{ [0: S=8 R0] [1: S=8 R1] }\"",
+	}
+	fname := "Issue47354"
+	got := runHarness(t, harnessPath, ppath, "main."+fname)
+	want := expected[runtime.GOARCH]
+	if got != want {
+		t.Errorf("failed Issue47354 arch %s:\ngot: %q\nwant: %q",
+			runtime.GOARCH, got, want)
+	}
+}
+
+func testIssue46845(t *testing.T, harnessPath string, ppath string) {
+
+	// NB: note the "addr=0x1000" for the stack-based parameter "args"
+	// below. This is not an accurate stack location, it's just an
+	// artifact of the way we call into Delve.
+	expected := map[string]string{
+		"amd64": `
+1: in-param "db" loc="{ [0: S=0 RAX] }"
+2: in-param "ctx" loc="{ [0: S=8 RBX] [1: S=8 RCX] }"
+3: in-param "dc" loc="{ [0: S=0 RDI] }"
+4: in-param "release" loc="{ [0: S=0 RSI] }"
+5: in-param "query" loc="{ [0: S=8 R8] [1: S=8 R9] }"
+6: in-param "args" loc="{ [0: S=8 addr=0x1000] [1: S=8 addr=0x1008] [2: S=8 addr=0x1010] }"
+7: out-param "res" loc="<not available>"
+8: out-param "err" loc="<not available>"
+`,
+		"arm64": `
+1: in-param "db" loc="{ [0: S=0 R0] }"
+2: in-param "ctx" loc="{ [0: S=8 R1] [1: S=8 R2] }"
+3: in-param "dc" loc="{ [0: S=0 R3] }"
+4: in-param "release" loc="{ [0: S=0 R4] }"
+5: in-param "query" loc="{ [0: S=8 R5] [1: S=8 R6] }"
+6: in-param "args" loc="{ [0: S=8 R7] [1: S=8 R8] [2: S=8 R9] }"
+7: out-param "res" loc="<not available>"
+8: out-param "err" loc="<not available>"
+`,
+	}
+	fname := "(*DB).Issue46845"
+	got := runHarness(t, harnessPath, ppath, "main."+fname)
+	want := strings.TrimSpace(expected[runtime.GOARCH])
+	if got != want {
+		t.Errorf("failed Issue47354 arch %s:\ngot: %s\nwant: %s",
+			runtime.GOARCH, got, want)
+	}
+}
+
+func TestDwarfVariableLocations(t *testing.T) {
+	testenv.NeedsGo1Point(t, 18)
+	testenv.MustHaveGoBuild(t)
+	testenv.MustHaveExternalNetwork(t)
+
+	// A note on the guard below:
+	// - Delve doesn't officially support darwin/arm64, but I've run
+	//   this test by hand on darwin/arm64 and it seems to work, so
+	//   it is included for the moment
+	// - the harness code currently only supports amd64 + arm64. If more
+	//   archs are added (ex: 386) the harness will need to be updated.
+	pair := runtime.GOOS + "/" + runtime.GOARCH
+	switch pair {
+	case "linux/amd64", "linux/arm64", "windows/amd64",
+		"darwin/amd64", "darwin/arm64":
+	default:
+		t.Skipf("unsupported OS/ARCH pair %s (this tests supports only OS values supported by Delve", pair)
+	}
+
+	// Build test harness.
+	harnessPath := buildHarness(t)
+
+	// Build program to inspect. NB: we're building at default (with
+	// optimization); it might also be worth doing a "-l -N" build
+	// to verify the location expressions in that case.
+	ppath := gobuild(t, programSourceCode, "prog")
+
+	// Sub-tests for each function we want to inspect.
+	t.Run("Issue47354", func(t *testing.T) {
+		t.Parallel()
+		testIssue47354(t, harnessPath, ppath)
+	})
+	t.Run("Issue46845", func(t *testing.T) {
+		t.Parallel()
+		testIssue46845(t, harnessPath, ppath)
+	})
+}
diff --git a/dwtest/dwtest.go b/dwtest/dwtest.go
new file mode 100644
index 0000000..de9498d
--- /dev/null
+++ b/dwtest/dwtest.go
@@ -0,0 +1,200 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Except for this comment and the import path, this file is a verbatim
+// copy of the file with the same name in $GOROOT/src/cmd/link/internal/dwtest.
+
+package dwtest
+
+import (
+	"debug/dwarf"
+	"errors"
+	"fmt"
+	"os"
+)
+
+// Helper type for supporting queries on DIEs within a DWARF
+// .debug_info section. Invoke the populate() method below passing in
+// a dwarf.Reader, which will read in all DIEs and keep track of
+// parent/child relationships. Queries can then be made to ask for
+// DIEs by name or by offset. This will hopefully reduce boilerplate
+// for future test writing.
+
+type Examiner struct {
+	dies        []*dwarf.Entry
+	idxByOffset map[dwarf.Offset]int
+	kids        map[int][]int
+	parent      map[int]int
+	byname      map[string][]int
+}
+
+// Populate the Examiner using the DIEs read from rdr.
+func (ex *Examiner) Populate(rdr *dwarf.Reader) error {
+	ex.idxByOffset = make(map[dwarf.Offset]int)
+	ex.kids = make(map[int][]int)
+	ex.parent = make(map[int]int)
+	ex.byname = make(map[string][]int)
+	var nesting []int
+	for entry, err := rdr.Next(); entry != nil; entry, err = rdr.Next() {
+		if err != nil {
+			return err
+		}
+		if entry.Tag == 0 {
+			// terminator
+			if len(nesting) == 0 {
+				return errors.New("nesting stack underflow")
+			}
+			nesting = nesting[:len(nesting)-1]
+			continue
+		}
+		idx := len(ex.dies)
+		ex.dies = append(ex.dies, entry)
+		if _, found := ex.idxByOffset[entry.Offset]; found {
+			return errors.New("DIE clash on offset")
+		}
+		ex.idxByOffset[entry.Offset] = idx
+		if name, ok := entry.Val(dwarf.AttrName).(string); ok {
+			ex.byname[name] = append(ex.byname[name], idx)
+		}
+		if len(nesting) > 0 {
+			parent := nesting[len(nesting)-1]
+			ex.kids[parent] = append(ex.kids[parent], idx)
+			ex.parent[idx] = parent
+		}
+		if entry.Children {
+			nesting = append(nesting, idx)
+		}
+	}
+	if len(nesting) > 0 {
+		return errors.New("unterminated child sequence")
+	}
+	return nil
+}
+
+func (e *Examiner) DIEs() []*dwarf.Entry {
+	return e.dies
+}
+
+func indent(ilevel int) {
+	for i := 0; i < ilevel; i++ {
+		fmt.Printf("  ")
+	}
+}
+
+// For debugging new tests
+func (ex *Examiner) DumpEntry(idx int, dumpKids bool, ilevel int) {
+	if idx >= len(ex.dies) {
+		fmt.Fprintf(os.Stderr, "DumpEntry: bad DIE %d: index out of range\n", idx)
+		return
+	}
+	entry := ex.dies[idx]
+	indent(ilevel)
+	fmt.Printf("0x%x: %v\n", idx, entry.Tag)
+	for _, f := range entry.Field {
+		indent(ilevel)
+		fmt.Printf("at=%v val=0x%x\n", f.Attr, f.Val)
+	}
+	if dumpKids {
+		ksl := ex.kids[idx]
+		for _, k := range ksl {
+			ex.DumpEntry(k, true, ilevel+2)
+		}
+	}
+}
+
+// Given a DIE offset, return the previously read dwarf.Entry, or nil
+func (ex *Examiner) EntryFromOffset(off dwarf.Offset) *dwarf.Entry {
+	if idx, found := ex.idxByOffset[off]; found && idx != -1 {
+		return ex.entryFromIdx(idx)
+	}
+	return nil
+}
+
+// Return the ID that Examiner uses to refer to the DIE at offset off
+func (ex *Examiner) IdxFromOffset(off dwarf.Offset) int {
+	if idx, found := ex.idxByOffset[off]; found {
+		return idx
+	}
+	return -1
+}
+
+// Return the dwarf.Entry pointer for the DIE with id 'idx'
+func (ex *Examiner) entryFromIdx(idx int) *dwarf.Entry {
+	if idx >= len(ex.dies) || idx < 0 {
+		return nil
+	}
+	return ex.dies[idx]
+}
+
+// Returns a list of child entries for a die with ID 'idx'
+func (ex *Examiner) Children(idx int) []*dwarf.Entry {
+	sl := ex.kids[idx]
+	ret := make([]*dwarf.Entry, len(sl))
+	for i, k := range sl {
+		ret[i] = ex.entryFromIdx(k)
+	}
+	return ret
+}
+
+// Returns parent DIE for DIE 'idx', or nil if the DIE is top level
+func (ex *Examiner) Parent(idx int) *dwarf.Entry {
+	p, found := ex.parent[idx]
+	if !found {
+		return nil
+	}
+	return ex.entryFromIdx(p)
+}
+
+// ParentCU returns the enclosing compilation unit DIE for the DIE
+// with a given index, or nil if for some reason we can't establish a
+// parent.
+func (ex *Examiner) ParentCU(idx int) *dwarf.Entry {
+	for {
+		parentDie := ex.Parent(idx)
+		if parentDie == nil {
+			return nil
+		}
+		if parentDie.Tag == dwarf.TagCompileUnit {
+			return parentDie
+		}
+		idx = ex.IdxFromOffset(parentDie.Offset)
+	}
+}
+
+// FileRef takes a given DIE by index and a numeric file reference
+// (presumably from a decl_file or call_file attribute), looks up the
+// reference in the .debug_line file table, and returns the proper
+// string for it. We need to know which DIE is making the reference
+// so as to find the right compilation unit.
+func (ex *Examiner) FileRef(dw *dwarf.Data, dieIdx int, fileRef int64) (string, error) {
+
+	// Find the parent compilation unit DIE for the specified DIE.
+	cuDie := ex.ParentCU(dieIdx)
+	if cuDie == nil {
+		return "", fmt.Errorf("no parent CU DIE for DIE with idx %d?", dieIdx)
+	}
+	// Construct a line reader and then use it to get the file string.
+	lr, lrerr := dw.LineReader(cuDie)
+	if lrerr != nil {
+		return "", fmt.Errorf("d.LineReader: %v", lrerr)
+	}
+	files := lr.Files()
+	if fileRef < 0 || int(fileRef) > len(files)-1 {
+		return "", fmt.Errorf("Examiner.FileRef: malformed file reference %d", fileRef)
+	}
+	return files[fileRef].Name, nil
+}
+
+// Return a list of all DIEs with name 'name'. When searching for DIEs
+// by name, keep in mind that the returned results will include child
+// DIEs such as params/variables. For example, asking for all DIEs named
+// "p" for even a small program will give you 400-500 entries.
+func (ex *Examiner) Named(name string) []*dwarf.Entry {
+	sl := ex.byname[name]
+	ret := make([]*dwarf.Entry, len(sl))
+	for i, k := range sl {
+		ret[i] = ex.entryFromIdx(k)
+	}
+	return ret
+}
diff --git a/dwtest/testdata/dwdumploc.go b/dwtest/testdata/dwdumploc.go
new file mode 100644
index 0000000..69ee702
--- /dev/null
+++ b/dwtest/testdata/dwdumploc.go
@@ -0,0 +1,486 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+// This file is the source code for a test helper binary 'dwdumploc',
+// which is built and then run by the tests in cmd/link/internal/dwtest.
+// It is set up to import packages "op" and "proc" from Delve (so
+// as to use Delve's DWARF location expression parser), and the
+// "dwtest" package from cmd/link/internal/dwtest (for general
+// DWARF examination/inspection).
+//
+// Given an input Go binary and a function name, this program
+// visits the DWARF for the function and dumps out the location expressions
+// for the function's input parameters on entry to the func. Example:
+//
+// ./dumpdwloc.exe -m ./dumpdwloc.exe -f main.locateFuncDetails
+// 1: in-param "executable" loc="{ [0: S=8 RAX] [1: S=8 RBX] }"
+// 2: in-param "fcn" loc="{ [0: S=8 RCX] [1: S=8 RDI] }"
+// 3: out-param "~r0" loc="<not available>"
+// 4: out-param "~r1" loc="<not available>"
+//
+// Since location expressions can refer to machine registers, dump
+// output for location expressions are architecture-dependent.  The
+// dumper tool currently supports two archs: amd64 and arm64.
+//
+
+import (
+	"debug/dwarf"
+	"debug/elf"
+	"debug/macho"
+	"debug/pe"
+	"dwdumploc/dwtest"
+	"flag"
+	"fmt"
+	"log"
+	"os"
+	"runtime"
+	"strings"
+
+	"github.com/go-delve/delve/pkg/dwarf/op"
+	"github.com/go-delve/delve/pkg/proc"
+)
+
+var verbflag = flag.Int("v", 0, "Verbose trace output level")
+var fcnflag = flag.String("f", "", "name of function to display")
+var moduleflag = flag.String("m", "", "load module to read")
+
+func verb(vlevel int, s string, a ...interface{}) {
+	if *verbflag >= vlevel {
+		fmt.Fprintf(os.Stderr, s, a...)
+		fmt.Fprintf(os.Stderr, "\n")
+	}
+}
+
+func warn(s string, a ...interface{}) {
+	fmt.Fprintf(os.Stderr, s, a...)
+	fmt.Fprintf(os.Stderr, "\n")
+}
+
+func usage(msg string) {
+	if len(msg) > 0 {
+		fmt.Fprintf(os.Stderr, "error: %s\n", msg)
+	}
+	fmt.Fprintf(os.Stderr, "usage: dwdumploc [flags] -m <binary> -f <fcn>\n")
+	flag.PrintDefaults()
+	os.Exit(2)
+}
+
+type finfo struct {
+	name     string
+	dwOffset dwarf.Offset
+	dwLoPC   uint64
+	dwHiPC   uint64
+	valid    bool
+	dwx      *dwtest.Examiner
+}
+
+// opener defines an interface for opening DWARF info in a Go binary.
+type opener interface {
+	// Open examines binary pointed to by 'path', returning a pointer
+	// to debug/dwarf.Data for it. If something goes wrong opening the
+	// binary (file missing, or maybe not a supported binary) a
+	// suitable error will be returned in the first error return. If
+	// something goes wrong reading the DWARF, a suitable error will
+	// be returned in the second error return.
+	Open(path string) (*dwarf.Data, error, error)
+	Which() string
+}
+
+// Remark: the boilerplate for three scenarios below (Elf, Macho, and
+// PE) seems like it should be an ideal scenario where you could use
+// generics, but my attempts at this were not especially successful.
+// The sticking point is that each of elf.Open/macho.Open etc return a
+// different thing, and I couldn't quite figure out how to get this to
+// work with generics. TODO: make another attempt.
+
+// elfOpener implements the opener interface for ELF
+// (https://en.wikipedia.org/wiki/Executable_and_Linkable_Format) binaries
+//
+type elfOpener struct {
+}
+
+func (eo *elfOpener) Open(path string) (*dwarf.Data, error, error) {
+	f, err := elf.Open(path)
+	if err != nil {
+		return nil, err, nil
+	}
+	d, err := f.DWARF()
+	if err != nil {
+		return nil, nil, err
+	}
+	return d, nil, nil
+}
+
+func (eo *elfOpener) Which() string {
+	return "elf"
+}
+
+// machoOpener implements the opener for macos/darwin Macho-O
+// (https://en.wikipedia.org/wiki/Mach-O) binaries.
+type machoOpener struct {
+}
+
+func (mo *machoOpener) Open(path string) (*dwarf.Data, error, error) {
+	f, err := macho.Open(path)
+	if err != nil {
+		return nil, err, nil
+	}
+	d, err := f.DWARF()
+	if err != nil {
+		return nil, nil, err
+	}
+	return d, nil, nil
+}
+
+func (mo *machoOpener) Which() string {
+	return "macho"
+}
+
+// peOpener implements the opener interface for Windows PE
+// (https://en.wikipedia.org/wiki/Portable_Executable) binaries.
+type peOpener struct {
+}
+
+func (po *peOpener) Open(path string) (*dwarf.Data, error, error) {
+	f, err := pe.Open(path)
+	if err != nil {
+		return nil, err, nil
+	}
+	d, err := f.DWARF()
+	if err != nil {
+		return nil, nil, err
+	}
+	return d, nil, nil
+}
+
+func (po *peOpener) Which() string {
+	return "pe"
+}
+
+// openDwarf tries to open the Go binary 'path' as an ELF file,
+// a Macho file, or a PE file in turn. If an open succeeds, it
+// returns a dwarf.Data for the binary; if they all fail, it returns
+// an error.
+func openDwarf(path string) (*dwarf.Data, error) {
+	eo := elfOpener{}
+	mo := machoOpener{}
+	po := peOpener{}
+	var eoo opener = &eo
+	var moo opener = &mo
+	var poo opener = &po
+	openers := []opener{eoo, moo, poo}
+	postmortem := ""
+	for k, o := range openers {
+		d, errf, errd := o.Open(path)
+		if d != nil {
+			return d, nil
+		}
+		if errd != nil {
+			return nil, errd
+		}
+		postmortem += fmt.Sprintf("\tattempt %d (%s) failed: %v\n", k, o.Which(), errf)
+	}
+	return nil, fmt.Errorf("init failed:\n%s", postmortem)
+}
+
+// locateFuncDetails walks the DWARF for Go binary 'executable'
+// looking for a subprogram DIE for function 'fcn'. If it finds a
+// function of the right name, it fills in info from the DWARF into
+// a "finfo" struct and returns it, or returns an empty struct
+// and an error if something went wrong.
+func locateFuncDetails(executable string, fcn string) (finfo, error) {
+	rrv := finfo{}
+
+	if inf, err := os.Open(executable); err != nil {
+		return rrv, fmt.Errorf("unable to open input executable %s: %v", executable, err)
+	} else {
+		inf.Close()
+	}
+
+	verb(1, "loading DWARF for %s", executable)
+	d, err := openDwarf(executable)
+	if err != nil {
+		return finfo{}, err
+	}
+	verb(1, "DWARF loaded for %s", executable)
+
+	// Construct dwtest.Examiner helper object, to make
+	// inspection of the DWARF easier.
+	rdr := d.Reader()
+	dwx := dwtest.Examiner{}
+	if err := dwx.Populate(rdr); err != nil {
+		return finfo{}, fmt.Errorf("error reading DWARF: %v", err)
+	}
+
+	// Walk DIEs looking for subprogram DIEs.
+	dies := dwx.DIEs()
+	for idx := 0; idx < len(dies); idx++ {
+		die := dies[idx]
+		off := die.Offset
+		if die.Tag == dwarf.TagCompileUnit {
+			if name, ok := die.Val(dwarf.AttrName).(string); ok {
+				verb(2, "compilation unit: %s", name)
+			}
+			continue
+		}
+		if die.Tag != dwarf.TagSubprogram {
+			// TODO: skip children
+			continue
+		}
+		// Name has to match the function we're looking for.
+		name, ok := die.Val(dwarf.AttrName).(string)
+		if !ok {
+			continue
+		}
+		if name != fcn {
+			// TODO: skip children
+			continue
+		}
+
+		verb(1, "found function %s at offset %x", fcn, off)
+		rrv.dwOffset = off
+		if lopc, ok := die.Val(dwarf.AttrLowpc).(uint64); ok {
+			rrv.dwLoPC = lopc
+		} else {
+			return finfo{}, fmt.Errorf("target function seems to be missing LowPC attribute")
+		}
+		if hipc, ok := die.Val(dwarf.AttrHighpc).(uint64); ok {
+			rrv.dwHiPC = hipc
+		} else {
+			return finfo{}, fmt.Errorf("target function seems to be missing HighPC attribute")
+		}
+		rrv.dwx = &dwx
+		rrv.name = fcn
+		rrv.valid = true
+		return rrv, nil
+	}
+	return finfo{}, fmt.Errorf("could not locate target function in DWARF")
+}
+
+var AMD64DWARFRegisters = map[int]string{
+	0:  "RAX",
+	1:  "RDX",
+	2:  "RCX",
+	3:  "RBX",
+	4:  "RSI",
+	5:  "RDI",
+	6:  "RBP",
+	7:  "RSP",
+	8:  "R8",
+	9:  "R9",
+	10: "R10",
+	11: "R11",
+	12: "R12",
+	13: "R13",
+	14: "R14",
+	15: "R15",
+	17: "X0",
+	18: "X1",
+	19: "X2",
+	20: "X3",
+	21: "X4",
+	22: "X5",
+	23: "X6",
+	24: "X7",
+	25: "X8",
+	26: "X9",
+	27: "X10",
+	28: "X11",
+	29: "X12",
+	30: "X13",
+	31: "X14",
+	32: "X15",
+}
+
+var ARM64DWARFRegisters = map[int]string{
+	// int
+	0:  "R0",
+	1:  "R1",
+	2:  "R2",
+	3:  "R3",
+	4:  "R4",
+	5:  "R5",
+	6:  "R6",
+	7:  "R7",
+	8:  "R8",
+	9:  "R9",
+	10: "R10",
+	11: "R11",
+	12: "R12",
+	13: "R13",
+	14: "R14",
+	15: "R15",
+	16: "R16",
+	17: "R17",
+	18: "R18",
+	19: "R19",
+	20: "R20",
+	21: "R21",
+	22: "R22",
+	23: "R23",
+	24: "R24",
+	25: "R25",
+	26: "R26",
+	27: "R27",
+	28: "R28",
+	29: "R29",
+	30: "R30",
+
+	// float
+	64: "F0",
+	65: "F1",
+	66: "F2",
+	67: "F3",
+	68: "F4",
+	69: "F5",
+	70: "F6",
+	71: "F7",
+	72: "F8",
+	73: "F9",
+	74: "F10",
+	75: "F11",
+	76: "F12",
+	77: "F13",
+	78: "F14",
+	79: "F15",
+	80: "F16",
+	81: "F17",
+	82: "F18",
+	83: "F19",
+	84: "F20",
+	85: "F21",
+	86: "F22",
+	87: "F23",
+	88: "F24",
+	89: "F25",
+	90: "F26",
+	91: "F27",
+	92: "F28",
+	93: "F29",
+	94: "F30",
+	95: "F31",
+}
+
+func regString(dwreg int) string {
+	var v string
+	switch runtime.GOARCH {
+	case "amd64":
+		v = AMD64DWARFRegisters[dwreg]
+	case "arm64":
+		v = ARM64DWARFRegisters[dwreg]
+	default:
+		panic(fmt.Sprintf("no support for arch %s", runtime.GOARCH))
+	}
+	if v != "" {
+		return v
+	}
+	return fmt.Sprintf("reg=%d", dwreg)
+}
+
+func pstring(addr int64, pcs []op.Piece, err error) (string, error) {
+	if err != nil {
+		serr := fmt.Sprintf("%s", err)
+		if strings.HasPrefix(serr, "could not find loclist entry") {
+			return "<not available>", nil
+		}
+		return "", err
+	}
+	if pcs == nil {
+		return fmt.Sprintf("addr=%x", addr), nil
+	}
+	r := "{"
+	for k, p := range pcs {
+		r += fmt.Sprintf(" [%d: S=%d", k, p.Size)
+		if p.Kind == op.RegPiece {
+			r += fmt.Sprintf(" %s]", regString(int(p.Val)))
+		} else {
+			r += fmt.Sprintf(" addr=0x%x]", p.Val)
+		}
+	}
+	r += " }"
+	return r, nil
+}
+
+// processParams initializes a 'proc.BinaryInfo' object for the binary
+// in question and then walks the formal parameters of the selected
+// function, invoking the Location method on each param to read its
+// location expression. Results are dumped to stdout, and an error is
+// returned if something goes wrong.
+func processParams(executable string, fi *finfo) error {
+	const _cfa = 0x1000
+	bi := proc.NewBinaryInfo(runtime.GOOS, runtime.GOARCH)
+	if err := bi.LoadBinaryInfo(executable, 0, []string{}); err != nil {
+		return err
+	}
+
+	// Walk subprogram DIE's children.
+	pidx := fi.dwx.IdxFromOffset(fi.dwOffset)
+	childDies := fi.dwx.Children(pidx)
+	idx := 0
+	for _, e := range childDies {
+		if e.Tag != dwarf.TagFormalParameter {
+			continue
+		}
+		if e.Val(dwarf.AttrName) == nil {
+			continue
+		}
+		idx++
+		name := e.Val(dwarf.AttrName).(string)
+		var isrvar bool
+		if e.Tag == dwarf.TagFormalParameter {
+			isrvar = e.Val(dwarf.AttrVarParam).(bool)
+		}
+		addr, pieces, _, err := bi.Location(e, dwarf.AttrLocation, fi.dwLoPC, op.DwarfRegisters{CFA: _cfa, FrameBase: _cfa}, nil)
+		pdump, err := pstring(addr, pieces, err)
+		if err != nil {
+			if fmt.Sprintf("%s", err) == "empty OP stack" {
+				pdump = "<not available>"
+			} else {
+				return fmt.Errorf("bad return from bi.Location at pc 0x%x: %q\n", fi.dwLoPC, err)
+			}
+		}
+		wh := "in"
+		if isrvar {
+			wh = "out"
+		}
+		fmt.Printf("%d: %s-param %q loc=%q\n", idx, wh, name, pdump)
+
+	}
+	return nil
+
+}
+
+// examineFile kicks off the search for DWARF info for 'fcn' within
+// Go binary 'executable', printing results to stdout if possible.
+func examineFile(executable string, fcn string) {
+	verb(1, "examineFile(%s,%s)", executable, fcn)
+	fi, err := locateFuncDetails(executable, fcn)
+	if err != nil {
+		log.Fatalf("error: %v\n", err)
+	}
+	if !fi.valid {
+		log.Fatalf("could not locate target function %s in executable %s", fcn, executable)
+
+	}
+	if err := processParams(executable, &fi); err != nil {
+		log.Fatalf("error: %v\n", err)
+	}
+}
+
+func main() {
+	log.SetFlags(0)
+	log.SetPrefix("dwdumploc: ")
+	flag.Parse()
+	verb(1, "in main")
+	if *fcnflag == "" || *moduleflag == "" {
+		usage("please supply -f and -m options")
+	}
+	if flag.NArg() != 0 {
+		usage("unexpected additional arguments")
+	}
+	examineFile(*moduleflag, *fcnflag)
+	verb(1, "leaving main")
+}
diff --git a/dwtest/testdata/go.mod.txt b/dwtest/testdata/go.mod.txt
new file mode 100644
index 0000000..2140a8c
--- /dev/null
+++ b/dwtest/testdata/go.mod.txt
@@ -0,0 +1,14 @@
+module dwdumploc
+
+go 1.18
+
+require github.com/go-delve/delve v1.7.2
+
+require (
+	github.com/aquasecurity/libbpfgo v0.1.2-0.20210708203834-4928d36fafac // indirect
+	github.com/hashicorp/golang-lru v0.5.4 // indirect
+	github.com/konsorten/go-windows-terminal-sequences v1.0.3 // indirect
+	github.com/sirupsen/logrus v1.6.0 // indirect
+	golang.org/x/arch v0.0.0-20190927153633-4e8777c89be4 // indirect
+	golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 // indirect
+)
diff --git a/dwtest/testdata/go.sum.txt b/dwtest/testdata/go.sum.txt
new file mode 100644
index 0000000..47790ab
--- /dev/null
+++ b/dwtest/testdata/go.sum.txt
@@ -0,0 +1,317 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
+cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
+cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
+cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
+cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
+cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
+cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
+cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/aquasecurity/libbpfgo v0.1.2-0.20210708203834-4928d36fafac h1:oehMMAySC3p8eSwcwQ8lTXxeCkkPll+AwNesUNowbJ8=
+github.com/aquasecurity/libbpfgo v0.1.2-0.20210708203834-4928d36fafac/go.mod h1:/+clceXE103FaXvVTIY2HAkQjxNtkra4DRWvZYr2SKw=
+github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
+github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
+github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
+github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
+github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
+github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
+github.com/cosiner/argv v0.1.0/go.mod h1:EusR6TucWKX+zFgtdUsKT2Cvg45K5rtpCcWz4hK06d8=
+github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/derekparker/trie v0.0.0-20200317170641-1fdf38b7b0e9/go.mod h1:D6ICZm05D9VN1n/8iOtBxLpXtoGp6HDFUJ1RNVieOSE=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/go-delve/delve v1.7.2 h1:QTDJlgx9OwUVYVm7xthyf2XHKrZcTQu3wkRbovktidM=
+github.com/go-delve/delve v1.7.2/go.mod h1:CHdOd8kuHlQxtBJr1HmJX5h+KmmWd/7Lk5d+D1zHn4E=
+github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-dap v0.5.1-0.20210713061233-c91b005e3987/go.mod h1:5q8aYQFnHOAZEMP+6vmq25HKYAEwE+LF5yh7JKrrhSQ=
+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
+github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
+github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
+github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
+github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
+github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
+github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
+github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
+github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
+github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
+github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
+github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
+github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
+github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
+github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI=
+github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
+github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
+github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
+github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
+github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
+github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
+github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/peterh/liner v0.0.0-20170317030525-88609521dc4b/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
+github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
+github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
+github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
+github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
+github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
+github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
+github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
+github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
+github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
+github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
+go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
+go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
+go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
+go.starlark.net v0.0.0-20200821142938-949cc6f4b097/go.mod h1:f0znQkUKRrkk36XxWbGjMqQM8wGv/xHBVE2qc3B5oFU=
+go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
+go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+golang.org/x/arch v0.0.0-20190927153633-4e8777c89be4 h1:QlVATYS7JBoZMVaf+cNjb90WD/beKVHnIxFKT4QaHVI=
+golang.org/x/arch v0.0.0-20190927153633-4e8777c89be4/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
+golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
+golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
+golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
+golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 h1:hZR0X1kPW+nwyJ9xRxqZk1vx5RUObAPBdKVvXPDUH/E=
+golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191127201027-ecd32218bd7f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
+google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
+google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
+gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
+rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
diff --git a/internal/testenv/testenv.go b/internal/testenv/testenv.go
new file mode 100644
index 0000000..a48d7de
--- /dev/null
+++ b/internal/testenv/testenv.go
@@ -0,0 +1,133 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// The helper functions in this package are copied from the original
+// versions from the file of the same name in $GOROOT/src/internal.
+
+package testenv
+
+import (
+	"errors"
+	"fmt"
+	"go/build"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"runtime"
+	"testing"
+)
+
+// HasGoBuild reports whether the current system can build programs
+// with ``go build'' and then run them with os.StartProcess or
+// exec.Command.
+func HasGoBuild() bool {
+	if os.Getenv("GO_GCFLAGS") != "" {
+		// It's too much work to require every caller of the go command
+		// to pass along "-gcflags="+os.Getenv("GO_GCFLAGS").
+		// For now, if $GO_GCFLAGS is set, report that we simply can't
+		// run go build.
+		return false
+	}
+	switch runtime.GOOS {
+	case "android", "js", "ios":
+		return false
+	}
+	return true
+}
+
+// MustHaveGoBuild checks that the current system can build programs with ``go build''
+// and then run them with os.StartProcess or exec.Command.
+// If not, MustHaveGoBuild calls t.Skip with an explanation.
+func MustHaveGoBuild(t testing.TB) {
+	if os.Getenv("GO_GCFLAGS") != "" {
+		t.Skipf("skipping test: 'go build' not compatible with setting $GO_GCFLAGS")
+	}
+	if !HasGoBuild() {
+		t.Skipf("skipping test: 'go build' not available on %s/%s", runtime.GOOS, runtime.GOARCH)
+	}
+}
+
+// GoToolPath reports the path to the Go tool.
+// It is a convenience wrapper around GoTool.
+// If the tool is unavailable GoToolPath calls t.Skip.
+// If the tool should be available and isn't, GoToolPath calls t.Fatal.
+func GoToolPath(t testing.TB) string {
+	MustHaveGoBuild(t)
+	path, err := GoTool()
+	if err != nil {
+		t.Fatal(err)
+	}
+	return path
+}
+
+// GoTool reports the path to the Go tool.
+func GoTool() (string, error) {
+	if !HasGoBuild() {
+		return "", errors.New("platform cannot run go tool")
+	}
+	var exeSuffix string
+	if runtime.GOOS == "windows" {
+		exeSuffix = ".exe"
+	}
+	path := filepath.Join(runtime.GOROOT(), "bin", "go"+exeSuffix)
+	if _, err := os.Stat(path); err == nil {
+		return path, nil
+	}
+	goBin, err := exec.LookPath("go" + exeSuffix)
+	if err != nil {
+		return "", errors.New("cannot find go tool: " + err.Error())
+	}
+	return goBin, nil
+}
+
+// HasExternalNetwork reports whether the current system can use
+// external (non-localhost) networks.
+func HasExternalNetwork() bool {
+	return !testing.Short() && runtime.GOOS != "js"
+}
+
+// MustHaveExternalNetwork checks that the current system can use
+// external (non-localhost) networks.
+// If not, MustHaveExternalNetwork calls t.Skip with an explanation.
+func MustHaveExternalNetwork(t testing.TB) {
+	if runtime.GOOS == "js" {
+		t.Skipf("skipping test: no external network on %s", runtime.GOOS)
+	}
+	if testing.Short() {
+		t.Skipf("skipping test: no external network in -short mode")
+	}
+}
+
+// Go1Point returns the x in Go 1.x.
+func Go1Point() int {
+	for i := len(build.Default.ReleaseTags) - 1; i >= 0; i-- {
+		var version int
+		if _, err := fmt.Sscanf(build.Default.ReleaseTags[i], "go1.%d", &version); err != nil {
+			continue
+		}
+		return version
+	}
+	panic("bad release tags")
+}
+
+// Testing is an abstraction of a *testing.T.
+type Testing interface {
+	Skipf(format string, args ...interface{})
+	Fatalf(format string, args ...interface{})
+}
+
+type helperer interface {
+	Helper()
+}
+
+// NeedsGo1Point skips t if the Go version used to run the test is
+// older than 1.x.
+func NeedsGo1Point(t Testing, x int) {
+	if t, ok := t.(helperer); ok {
+		t.Helper()
+	}
+	if Go1Point() < x {
+		t.Skipf("running Go version %q is version 1.%d, older than required 1.%d", runtime.Version(), Go1Point(), x)
+	}
+}