github.com/undoio/delve@v1.9.0/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/undoio/delve/pkg/goversion" 19 "github.com/undoio/delve/pkg/proc" 20 "github.com/undoio/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.Target { 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, err := ioutil.TempDir("", "") 205 if err != nil { 206 t.Fatal(err) 207 } 208 test.PathsToRemove = append(test.PathsToRemove, tempDir) 209 var buildFlags test.BuildFlags 210 if buildMode == "pie" { 211 buildFlags = test.BuildModePIE 212 } 213 fix := test.BuildFixture(name, buildFlags) 214 bashCmd := fmt.Sprintf("cd %v && ulimit -c unlimited && GOTRACEBACK=crash %v %s", tempDir, fix.Path, args) 215 exec.Command("bash", "-c", bashCmd).Run() 216 cores, err := filepath.Glob(path.Join(tempDir, "core*")) 217 switch { 218 case err != nil || len(cores) > 1: 219 t.Fatalf("Got %v, wanted one file named core* in %v", cores, tempDir) 220 case len(cores) == 0: 221 t.Skipf("core file was not produced, could not run test") 222 return nil 223 } 224 corePath := cores[0] 225 226 p, err := OpenCore(corePath, fix.Path, []string{}) 227 if err != nil { 228 t.Errorf("OpenCore(%q) failed: %v", corePath, err) 229 pat, err := ioutil.ReadFile("/proc/sys/kernel/core_pattern") 230 t.Errorf("read core_pattern: %q, %v", pat, err) 231 apport, err := ioutil.ReadFile("/var/log/apport.log") 232 t.Errorf("read apport log: %q, %v", apport, err) 233 t.Fatalf("previous errors") 234 } 235 return p 236 } 237 238 func logRegisters(t *testing.T, regs proc.Registers, arch *proc.Arch) { 239 dregs := arch.RegistersToDwarfRegisters(0, regs) 240 dregs.Reg(^uint64(0)) 241 for i := 0; i < dregs.CurrentSize(); i++ { 242 reg := dregs.Reg(uint64(i)) 243 if reg == nil { 244 continue 245 } 246 name, _, value := arch.DwarfRegisterToString(i, reg) 247 t.Logf("%s = %s", name, value) 248 } 249 } 250 251 func TestCore(t *testing.T) { 252 if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" { 253 return 254 } 255 if runtime.GOOS == "linux" && os.Getenv("CI") == "true" && buildMode == "pie" { 256 t.Skip("disabled on linux, Github Actions, with PIE buildmode") 257 } 258 p := withCoreFile(t, "panic", "") 259 260 recorded, _ := p.Recorded() 261 if !recorded { 262 t.Fatalf("expecting recorded to be true") 263 } 264 265 gs, _, err := proc.GoroutinesInfo(p, 0, 0) 266 if err != nil || len(gs) == 0 { 267 t.Fatalf("GoroutinesInfo() = %v, %v; wanted at least one goroutine", gs, err) 268 } 269 270 var panicking *proc.G 271 var panickingStack []proc.Stackframe 272 for _, g := range gs { 273 t.Logf("Goroutine %d", g.ID) 274 stack, err := g.Stacktrace(10, 0) 275 if err != nil { 276 t.Errorf("Stacktrace() on goroutine %v = %v", g, err) 277 } 278 for _, frame := range stack { 279 fnname := "" 280 if frame.Call.Fn != nil { 281 fnname = frame.Call.Fn.Name 282 } 283 t.Logf("\tframe %s:%d in %s %#x (systemstack: %v)", frame.Call.File, frame.Call.Line, fnname, frame.Call.PC, frame.SystemStack) 284 if frame.Current.Fn != nil && strings.Contains(frame.Current.Fn.Name, "panic") { 285 panicking = g 286 panickingStack = stack 287 } 288 } 289 } 290 if panicking == nil { 291 t.Fatalf("Didn't find a call to panic in goroutine stacks: %v", gs) 292 } 293 294 var mainFrame *proc.Stackframe 295 // Walk backward, because the current function seems to be main.main 296 // in the actual call to panic(). 297 for i := len(panickingStack) - 1; i >= 0; i-- { 298 if panickingStack[i].Current.Fn != nil && panickingStack[i].Current.Fn.Name == "main.main" { 299 mainFrame = &panickingStack[i] 300 } 301 } 302 if mainFrame == nil { 303 t.Fatalf("Couldn't find main in stack %v", panickingStack) 304 } 305 msg, err := proc.FrameToScope(p, p.Memory(), nil, *mainFrame).EvalExpression("msg", proc.LoadConfig{MaxStringLen: 64}) 306 if err != nil { 307 t.Fatalf("Couldn't EvalVariable(msg, ...): %v", err) 308 } 309 if constant.StringVal(msg.Value) != "BOOM!" { 310 t.Errorf("main.msg = %q, want %q", msg.Value, "BOOM!") 311 } 312 313 regs, err := p.CurrentThread().Registers() 314 if err != nil { 315 t.Fatalf("Couldn't get current thread registers: %v", err) 316 } 317 logRegisters(t, regs, p.BinInfo().Arch) 318 } 319 320 func TestCoreFpRegisters(t *testing.T) { 321 if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" { 322 return 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 p := withCoreFile(t, "fputest/", "panic") 331 332 gs, _, err := proc.GoroutinesInfo(p, 0, 0) 333 if err != nil || len(gs) == 0 { 334 t.Fatalf("GoroutinesInfo() = %v, %v; wanted at least one goroutine", gs, err) 335 } 336 337 var regs proc.Registers 338 for _, thread := range p.ThreadList() { 339 frames, err := proc.ThreadStacktrace(thread, 10) 340 if err != nil { 341 t.Errorf("ThreadStacktrace for %x = %v", thread.ThreadID(), err) 342 continue 343 } 344 for i := range frames { 345 if frames[i].Current.Fn == nil { 346 continue 347 } 348 if frames[i].Current.Fn.Name == "main.main" { 349 regs, err = thread.Registers() 350 if err != nil { 351 t.Fatalf("Could not get registers for thread %x, %v", thread.ThreadID(), err) 352 } 353 break 354 } 355 } 356 if regs != nil { 357 break 358 } 359 } 360 361 regtests := []struct{ name, value string }{ 362 {"ST(0)", "0x3fffe666660000000000"}, 363 {"ST(1)", "0x3fffd9999a0000000000"}, 364 {"ST(2)", "0x3fffcccccd0000000000"}, 365 {"ST(3)", "0x3fffc000000000000000"}, 366 {"ST(4)", "0x3fffb333333333333000"}, 367 {"ST(5)", "0x3fffa666666666666800"}, 368 {"ST(6)", "0x3fff9999999999999800"}, 369 {"ST(7)", "0x3fff8cccccccccccd000"}, 370 // Unlike TestClientServer_FpRegisters in service/test/integration2_test 371 // we can not test the value of XMM0, it probably has been reused by 372 // something between the panic and the time we get the core dump. 373 {"XMM9", "0x3ff66666666666663ff4cccccccccccd"}, 374 {"XMM10", "0x3fe666663fd9999a3fcccccd3fc00000"}, 375 {"XMM3", "0x3ff199999999999a3ff3333333333333"}, 376 {"XMM4", "0x3ff4cccccccccccd3ff6666666666666"}, 377 {"XMM5", "0x3fcccccd3fc000003fe666663fd9999a"}, 378 {"XMM6", "0x4004cccccccccccc4003333333333334"}, 379 {"XMM7", "0x40026666666666664002666666666666"}, 380 {"XMM8", "0x4059999a404ccccd4059999a404ccccd"}, 381 } 382 383 arch := p.BinInfo().Arch 384 logRegisters(t, regs, arch) 385 dregs := arch.RegistersToDwarfRegisters(0, regs) 386 387 for _, regtest := range regtests { 388 found := false 389 dregs.Reg(^uint64(0)) 390 for i := 0; i < dregs.CurrentSize(); i++ { 391 reg := dregs.Reg(uint64(i)) 392 regname, _, regval := arch.DwarfRegisterToString(i, reg) 393 if reg != nil && regname == regtest.name { 394 found = true 395 if !strings.HasPrefix(regval, regtest.value) { 396 t.Fatalf("register %s expected %q got %q", regname, regtest.value, regval) 397 } 398 } 399 } 400 if !found { 401 t.Fatalf("register %s not found: %v", regtest.name, regs) 402 } 403 } 404 } 405 406 func TestCoreWithEmptyString(t *testing.T) { 407 if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" { 408 return 409 } 410 if runtime.GOOS == "linux" && os.Getenv("CI") == "true" && buildMode == "pie" { 411 t.Skip("disabled on linux, Github Actions, with PIE buildmode") 412 } 413 p := withCoreFile(t, "coreemptystring", "") 414 415 gs, _, err := proc.GoroutinesInfo(p, 0, 0) 416 assertNoError(err, t, "GoroutinesInfo") 417 418 var mainFrame *proc.Stackframe 419 mainSearch: 420 for _, g := range gs { 421 stack, err := g.Stacktrace(10, 0) 422 assertNoError(err, t, "Stacktrace()") 423 for _, frame := range stack { 424 if frame.Current.Fn != nil && frame.Current.Fn.Name == "main.main" { 425 mainFrame = &frame 426 break mainSearch 427 } 428 } 429 } 430 431 if mainFrame == nil { 432 t.Fatal("could not find main.main frame") 433 } 434 435 scope := proc.FrameToScope(p, p.Memory(), nil, *mainFrame) 436 loadConfig := proc.LoadConfig{FollowPointers: true, MaxVariableRecurse: 1, MaxStringLen: 64, MaxArrayValues: 64, MaxStructFields: -1} 437 v1, err := scope.EvalExpression("t", loadConfig) 438 assertNoError(err, t, "EvalVariable(t)") 439 assertNoError(v1.Unreadable, t, "unreadable variable 't'") 440 t.Logf("t = %#v\n", v1) 441 v2, err := scope.EvalExpression("s", loadConfig) 442 assertNoError(err, t, "EvalVariable(s)") 443 assertNoError(v2.Unreadable, t, "unreadable variable 's'") 444 t.Logf("s = %#v\n", v2) 445 } 446 447 func TestMinidump(t *testing.T) { 448 if runtime.GOOS != "windows" { 449 t.Skip("minidumps can only be produced on windows") 450 } 451 var buildFlags test.BuildFlags 452 if buildMode == "pie" { 453 buildFlags = test.BuildModePIE 454 } 455 fix := test.BuildFixture("sleep", buildFlags) 456 mdmpPath := procdump(t, fix.Path) 457 458 p, err := OpenCore(mdmpPath, fix.Path, []string{}) 459 if err != nil { 460 t.Fatalf("OpenCore: %v", err) 461 } 462 gs, _, err := proc.GoroutinesInfo(p, 0, 0) 463 if err != nil || len(gs) == 0 { 464 t.Fatalf("GoroutinesInfo() = %v, %v; wanted at least one goroutine", gs, err) 465 } 466 t.Logf("%d goroutines", len(gs)) 467 foundMain, foundTime := false, false 468 for _, g := range gs { 469 stack, err := g.Stacktrace(10, 0) 470 if err != nil { 471 t.Errorf("Stacktrace() on goroutine %v = %v", g, err) 472 } 473 t.Logf("goroutine %d", g.ID) 474 for _, frame := range stack { 475 name := "?" 476 if frame.Current.Fn != nil { 477 name = frame.Current.Fn.Name 478 } 479 t.Logf("\t%s:%d in %s %#x", frame.Current.File, frame.Current.Line, name, frame.Current.PC) 480 if frame.Current.Fn == nil { 481 continue 482 } 483 switch frame.Current.Fn.Name { 484 case "main.main": 485 foundMain = true 486 case "time.Sleep": 487 foundTime = true 488 } 489 } 490 if foundMain != foundTime { 491 t.Errorf("found main.main but no time.Sleep (or viceversa) %v %v", foundMain, foundTime) 492 } 493 } 494 if !foundMain { 495 t.Fatalf("could not find main goroutine") 496 } 497 } 498 499 func procdump(t *testing.T, exePath string) string { 500 exeDir := filepath.Dir(exePath) 501 cmd := exec.Command("procdump64", "-accepteula", "-ma", "-n", "1", "-s", "3", "-x", exeDir, exePath, "quit") 502 out, err := cmd.CombinedOutput() // procdump exits with non-zero status on success, so we have to ignore the error here 503 if !strings.Contains(string(out), "Dump count reached.") { 504 t.Fatalf("possible error running procdump64, output: %q, error: %v", string(out), err) 505 } 506 507 dh, err := os.Open(exeDir) 508 if err != nil { 509 t.Fatalf("could not open executable file directory %q: %v", exeDir, err) 510 } 511 defer dh.Close() 512 fis, err := dh.Readdir(-1) 513 if err != nil { 514 t.Fatalf("could not read executable file directory %q: %v", exeDir, err) 515 } 516 t.Logf("looking for dump file") 517 exeName := filepath.Base(exePath) 518 for _, fi := range fis { 519 name := fi.Name() 520 t.Logf("\t%s", name) 521 if strings.HasPrefix(name, exeName) && strings.HasSuffix(name, ".dmp") { 522 mdmpPath := filepath.Join(exeDir, name) 523 test.PathsToRemove = append(test.PathsToRemove, mdmpPath) 524 return mdmpPath 525 } 526 } 527 528 t.Fatalf("could not find dump file") 529 return "" 530 }