mirror of
				https://github.com/go-delve/delve.git
				synced 2025-10-31 10:47:27 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			528 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			528 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package core
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"flag"
 | |
| 	"fmt"
 | |
| 	"go/constant"
 | |
| 	"io/ioutil"
 | |
| 	"os"
 | |
| 	"os/exec"
 | |
| 	"path"
 | |
| 	"path/filepath"
 | |
| 	"reflect"
 | |
| 	"runtime"
 | |
| 	"strings"
 | |
| 	"testing"
 | |
| 
 | |
| 	"github.com/go-delve/delve/pkg/goversion"
 | |
| 	"github.com/go-delve/delve/pkg/proc"
 | |
| 	"github.com/go-delve/delve/pkg/proc/test"
 | |
| )
 | |
| 
 | |
| var buildMode string
 | |
| 
 | |
| func TestMain(m *testing.M) {
 | |
| 	flag.StringVar(&buildMode, "test-buildmode", "", "selects build mode")
 | |
| 	flag.Parse()
 | |
| 	if buildMode != "" && buildMode != "pie" {
 | |
| 		fmt.Fprintf(os.Stderr, "unknown build mode %q", buildMode)
 | |
| 		os.Exit(1)
 | |
| 	}
 | |
| 	os.Exit(test.RunTestsWithFixtures(m))
 | |
| }
 | |
| 
 | |
