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