github.com/cnboonhan/delve@v0.0.0-20230908061759-363f2388c2fb/pkg/proc/core/core_test.go (about)

     1  package core
     2  
     3  import (
     4  	"bytes"
     5  	"flag"
     6  	"fmt"
     7  	"go/constant"
     8  	"io/ioutil"
     9  	"os"
    10  	"os/exec"
    11  	"path"
    12  	"path/filepath"
    13  	"reflect"
    14  	"runtime"
    15  	"strings"
    16  	"testing"
    17  
    18  	"github.com/go-delve/delve/pkg/goversion"
    19  	"github.com/go-delve/delve/pkg/proc"
    20  	"github.com/go-delve/delve/pkg/proc/test"
    21  )
    22  
    23  var buildMode string
    24  
    25  func TestMain(m *testing.M) {
    26  	flag.StringVar(&buildMode, "test-buildmode", "", "selects build mode")
    27  	flag.Parse()
    28  	if buildMode != "" && buildMode != "pie" {
    29  		fmt.Fprintf(os.Stderr, "unknown build mode %q", buildMode)
    30  		os.Exit(1)
    31  	}
    32  	os.Exit(test.RunTestsWithFixtures(m))
    33  }
    34  
    35  func assertNoError(err error, t testing.TB, s string) {
    36  	if err != nil {
    37  		_, file, line, _ := runtime.Caller(1)
    38  		fname := filepath.Base(file)
    39  		t.Fatalf("failed assertion at %s:%d: %s - %s\n", fname, line, s, err)
    40  	}
    41  }
    42  
    43  func TestSplicedReader(t *testing.T) {
    44  	data := []byte{}
    45  	data2 := []byte{}
    46  	for i := 0; i < 100; i++ {
    47  		data = append(data, byte(i))
    48  		data2 = append(data2, byte(i+100))
    49  	}
    50  
    51  	type region struct {
    52  		data   []byte
    53  		off    uint64
    54  		length uint64
    55  	}
    56  	tests := []struct {
    57  		name     string
    58  		regions  []region
    59  		readAddr uint64
    60  		readLen  int
    61  		want     []byte
    62  	}{
    63  		{
    64  			"Insert after",
    65  			[]region{
    66  				{data, 0, 1},
    67  				{data2, 1, 1},
    68  			},
    69  			0,
    70  			2,
    71  			[]byte{0, 101},
    72  		},
    73  		{
    74  			"Insert before",
    75  			[]region{
    76  				{data, 1, 1},
    77  				{data2, 0, 1},
    78  			},
    79  			0,
    80  			2,
    81  			[]byte{100, 1},
    82  		},
    83  		{
    84  			"Completely overwrite",
    85  			[]region{
    86  				{data, 1, 1},
    87  				{data2, 0, 3},
    88  			},
    89  			0,
    90  			3,
    91  			[]byte{100, 101, 102},
    92  		},
    93  		{
    94  			"Overwrite end",
    95  			[]region{
    96  				{data, 0, 2},
    97  				{data2, 1, 2},
    98  			},
    99  			0,
   100  			3,
   101  			[]byte{0, 101, 102},
   102  		},
   103  		{
   104  			"Overwrite start",
   105  			[]region{
   106  				{data, 0, 3},
   107  				{data2, 0, 2},
   108  			},
   109  			0,
   110  			3,
   111  			[]byte{100, 101, 2},
   112  		},
   113  		{
   114  			"Punch hole",
   115  			[]region{
   116  				{data, 0, 5},
   117  				{data2, 1, 3},
   118  			},
   119  			0,
   120  			5,
   121  			[]byte{0, 101, 102, 103, 4},
   122  		},
   123  		{
   124  			"Overlap two",
   125  			[]region{
   126  				{data, 10, 4},
   127  				{data, 14, 4},
   128  				{data2, 12, 4},
   129  			},
   130  			10,
   131  			8,
   132  			[]byte{10, 11, 112, 113, 114, 115, 16, 17},
   133  		},
   134  	}
   135  	for _, test := range tests {
   136  		t.Run(test.name, func(t *testing.T) {
   137  			mem := &SplicedMemory{}
   138  			for _, region := range test.regions {
   139  				r := bytes.NewReader(region.data)
   140  				mem.Add(&offsetReaderAt{r, 0}, region.off, region.length)
   141  			}
   142  			got := make([]byte, test.readLen)
   143  			n, err := mem.ReadMemory(got, test.readAddr)
   144  			if n != test.readLen || err != nil || !reflect.DeepEqual(got, test.want) {
   145  				t.Errorf("ReadAt = %v, %v, %v, want %v, %v, %v", n, err, got, test.readLen, nil, test.want)
   146  			}
   147  		})
   148  	}
   149  
   150  	// Test some ReadMemory errors
   151  
   152  	mem := &SplicedMemory{}
   153  	for _, region := range []region{
   154  		{[]byte{0xa1, 0xa2, 0xa3, 0xa4}, 0x1000, 4},
   155  		{[]byte{0xb1, 0xb2, 0xb3, 0xb4}, 0x1004, 4},
   156  		{[]byte{0xc1, 0xc2, 0xc3, 0xc4}, 0x1010, 4},
   157  	} {
   158  		r := bytes.NewReader(region.data)
   159  		mem.Add(&offsetReaderAt{r, region.off}, region.off, region.length)
   160  	}
   161  
   162  	got := make([]byte, 4)
   163  
   164  	// Read before the first mapping
   165  	_, err := mem.ReadMemory(got, 0x900)
   166  	if err == nil || !strings.HasPrefix(err.Error(), "error while reading spliced memory at 0x900") {
   167  		t.Errorf("Read before the start of memory didn't fail (or wrong error): %v", err)
   168  	}
   169  
   170  	// Read after the last mapping
   171  	_, err = mem.ReadMemory(got, 0x1100)
   172  	if err == nil || (err.Error() != "offset 4352 did not match any regions") {
   173  		t.Errorf("Read after the end of memory didn't fail (or wrong error): %v", err)
   174  	}
   175  
   176  	// Read at the start of the first entry
   177  	_, err = mem.ReadMemory(got, 0x1000)
   178  	if err != nil || !bytes.Equal(got, []byte{0xa1, 0xa2, 0xa3, 0xa4}) {
   179  		t.Errorf("Reading at the start of the first entry: %v %#x", err, got)
   180  	}
   181  
   182  	// Read at the start of the second entry
   183  	_, err = mem.ReadMemory(got, 0x1004)
   184  	if err != nil || !bytes.Equal(got, []byte{0xb1, 0xb2, 0xb3, 0xb4}) {
   185  		t.Errorf("Reading at the start of the second entry: %v %#x", err, got)
   186  	}
   187  
   188  	// Read straddling entries 1 and 2
   189  	_, err = mem.ReadMemory(got, 0x1002)
   190  	if err != nil || !bytes.Equal(got, []byte{0xa3, 0xa4, 0xb1, 0xb2}) {
   191  		t.Errorf("Straddled read of the second entry: %v %#x", err, got)
   192  	}
   193  
   194  	// Read past the end of the second entry
   195  	_, err = mem.ReadMemory(got, 0x1007)
   196  	if err == nil || !strings.HasPrefix(err.Error(), "error while reading spliced memory at 0x1008") {
   197  		t.Errorf("Read into gap: %v", err)
   198  	}
   199  }
   200  
   201  func withCoreFile(t *testing.T, name, args string) *proc.TargetGroup {
   202  	// This is all very fragile and won't work on hosts with non-default core patterns.
   203  	// Might be better to check in the binary and core?
   204  	tempDir := t.TempDir()
   205  	var buildFlags test.BuildFlags
   206  	if buildMode == "pie" {
   207  		buildFlags = test.BuildModePIE
   208  	}
   209  	fix := test.BuildFixture(name, buildFlags)
   210  	bashCmd := fmt.Sprintf("cd %v && ulimit -c unlimited && GOTRACEBACK=crash %v %s", tempDir, fix.Path, args)
   211  	exec.Command("bash", "-c", bashCmd).Run()
   212  	cores, err := filepath.Glob(path.Join(tempDir, "core*"))
   213  	switch {
   214  	case err != nil || len(cores) > 1:
   215  		t.Fatalf("Got %v, wanted one file named core* in %v", cores, tempDir)
   216  	case len(cores) == 0:
   217  		t.Skipf("core file was not produced, could not run test")
   218  		return nil
   219  	}
   220  	corePath := cores[0]
   221  
   222  	p, err := OpenCore(corePath, fix.Path, []string{})
   223  	if err != nil {
   224  		t.Errorf("OpenCore(%q) failed: %v", corePath, err)
   225  		pat, err := ioutil.ReadFile("/proc/sys/kernel/core_pattern")
   226  		t.Errorf("read core_pattern: %q, %v", pat, err)
   227  		apport, err := ioutil.ReadFile("/var/log/apport.log")
   228  		t.Errorf("read apport log: %q, %v", apport, err)
   229  		t.Fatalf("previous errors")
   230  	}
   231  	return p
   232  }
   233  
   234  func logRegisters(t *testing.T, regs proc.Registers, arch *proc.Arch) {
   235  	dregs := arch.RegistersToDwarfRegisters(0, regs)
   236  	dregs.Reg(^uint64(0))
   237  	for i := 0; i < dregs.CurrentSize(); i++ {
   238  		reg := dregs.Reg(uint64(i))
   239  		if reg == nil {
   240  			continue
   241  		}
   242  		name, _, value := arch.DwarfRegisterToString(i, reg)
   243  		t.Logf("%s = %s", name, value)
   244  	}
   245  }
   246  
   247  func TestCore(t *testing.T) {
   248  	if runtime.GOOS != "linux" || runtime.GOARCH == "386" {
   249  		t.Skip("unsupported")
   250  	}
   251  	if runtime.GOOS == "linux" && os.Getenv("CI") == "true" && buildMode == "pie" {
   252  		t.Skip("disabled on linux, Github Actions, with PIE buildmode")
   253  	}
   254  	grp := withCoreFile(t, "panic", "")
   255  	p := grp.Selected
   256  
   257  	recorded, _ := grp.Recorded()
   258  	if !recorded {
   259  		t.Fatalf("expecting recorded to be true")
   260  	}
   261  
   262  	gs, _, err := proc.GoroutinesInfo(p, 0, 0)
   263  	if err != nil || len(gs) == 0 {
   264  		t.Fatalf("GoroutinesInfo() = %v, %v; wanted at least one goroutine", gs, err)
   265  	}
   266  
   267  	var panicking *proc.G
   268  	var panickingStack []proc.Stackframe
   269  	for _, g := range gs {
   270  		t.Logf("Goroutine %d", g.ID)
   271  		stack, err := proc.GoroutineStacktrace(p, g, 10, 0)
   272  		if err != nil {
   273  			t.Errorf("Stacktrace() on goroutine %v = %v", g, err)
   274  		}
   275  		for _, frame := range stack {
   276  			fnname := ""
   277  			if frame.Call.Fn != nil {
   278  				fnname = frame.Call.Fn.Name
   279  			}
   280  			t.Logf("\tframe %s:%d in %s %#x (systemstack: %v)", frame.Call.File, frame.Call.Line, fnname, frame.Call.PC, frame.SystemStack)
   281  			if frame.Current.Fn != nil && strings.Contains(frame.Current.Fn.Name, "panic") {
   282  				panicking = g
   283  				panickingStack = stack
   284  			}
   285  		}
   286  	}
   287  	if panicking == nil {
   288  		t.Fatalf("Didn't find a call to panic in goroutine stacks: %v", gs)
   289  	}
   290  
   291  	var mainFrame *proc.Stackframe
   292  	// Walk backward, because the current function seems to be main.main
   293  	// in the actual call to panic().
   294  	for i := len(panickingStack) - 1; i >= 0; i-- {
   295  		if panickingStack[i].Current.Fn != nil && panickingStack[i].Current.Fn.Name == "main.main" {
   296  			mainFrame = &panickingStack[i]
   297  		}
   298  	}
   299  	if mainFrame == nil {
   300  		t.Fatalf("Couldn't find main in stack %v", panickingStack)
   301  	}
   302  	msg, err := proc.FrameToScope(p, p.Memory(), nil, p.CurrentThread().ThreadID(), *mainFrame).EvalExpression("msg", proc.LoadConfig{MaxStringLen: 64})
   303  	if err != nil {
   304  		t.Fatalf("Couldn't EvalVariable(msg, ...): %v", err)
   305  	}
   306  	if constant.StringVal(msg.Value) != "BOOM!" {
   307  		t.Errorf("main.msg = %q, want %q", msg.Value, "BOOM!")
   308  	}
   309  
   310  	regs, err := p.CurrentThread().Registers()
   311  	if err != nil {
   312  		t.Fatalf("Couldn't get current thread registers: %v", err)
   313  	}
   314  	logRegisters(t, regs, p.BinInfo().Arch)
   315  }
   316  
   317  func TestCoreFpRegisters(t *testing.T) {
   318  	if runtime.GOOS != "linux" || runtime.GOARCH == "386" {
   319  		t.Skip("unsupported")
   320  	}
   321  	if runtime.GOARCH != "amd64" {
   322  		t.Skip("test requires amd64")
   323  	}
   324  	// in go1.10 the crash is executed on a different thread and registers are
   325  	// no longer available in the core dump.
   326  	if ver, _ := goversion.Parse(runtime.Version()); ver.Major < 0 || ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 10, Rev: -1}) {
   327  		t.Skip("not supported in go1.10 and later")
   328  	}
   329  
   330  	grp := withCoreFile(t, "fputest/", "panic")
   331  	p := grp.Selected
   332  
   333  	gs, _, err := proc.GoroutinesInfo(p, 0, 0)
   334  	if err != nil || len(gs) == 0 {
   335  		t.Fatalf("GoroutinesInfo() = %v, %v; wanted at least one goroutine", gs, err)
   336  	}
   337  
   338  	var regs proc.Registers
   339  	for _, thread := range p.ThreadList() {
   340  		frames, err := proc.ThreadStacktrace(p, thread, 10)
   341  		if err != nil {
   342  			t.Errorf("ThreadStacktrace for %x = %v", thread.ThreadID(), err)
   343  			continue
   344  		}
   345  		for i := range frames {
   346  			if frames[i].Current.Fn == nil {
   347  				continue
   348  			}
   349  			if frames[i].Current.Fn.Name == "main.main" {
   350  				regs, err = thread.Registers()
   351  				if err != nil {
   352  					t.Fatalf("Could not get registers for thread %x, %v", thread.ThreadID(), err)
   353  				}
   354  				break
   355  			}
   356  		}
   357  		if regs != nil {
   358  			break
   359  		}
   360  	}
   361  
   362  	regtests := []struct{ name, value string }{
   363  		{"ST(0)", "0x3fffe666660000000000"},
   364  		{"ST(1)", "0x3fffd9999a0000000000"},
   365  		{"ST(2)", "0x3fffcccccd0000000000"},
   366  		{"ST(3)", "0x3fffc000000000000000"},
   367  		{"ST(4)", "0x3fffb333333333333000"},
   368  		{"ST(5)", "0x3fffa666666666666800"},
   369  		{"ST(6)", "0x3fff9999999999999800"},
   370  		{"ST(7)", "0x3fff8cccccccccccd000"},
   371  		// Unlike TestClientServer_FpRegisters in service/test/integration2_test
   372  		// we can not test the value of XMM0, it probably has been reused by
   373  		// something between the panic and the time we get the core dump.
   374  		{"XMM9", "0x3ff66666666666663ff4cccccccccccd"},
   375  		{"XMM10", "0x3fe666663fd9999a3fcccccd3fc00000"},
   376  		{"XMM3", "0x3ff199999999999a3ff3333333333333"},
   377  		{"XMM4", "0x3ff4cccccccccccd3ff6666666666666"},
   378  		{"XMM5", "0x3fcccccd3fc000003fe666663fd9999a"},
   379  		{"XMM6", "0x4004cccccccccccc4003333333333334"},
   380  		{"XMM7", "0x40026666666666664002666666666666"},
   381  		{"XMM8", "0x4059999a404ccccd4059999a404ccccd"},
   382  	}
   383  
   384  	arch := p.BinInfo().Arch
   385  	logRegisters(t, regs, arch)
   386  	dregs := arch.RegistersToDwarfRegisters(0, regs)
   387  
   388  	for _, regtest := range regtests {
   389  		found := false
   390  		dregs.Reg(^uint64(0))
   391  		for i := 0; i < dregs.CurrentSize(); i++ {
   392  			reg := dregs.Reg(uint64(i))
   393  			regname, _, regval := arch.DwarfRegisterToString(i, reg)
   394  			if reg != nil && regname == regtest.name {
   395  				found = true
   396  				if !strings.HasPrefix(regval, regtest.value) {
   397  					t.Fatalf("register %s expected %q got %q", regname, regtest.value, regval)
   398  				}
   399  			}
   400  		}
   401  		if !found {
   402  			t.Fatalf("register %s not found: %v", regtest.name, regs)
   403  		}
   404  	}
   405  }
   406  
   407  func TestCoreWithEmptyString(t *testing.T) {
   408  	if runtime.GOOS != "linux" || runtime.GOARCH == "386" {
   409  		t.Skip("unsupported")
   410  	}
   411  	if runtime.GOOS == "linux" && os.Getenv("CI") == "true" && buildMode == "pie" {
   412  		t.Skip("disabled on linux, Github Actions, with PIE buildmode")
   413  	}
   414  	grp := withCoreFile(t, "coreemptystring", "")
   415  	p := grp.Selected
   416  
   417  	gs, _, err := proc.GoroutinesInfo(p, 0, 0)
   418  	assertNoError(err, t, "GoroutinesInfo")
   419  
   420  	var mainFrame *proc.Stackframe
   421  mainSearch:
   422  	for _, g := range gs {
   423  		stack, err := proc.GoroutineStacktrace(p, g, 10, 0)
   424  		assertNoError(err, t, "Stacktrace()")
   425  		for _, frame := range stack {
   426  			if frame.Current.Fn != nil && frame.Current.Fn.Name == "main.main" {
   427  				mainFrame = &frame
   428  				break mainSearch
   429  			}
   430  		}
   431  	}
   432  
   433  	if mainFrame == nil {
   434  		t.Fatal("could not find main.main frame")
   435  	}
   436  
   437  	scope := proc.FrameToScope(p, p.Memory(), nil, p.CurrentThread().ThreadID(), *mainFrame)
   438  	loadConfig := proc.LoadConfig{FollowPointers: true, MaxVariableRecurse: 1, MaxStringLen: 64, MaxArrayValues: 64, MaxStructFields: -1}
   439  	v1, err := scope.EvalExpression("t", loadConfig)
   440  	assertNoError(err, t, "EvalVariable(t)")
   441  	assertNoError(v1.Unreadable, t, "unreadable variable 't'")
   442  	t.Logf("t = %#v\n", v1)
   443  	v2, err := scope.EvalExpression("s", loadConfig)
   444  	assertNoError(err, t, "EvalVariable(s)")
   445  	assertNoError(v2.Unreadable, t, "unreadable variable 's'")
   446  	t.Logf("s = %#v\n", v2)
   447  }
   448  
   449  func TestMinidump(t *testing.T) {
   450  	if runtime.GOOS != "windows" || runtime.GOARCH != "amd64" {
   451  		t.Skip("minidumps can only be produced on windows/amd64")
   452  	}
   453  	var buildFlags test.BuildFlags
   454  	if buildMode == "pie" {
   455  		buildFlags = test.BuildModePIE
   456  	}
   457  	fix := test.BuildFixture("sleep", buildFlags)
   458  	mdmpPath := procdump(t, fix.Path)
   459  
   460  	grp, err := OpenCore(mdmpPath, fix.Path, []string{})
   461  	if err != nil {
   462  		t.Fatalf("OpenCore: %v", err)
   463  	}
   464  	p := grp.Selected
   465  	gs, _, err := proc.GoroutinesInfo(p, 0, 0)
   466  	if err != nil || len(gs) == 0 {
   467  		t.Fatalf("GoroutinesInfo() = %v, %v; wanted at least one goroutine", gs, err)
   468  	}
   469  	t.Logf("%d goroutines", len(gs))
   470  	foundMain, foundTime := false, false
   471  	for _, g := range gs {
   472  		stack, err := proc.GoroutineStacktrace(p, g, 10, 0)
   473  		if err != nil {
   474  			t.Errorf("Stacktrace() on goroutine %v = %v", g, err)
   475  		}
   476  		t.Logf("goroutine %d", g.ID)
   477  		for _, frame := range stack {
   478  			name := "?"
   479  			if frame.Current.Fn != nil {
   480  				name = frame.Current.Fn.Name
   481  			}
   482  			t.Logf("\t%s:%d in %s %#x", frame.Current.File, frame.Current.Line, name, frame.Current.PC)
   483  			if frame.Current.Fn == nil {
   484  				continue
   485  			}
   486  			switch frame.Current.Fn.Name {
   487  			case "main.main":
   488  				foundMain = true
   489  			case "time.Sleep":
   490  				foundTime = true
   491  			}
   492  		}
   493  		if foundMain != foundTime {
   494  			t.Errorf("found main.main but no time.Sleep (or viceversa) %v %v", foundMain, foundTime)
   495  		}
   496  	}
   497  	if !foundMain {
   498  		t.Fatalf("could not find main goroutine")
   499  	}
   500  }
   501  
   502  func procdump(t *testing.T, exePath string) string {
   503  	exeDir := filepath.Dir(exePath)
   504  	cmd := exec.Command("procdump64", "-accepteula", "-ma", "-n", "1", "-s", "3", "-x", exeDir, exePath, "quit")
   505  	out, err := cmd.CombinedOutput() // procdump exits with non-zero status on success, so we have to ignore the error here
   506  	if !strings.Contains(string(out), "Dump count reached.") {
   507  		t.Fatalf("possible error running procdump64, output: %q, error: %v", string(out), err)
   508  	}
   509  
   510  	fis, err := os.ReadDir(exeDir)
   511  	if err != nil {
   512  		t.Fatalf("could not read executable file directory %q: %v", exeDir, err)
   513  	}
   514  	t.Logf("looking for dump file")
   515  	exeName := filepath.Base(exePath)
   516  	for _, fi := range fis {
   517  		name := fi.Name()
   518  		t.Logf("\t%s", name)
   519  		if strings.HasPrefix(name, exeName) && strings.HasSuffix(name, ".dmp") {
   520  			mdmpPath := filepath.Join(exeDir, name)
   521  			test.PathsToRemove = append(test.PathsToRemove, mdmpPath)
   522  			return mdmpPath
   523  		}
   524  	}
   525  
   526  	t.Fatalf("could not find dump file")
   527  	return ""
   528  }