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 }