| func assertNoError(err error, t testing.TB, s string) {
 | |
| 	if err != nil {
 | |
| 		_, file, line, _ := runtime.Caller(1)
 | |
| 		fname := filepath.Base(file)
 | |
| 		t.Fatalf("failed assertion at %s:%d: %s - %s\n", fname, line, s, err)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestSplicedReader(t *testing.T) {
 | |
| 	data := []byte{}
 | |
| 	data2 := []byte{}
 | |
| 	for i := 0; i < 100; i++ {
 | |
| 		data = append(data, byte(i))
 | |
| 		data2 = append(data2, byte(i+100))
 | |
| 	}
 | |
| 
 | |
| 	type region struct {
 | |
| 		data   []byte
 | |
| 		off    uint64
 | |
| 		length uint64
 | |
| 	}
 | |
| 	tests := []struct {
 | |
| 		name     string
 | |
| 		regions  []region
 | |
| 		readAddr uint64
 | |
| 		readLen  int
 | |
| 		want     []byte
 | |
| 	}{
 | |
| 		{
 | |
| 			"Insert after",
 | |
| 			[]region{
 | |
| 				{data, 0, 1},
 | |
| 				{data2, 1, 1},
 | |
| 			},
 | |
| 			0,
 | |
| 			2,
 | |
| 			[]byte{0, 101},
 | |
| 		},
 | |
| 		{
 | |
| 			"Insert before",
 | |
| 			[]region{
 | |
| 				{data, 1, 1},
 | |
| 				{data2, 0, 1},
 | |
| 			},
 | |
| 			0,
 | |
| 			2,
 | |
| 			[]byte{100, 1},
 | |
| 		},
 | |
| 		{
 | |
| 			"Completely overwrite",
 | |
| 			[]region{
 | |
| 				{data, 1, 1},
 | |
| 				{data2, 0, 3},
 | |
| 			},
 | |
| 			0,
 | |
| 			3,
 | |
| 			[]byte{100, 101, 102},
 | |
| 		},
 | |
| 		{
 | |
| 			"Overwrite end",
 | |
| 			[]region{
 | |
| 				{data, 0, 2},
 | |
| 				{data2, 1, 2},
 | |
| 			},
 | |
| 			0,
 | |
| 			3,
 | |
| 			[]byte{0, 101, 102},
 | |
| 		},
 | |
| 		{
 | |
| 			"Overwrite start",
 | |
| 			[]region{
 | |
| 				{data, 0, 3},
 | |
| 				{data2, 0, 2},
 | |
| 			},
 | |
| 			0,
 | |
| 			3,
 | |
| 			[]byte{100, 101, 2},
 | |
| 		},
 | |
| 		{
 | |
| 			"Punch hole",
 | |
| 			[]region{
 | |
| 				{data, 0, 5},
 | |
| 				{data2, 1, 3},
 | |
| 			},
 | |
| 			0,
 | |
| 			5,
 | |
| 			[]byte{0, 101, 102, 103, 4},
 | |
| 		},
 | |
| 		{
 | |
| 			"Overlap two",
 | |
| 			[]region{
 | |
| 				{data, 10, 4},
 | |
| 				{data, 14, 4},
 | |
| 				{data2, 12, 4},
 | |
| 			},
 | |
| 			10,
 | |
| 			8,
 | |
| 			[]byte{10, 11, 112, 113, 114, 115, 16, 17},
 | |
| 		},
 | |
| 	}
 | |
| 	for _, test := range tests {
 | |
| 		t.Run(test.name, func(t *testing.T) {
 | |
| 			mem := &splicedMemory{}
 | |
| 			for _, region := range test.regions {
 | |
| 				r := bytes.NewReader(region.data)
 | |
| 				mem.Add(&offsetReaderAt{r, 0}, region.off, region.length)
 | |
| 			}
 | |
| 			got := make([]byte, test.readLen)
 | |
| 			n, err := mem.ReadMemory(got, test.readAddr)
 | |
| 			if n != test.readLen || err != nil || !reflect.DeepEqual(got, test.want) {
 | |
| 				t.Errorf("ReadAt = %v, %v, %v, want %v, %v, %v", n, err, got, test.readLen, nil, test.want)
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| 
 | |
| 	// Test some ReadMemory errors
 | |
| 
 | |
| 	mem := &splicedMemory{}
 | |
| 	for _, region := range []region{
 | |
| 		{[]byte{0xa1, 0xa2, 0xa3, 0xa4}, 0x1000, 4},
 | |
| 		{[]byte{0xb1, 0xb2, 0xb3, 0xb4}, 0x1004, 4},
 | |
| 		{[]byte{0xc1, 0xc2, 0xc3, 0xc4}, 0x1010, 4},
 | |
| 	} {
 | |
| 		r := bytes.NewReader(region.data)
 | |
| 		mem.Add(&offsetReaderAt{r, region.off}, region.off, region.length)
 | |
| 	}
 | |
| 
 | |
| 	got := make([]byte, 4)
 | |
| 
 | |
| 	// Read before the first mapping
 | |
| 	_, err := mem.ReadMemory(got, 0x900)
 | |
| 	if err == nil || !strings.HasPrefix(err.Error(), "error while reading spliced memory at 0x900") {
 | |
| 		t.Errorf("Read before the start of memory didn't fail (or wrong error): %v", err)
 | |
| 	}
 | |
| 
 | |
| 	// Read after the last mapping
 | |
| 	_, err = mem.ReadMemory(got, 0x1100)
 | |
| 	if err == nil || (err.Error() != "offset 4352 did not match any regions") {
 | |
| 		t.Errorf("Read after the end of memory didn't fail (or wrong error): %v", err)
 | |
| 	}
 | |
| 
 | |
| 	// Read at the start of the first entry
 | |
| 	_, err = mem.ReadMemory(got, 0x1000)
 | |
| 	if err != nil || !bytes.Equal(got, []byte{0xa1, 0xa2, 0xa3, 0xa4}) {
 | |
| 		t.Errorf("Reading at the start of the first entry: %v %#x", err, got)
 | |
| 	}
 | |
| 
 | |
| 	// Read at the start of the second entry
 | |
| 	_, err = mem.ReadMemory(got, 0x1004)
 | |
| 	if err != nil || !bytes.Equal(got, []byte{0xb1, 0xb2, 0xb3, 0xb4}) {
 | |
| 		t.Errorf("Reading at the start of the second entry: %v %#x", err, got)
 | |
| 	}
 | |
| 
 | |
| 	// Read straddling entries 1 and 2
 | |
| 	_, err = mem.ReadMemory(got, 0x1002)
 | |
| 	if err != nil || !bytes.Equal(got, []byte{0xa3, 0xa4, 0xb1, 0xb2}) {
 | |
| 		t.Errorf("Straddled read of the second entry: %v %#x", err, got)
 | |
| 	}
 | |
| 
 | |
| 	// Read past the end of the second entry
 | |
| 	_, err = mem.ReadMemory(got, 0x1007)
 | |
| 	if err == nil || !strings.HasPrefix(err.Error(), "error while reading spliced memory at 0x1008") {
 | |
| 		t.Errorf("Read into gap: %v", err)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func withCoreFile(t *testing.T, name, args string) *proc.Target {
 | |
| 	// This is all very fragile and won't work on hosts with non-default core patterns.
 | |
| 	// Might be better to check in the binary and core?
 | |
| 	tempDir := t.TempDir()
 | |
| 	var buildFlags test.BuildFlags
 | |
| 	if buildMode == "pie" {
 | |
| 		buildFlags = test.BuildModePIE
 | |
| 	}
 | |
| 	fix := test.BuildFixture(name, buildFlags)
 | |
| 	bashCmd := fmt.Sprintf("cd %v && ulimit -c unlimited && GOTRACEBACK=crash %v %s", tempDir, fix.Path, args)
 | |
| 	exec.Command("bash", "-c", bashCmd).Run()
 | |
| 	cores, err := filepath.Glob(path.Join(tempDir, "core*"))
 | |
| 	switch {
 | |
| 	case err != nil || len(cores) > 1:
 | |
| 		t.Fatalf("Got %v, wanted one file named core* in %v", cores, tempDir)
 | |
| 	case len(cores) == 0:
 | |
| 		t.Skipf("core file was not produced, could not run test")
 | |
| 		return nil
 | |
| 	}
 | |
| 	corePath := cores[0]
 | |
| 
 | |
| 	p, err := OpenCore(corePath, fix.Path, []string{})
 | |
| 	if err != nil {
 | |
| 		t.Errorf("OpenCore(%q) failed: %v", corePath, err)
 | |
| 		pat, err := ioutil.ReadFile("/proc/sys/kernel/core_pattern")
 | |
| 		t.Errorf("read core_pattern: %q, %v", pat, err)
 | |
| 		apport, err := ioutil.ReadFile("/var/log/apport.log")
 | |
| 		t.Errorf("read apport log: %q, %v", apport, err)
 | |
| 		t.Fatalf("previous errors")
 | |
| 	}
 | |
| 	return p
 | |
| }
 | |
| 
 | |
| func logRegisters(t *testing.T, regs proc.Registers, arch *proc.Arch) {
 | |
| 	dregs := arch.RegistersToDwarfRegisters(0, regs)
 | |
| 	dregs.Reg(^uint64(0))
 | |
| 	for i := 0; i < dregs.CurrentSize(); i++ {
 | |
| 		reg := dregs.Reg(uint64(i))
 | |
| 		if reg == nil {
 | |
| 			continue
 | |
| 		}
 | |
| 		name, _, value := arch.DwarfRegisterToString(i, reg)
 | |
| 		t.Logf("%s = %s", name, value)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestCore(t *testing.T) {
 | |
| 	if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" {
 | |
| 		return
 | |
| 	}
 | |
| 	if runtime.GOOS == "linux" && os.Getenv("CI") == "true" && buildMode == "pie" {
 | |
| 		t.Skip("disabled on linux, Github Actions, with PIE buildmode")
 | |
| 	}
 | |
| 	p := withCoreFile(t, "panic", "")
 | |
| 	grp := proc.NewGroup(p)
 | |
| 
 | |
| 	recorded, _ := grp.Recorded()
 | |
| 	if !recorded {
 | |
| 		t.Fatalf("expecting recorded to be true")
 | |
| 	}
 | |
| 
 | |
| 	gs, _, err := proc.GoroutinesInfo(p, 0, 0)
 | |
| 	if err != nil || len(gs) == 0 {
 | |
| 		t.Fatalf("GoroutinesInfo() = %v, %v; wanted at least one goroutine", gs, err)
 | |
| 	}
 | |
| 
 | |
| 	var panicking *proc.G
 | |
| 	var panickingStack []proc.Stackframe
 | |
| 	for _, g := range gs {
 | |
| 		t.Logf("Goroutine %d", g.ID)
 | |
| 		stack, err := g.Stacktrace(10, 0)
 | |
| 		if err != nil {
 | |
| 			t.Errorf("Stacktrace() on goroutine %v = %v", g, err)
 | |
| 		}
 | |
| 		for _, frame := range stack {
 | |
| 			fnname := ""
 | |
| 			if frame.Call.Fn != nil {
 | |
| 				fnname = frame.Call.Fn.Name
 | |
| 			}
 | |
| 			t.Logf("\tframe %s:%d in %s %#x (systemstack: %v)", frame.Call.File, frame.Call.Line, fnname, frame.Call.PC, frame.SystemStack)
 | |
| 			if frame.Current.Fn != nil && strings.Contains(frame.Current.Fn.Name, "panic") {
 | |
| 				panicking = g
 | |
| 				panickingStack = stack
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	if panicking == nil {
 | |
| 		t.Fatalf("Didn't find a call to panic in goroutine stacks: %v", gs)
 | |
| 	}
 | |
| 
 | |
| 	var mainFrame *proc.Stackframe
 | |
| 	// Walk backward, because the current function seems to be main.main
 | |
| 	// in the actual call to panic().
 | |
| 	for i := len(panickingStack) - 1; i >= 0; i-- {
 | |
| 		if panickingStack[i].Current.Fn != nil && panickingStack[i].Current.Fn.Name == "main.main" {
 | |
| 			mainFrame = &panickingStack[i]
 | |
| 		}
 | |
| 	}
 | |
| 	if mainFrame == nil {
 | |
| 		t.Fatalf("Couldn't find main in stack %v", panickingStack)
 | |
| 	}
 | |
| 	msg, err := proc.FrameToScope(p, p.Memory(), nil, *mainFrame).EvalExpression("msg", proc.LoadConfig{MaxStringLen: 64})
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Couldn't EvalVariable(msg, ...): %v", err)
 | |
| 	}
 | |
| 	if constant.StringVal(msg.Value) != "BOOM!" {
 | |
| 		t.Errorf("main.msg = %q, want %q", msg.Value, "BOOM!")
 | |
| 	}
 | |
| 
 | |
| 	regs, err := p.CurrentThread().Registers()
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Couldn't get current thread registers: %v", err)
 | |
| 	}
 | |
| 	logRegisters(t, regs, p.BinInfo().Arch)
 | |
| }
 | |
| 
 | |
| func TestCoreFpRegisters(t *testing.T) {
 | |
| 	if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" {
 | |
| 		return
 | |
| 	}
 | |
| 	// in go1.10 the crash is executed on a different thread and registers are
 | |
| 	// no longer available in the core dump.
 | |
| 	if ver, _ := goversion.Parse(runtime.Version()); ver.Major < 0 || ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 10, Rev: -1}) {
 | |
| 		t.Skip("not supported in go1.10 and later")
 | |
| 	}
 | |
| 
 | |
| 	p := withCoreFile(t, "fputest/", "panic")
 | |
| 
 | |
| 	gs, _, err := proc.GoroutinesInfo(p, 0, 0)
 | |
| 	if err != nil || len(gs) == 0 {
 | |
| 		t.Fatalf("GoroutinesInfo() = %v, %v; wanted at least one goroutine", gs, err)
 | |
| 	}
 | |
| 
 | |
| 	var regs proc.Registers
 | |
| 	for _, thread := range p.ThreadList() {
 | |
| 		frames, err := proc.ThreadStacktrace(thread, 10)
 | |
| 		if err != nil {
 | |
| 			t.Errorf("ThreadStacktrace for %x = %v", thread.ThreadID(), err)
 | |
| 			continue
 | |
| 		}
 | |
| 		for i := range frames {
 | |
| 			if frames[i].Current.Fn == nil {
 | |
| 				continue
 | |
| 			}
 | |
| 			if frames[i].Current.Fn.Name == "main.main" {
 | |
| 				regs, err = thread.Registers()
 | |
| 				if err != nil {
 | |
| 					t.Fatalf("Could not get registers for thread %x, %v", thread.ThreadID(), err)
 | |
| 				}
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 		if regs != nil {
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	regtests := []struct{ name, value string }{
 | |
| 		{"ST(0)", "0x3fffe666660000000000"},
 | |
| 		{"ST(1)", "0x3fffd9999a0000000000"},
 | |
| 		{"ST(2)", "0x3fffcccccd0000000000"},
 | |
| 		{"ST(3)", "0x3fffc000000000000000"},
 | |
| 		{"ST(4)", "0x3fffb333333333333000"},
 | |
| 		{"ST(5)", "0x3fffa666666666666800"},
 | |
| 		{"ST(6)", "0x3fff9999999999999800"},
 | |
| 		{"ST(7)", "0x3fff8cccccccccccd000"},
 | |
| 		// Unlike TestClientServer_FpRegisters in service/test/integration2_test
 | |
| 		// we can not test the value of XMM0, it probably has been reused by
 | |
| 		// something between the panic and the time we get the core dump.
 | |
| 		{"XMM9", "0x3ff66666666666663ff4cccccccccccd"},
 | |
| 		{"XMM10", "0x3fe666663fd9999a3fcccccd3fc00000"},
 | |
| 		{"XMM3", "0x3ff199999999999a3ff3333333333333"},
 | |
| 		{"XMM4", "0x3ff4cccccccccccd3ff6666666666666"},
 | |
| 		{"XMM5", "0x3fcccccd3fc000003fe666663fd9999a"},
 | |
| 		{"XMM6", "0x4004cccccccccccc4003333333333334"},
 | |
| 		{"XMM7", "0x40026666666666664002666666666666"},
 | |
| 		{"XMM8", "0x4059999a404ccccd4059999a404ccccd"},
 | |
| 	}
 | |
| 
 | |
| 	arch := p.BinInfo().Arch
 | |
| 	logRegisters(t, regs, arch)
 | |
| 	dregs := arch.RegistersToDwarfRegisters(0, regs)
 | |
| 
 | |
| 	for _, regtest := range regtests {
 | |
| 		found := false
 | |
| 		dregs.Reg(^uint64(0))
 | |
| 		for i := 0; i < dregs.CurrentSize(); i++ {
 | |
| 			reg := dregs.Reg(uint64(i))
 | |
| 			regname, _, regval := arch.DwarfRegisterToString(i, reg)
 | |
| 			if reg != nil && regname == regtest.name {
 | |
| 				found = true
 | |
| 				if !strings.HasPrefix(regval, regtest.value) {
 | |
| 					t.Fatalf("register %s expected %q got %q", regname, regtest.value, regval)
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		if !found {
 | |
| 			t.Fatalf("register %s not found: %v", regtest.name, regs)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestCoreWithEmptyString(t *testing.T) {
 | |
| 	if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" {
 | |
| 		return
 | |
| 	}
 | |
| 	if runtime.GOOS == "linux" && os.Getenv("CI") == "true" && buildMode == "pie" {
 | |
| 		t.Skip("disabled on linux, Github Actions, with PIE buildmode")
 | |
| 	}
 | |
| 	p := withCoreFile(t, "coreemptystring", "")
 | |
| 
 | |
| 	gs, _, err := proc.GoroutinesInfo(p, 0, 0)
 | |
| 	assertNoError(err, t, "GoroutinesInfo")
 | |
| 
 | |
| 	var mainFrame *proc.Stackframe
 | |
| mainSearch:
 | |
| 	for _, g := range gs {
 | |
| 		stack, err := g.Stacktrace(10, 0)
 | |
| 		assertNoError(err, t, "Stacktrace()")
 | |
| 		for _, frame := range stack {
 | |
| 			if frame.Current.Fn != nil && frame.Current.Fn.Name == "main.main" {
 | |
| 				mainFrame = &frame
 | |
| 				break mainSearch
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if mainFrame == nil {
 | |
| 		t.Fatal("could not find main.main frame")
 | |
| 	}
 | |
| 
 | |
| 	scope := proc.FrameToScope(p, p.Memory(), nil, *mainFrame)
 | |
| 	loadConfig := proc.LoadConfig{FollowPointers: true, MaxVariableRecurse: 1, MaxStringLen: 64, MaxArrayValues: 64, MaxStructFields: -1}
 | |
| 	v1, err := scope.EvalExpression("t", loadConfig)
 | |
| 	assertNoError(err, t, "EvalVariable(t)")
 | |
| 	assertNoError(v1.Unreadable, t, "unreadable variable 't'")
 | |
| 	t.Logf("t = %#v\n", v1)
 | |
| 	v2, err := scope.EvalExpression("s", loadConfig)
 | |
| 	assertNoError(err, t, "EvalVariable(s)")
 | |
| 	assertNoError(v2.Unreadable, t, "unreadable variable 's'")
 | |
| 	t.Logf("s = %#v\n", v2)
 | |
| }
 | |
| 
 | |
| func TestMinidump(t *testing.T) {
 | |
| 	if runtime.GOOS != "windows" || runtime.GOARCH != "amd64" {
 | |
| 		t.Skip("minidumps can only be produced on windows/amd64")
 | |
| 	}
 | |
| 	var buildFlags test.BuildFlags
 | |
| 	if buildMode == "pie" {
 | |
| 		buildFlags = test.BuildModePIE
 | |
| 	}
 | |
| 	fix := test.BuildFixture("sleep", buildFlags)
 | |
| 	mdmpPath := procdump(t, fix.Path)
 | |
| 
 | |
| 	p, err := OpenCore(mdmpPath, fix.Path, []string{})
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("OpenCore: %v", err)
 | |
| 	}
 | |
| 	gs, _, err := proc.GoroutinesInfo(p, 0, 0)
 | |
| 	if err != nil || len(gs) == 0 {
 | |
| 		t.Fatalf("GoroutinesInfo() = %v, %v; wanted at least one goroutine", gs, err)
 | |
| 	}
 | |
| 	t.Logf("%d goroutines", len(gs))
 | |
| 	foundMain, foundTime := false, false
 | |
| 	for _, g := range gs {
 | |
| 		stack, err := g.Stacktrace(10, 0)
 | |
| 		if err != nil {
 | |
| 			t.Errorf("Stacktrace() on goroutine %v = %v", g, err)
 | |
| 		}
 | |
| 		t.Logf("goroutine %d", g.ID)
 | |
| 		for _, frame := range stack {
 | |
| 			name := "?"
 | |
| 			if frame.Current.Fn != nil {
 | |
| 				name = frame.Current.Fn.Name
 | |
| 			}
 | |
| 			t.Logf("\t%s:%d in %s %#x", frame.Current.File, frame.Current.Line, name, frame.Current.PC)
 | |
| 			if frame.Current.Fn == nil {
 | |
| 				continue
 | |
| 			}
 | |
| 			switch frame.Current.Fn.Name {
 | |
| 			case "main.main":
 | |
| 				foundMain = true
 | |
| 			case "time.Sleep":
 | |
| 				foundTime = true
 | |
| 			}
 | |
| 		}
 | |
| 		if foundMain != foundTime {
 | |
| 			t.Errorf("found main.main but no time.Sleep (or viceversa) %v %v", foundMain, foundTime)
 | |
| 		}
 | |
| 	}
 | |
| 	if !foundMain {
 | |
| 		t.Fatalf("could not find main goroutine")
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func procdump(t *testing.T, exePath string) string {
 | |
| 	exeDir := filepath.Dir(exePath)
 | |
| 	cmd := exec.Command("procdump64", "-accepteula", "-ma", "-n", "1", "-s", "3", "-x", exeDir, exePath, "quit")
 | |
| 	out, err := cmd.CombinedOutput() // procdump exits with non-zero status on success, so we have to ignore the error here
 | |
| 	if !strings.Contains(string(out), "Dump count reached.") {
 | |
| 		t.Fatalf("possible error running procdump64, output: %q, error: %v", string(out), err)
 | |
| 	}
 | |
| 
 | |
| 	dh, err := os.Open(exeDir)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("could not open executable file directory %q: %v", exeDir, err)
 | |
| 	}
 | |
| 	defer dh.Close()
 | |
| 	fis, err := dh.Readdir(-1)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("could not read executable file directory %q: %v", exeDir, err)
 | |
| 	}
 | |
| 	t.Logf("looking for dump file")
 | |
| 	exeName := filepath.Base(exePath)
 | |
| 	for _, fi := range fis {
 | |
| 		name := fi.Name()
 | |
| 		t.Logf("\t%s", name)
 | |
| 		if strings.HasPrefix(name, exeName) && strings.HasSuffix(name, ".dmp") {
 | |
| 			mdmpPath := filepath.Join(exeDir, name)
 | |
| 			test.PathsToRemove = append(test.PathsToRemove, mdmpPath)
 | |
| 			return mdmpPath
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	t.Fatalf("could not find dump file")
 | |
| 	return ""
 | |
| }
 | 
