golang.org/x/arch@v0.17.0/ppc64/ppc64asm/ext_test.go (about) 1 // Copyright 2014 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Support for testing against external disassembler program. 6 // Copied and simplified from rsc.io/arm/armasm/ext_test.go. 7 8 package ppc64asm 9 10 import ( 11 "bufio" 12 "bytes" 13 "encoding/binary" 14 "encoding/hex" 15 "flag" 16 "fmt" 17 "io" 18 "io/ioutil" 19 "log" 20 "math/rand" 21 "os" 22 "os/exec" 23 "regexp" 24 "runtime" 25 "strings" 26 "testing" 27 "time" 28 ) 29 30 var ( 31 printTests = flag.Bool("printtests", false, "print test cases that exercise new code paths") 32 dumpTest = flag.Bool("dump", false, "dump all encodings") 33 mismatch = flag.Bool("mismatch", false, "log allowed mismatches") 34 longTest = flag.Bool("long", false, "long test") 35 keep = flag.Bool("keep", false, "keep object files around") 36 debug = false 37 ) 38 39 // An ExtInst represents a single decoded instruction parsed 40 // from an external disassembler's output. 41 type ExtInst struct { 42 addr uint32 43 enc [8]byte 44 nenc int 45 text string 46 } 47 48 func (r ExtInst) String() string { 49 return fmt.Sprintf("%#x: % x: %s", r.addr, r.enc, r.text) 50 } 51 52 // An ExtDis is a connection between an external disassembler and a test. 53 type ExtDis struct { 54 Dec chan ExtInst 55 File *os.File 56 Size int 57 KeepFile bool 58 Cmd *exec.Cmd 59 } 60 61 // Run runs the given command - the external disassembler - and returns 62 // a buffered reader of its standard output. 63 func (ext *ExtDis) Run(cmd ...string) (*bufio.Reader, error) { 64 if *keep { 65 log.Printf("%s\n", strings.Join(cmd, " ")) 66 } 67 ext.Cmd = exec.Command(cmd[0], cmd[1:]...) 68 out, err := ext.Cmd.StdoutPipe() 69 if err != nil { 70 return nil, fmt.Errorf("stdoutpipe: %v", err) 71 } 72 if err := ext.Cmd.Start(); err != nil { 73 return nil, fmt.Errorf("exec: %v", err) 74 } 75 76 b := bufio.NewReaderSize(out, 1<<20) 77 return b, nil 78 } 79 80 // Wait waits for the command started with Run to exit. 81 func (ext *ExtDis) Wait() error { 82 return ext.Cmd.Wait() 83 } 84 85 // testExtDis tests a set of byte sequences against an external disassembler. 86 // The disassembler is expected to produce the given syntax and be run 87 // in the given architecture mode (16, 32, or 64-bit). 88 // The extdis function must start the external disassembler 89 // and then parse its output, sending the parsed instructions on ext.Dec. 90 // The generate function calls its argument f once for each byte sequence 91 // to be tested. The generate function itself will be called twice, and it must 92 // make the same sequence of calls to f each time. 93 // When a disassembly does not match the internal decoding, 94 // allowedMismatch determines whether this mismatch should be 95 // allowed, or else considered an error. 96 func testExtDis( 97 t *testing.T, 98 syntax string, 99 extdis func(ext *ExtDis) error, 100 generate func(f func([]byte)), 101 allowedMismatch func(text string, size int, inst *Inst, dec ExtInst) bool, 102 ) { 103 start := time.Now() 104 ext := &ExtDis{ 105 Dec: make(chan ExtInst), 106 } 107 errc := make(chan error) 108 109 // First pass: write instructions to input file for external disassembler. 110 file, f, size, err := writeInst(generate) 111 if err != nil { 112 t.Fatal(err) 113 } 114 ext.Size = size 115 ext.File = f 116 defer func() { 117 f.Close() 118 if !*keep { 119 os.Remove(file) 120 } 121 }() 122 123 // Second pass: compare disassembly against our decodings. 124 var ( 125 totalTests = 0 126 totalSkips = 0 127 totalErrors = 0 128 129 errors = make([]string, 0, 100) // sampled errors, at most cap 130 ) 131 go func() { 132 errc <- extdis(ext) 133 }() 134 generate(func(enc []byte) { 135 dec, ok := <-ext.Dec 136 if !ok { 137 t.Errorf("decoding stream ended early") 138 return 139 } 140 inst, text := disasm(syntax, pad(enc)) 141 totalTests++ 142 if *dumpTest { 143 fmt.Printf("%x -> %s [%d]\n", enc[:len(enc)], dec.text, dec.nenc) 144 } 145 if text != dec.text || inst.Len != dec.nenc { 146 suffix := "" 147 if allowedMismatch(text, size, &inst, dec) { 148 totalSkips++ 149 if !*mismatch { 150 return 151 } 152 suffix += " (allowed mismatch)" 153 } 154 totalErrors++ 155 if len(errors) >= cap(errors) { 156 j := rand.Intn(totalErrors) 157 if j >= cap(errors) { 158 return 159 } 160 errors = append(errors[:j], errors[j+1:]...) 161 } 162 errors = append(errors, fmt.Sprintf("decode(%x) = %q, %d, want %q, %d%s", enc, text, inst.Len, dec.text, dec.nenc, suffix)) 163 } 164 }) 165 166 if *mismatch { 167 totalErrors -= totalSkips 168 } 169 170 for _, b := range errors { 171 t.Log(b) 172 } 173 174 if totalErrors > 0 { 175 t.Fail() 176 } 177 t.Logf("%d test cases, %d expected mismatches, %d failures; %.0f cases/second", totalTests, totalSkips, totalErrors, float64(totalTests)/time.Since(start).Seconds()) 178 179 if err := <-errc; err != nil { 180 t.Fatalf("external disassembler: %v", err) 181 } 182 183 } 184 185 const start = 0x8000 // start address of text 186 187 // writeInst writes the generated byte sequences to a new file 188 // starting at offset start. That file is intended to be the input to 189 // the external disassembler. 190 func writeInst(generate func(func([]byte))) (file string, f *os.File, size int, err error) { 191 f, err = ioutil.TempFile("", "ppc64asm") 192 if err != nil { 193 return 194 } 195 196 file = f.Name() 197 198 f.Seek(start, io.SeekStart) 199 w := bufio.NewWriter(f) 200 defer w.Flush() 201 size = 0 202 generate(func(x []byte) { 203 if len(x) != 4 && len(x) != 8 { 204 panic(fmt.Sprintf("Unexpected instruction %v\n", x)) 205 } 206 izeros := zeros 207 if len(x) == 4 { 208 // Only pad to 4 bytes for a 4 byte instruction word. 209 izeros = izeros[4:] 210 } 211 if debug { 212 fmt.Printf("%#x: %x%x\n", start+size, x, izeros[len(x):]) 213 } 214 w.Write(x) 215 w.Write(izeros[len(x):]) 216 size += len(izeros) 217 }) 218 return file, f, size, nil 219 } 220 221 var zeros = []byte{0, 0, 0, 0, 0, 0, 0, 0} 222 223 // pad pads the code sequence with pops. 224 func pad(enc []byte) []byte { 225 if len(enc) < 4 { 226 enc = append(enc[:len(enc):len(enc)], zeros[:4-len(enc)]...) 227 } 228 return enc 229 } 230 231 // disasm returns the decoded instruction and text 232 // for the given source bytes, using the given syntax and mode. 233 func disasm(syntax string, src []byte) (inst Inst, text string) { 234 // If printTests is set, we record the coverage value 235 // before and after, and we write out the inputs for which 236 // coverage went up, in the format expected in testdata/decode.text. 237 // This produces a fairly small set of test cases that exercise nearly 238 // all the code. 239 var cover float64 240 if *printTests { 241 cover -= coverage() 242 } 243 244 inst, err := Decode(src, binary.BigEndian) 245 if err != nil { 246 text = "error: " + err.Error() 247 } else { 248 text = inst.String() 249 switch syntax { 250 //case "arm": 251 // text = ARMSyntax(inst) 252 case "gnu": 253 text = GNUSyntax(inst, 0) 254 //case "plan9": 255 // text = GoSyntax(inst, 0, nil) 256 default: 257 text = "error: unknown syntax " + syntax 258 } 259 } 260 261 if *printTests { 262 cover += coverage() 263 if cover > 0 { 264 max := len(src) 265 if max > 4 && inst.Len <= 4 { 266 max = 4 267 } 268 fmt.Printf("%x|%x\t%s\t%s\n", src[:inst.Len], src[inst.Len:max], syntax, text) 269 } 270 } 271 272 return 273 } 274 275 // coverage returns a floating point number denoting the 276 // test coverage until now. The number increases when new code paths are exercised, 277 // both in the Go program and in the decoder byte code. 278 func coverage() float64 { 279 var f float64 280 f += testing.Coverage() 281 f += decodeCoverage() 282 return f 283 } 284 285 func decodeCoverage() float64 { 286 n := 0 287 for _, t := range decoderCover { 288 if t { 289 n++ 290 } 291 } 292 return float64(1+n) / float64(1+len(decoderCover)) 293 } 294 295 // Helpers for writing disassembler output parsers. 296 297 // hasPrefix reports whether any of the space-separated words in the text s 298 // begins with any of the given prefixes. 299 func hasPrefix(s string, prefixes ...string) bool { 300 for _, prefix := range prefixes { 301 for s := s; s != ""; { 302 if strings.HasPrefix(s, prefix) { 303 return true 304 } 305 i := strings.Index(s, " ") 306 if i < 0 { 307 break 308 } 309 s = s[i+1:] 310 } 311 } 312 return false 313 } 314 315 // contains reports whether the text s contains any of the given substrings. 316 func contains(s string, substrings ...string) bool { 317 for _, sub := range substrings { 318 if strings.Contains(s, sub) { 319 return true 320 } 321 } 322 return false 323 } 324 325 // isHex reports whether b is a hexadecimal character (0-9A-Fa-f). 326 func isHex(b byte) bool { return b == '0' || unhex[b] > 0 } 327 328 // parseHex parses the hexadecimal byte dump in hex, 329 // appending the parsed bytes to raw and returning the updated slice. 330 // The returned bool signals whether any invalid hex was found. 331 // Spaces and tabs between bytes are okay but any other non-hex is not. 332 func parseHex(hex []byte, raw []byte) ([]byte, bool) { 333 hex = trimSpace(hex) 334 for j := 0; j < len(hex); { 335 for hex[j] == ' ' || hex[j] == '\t' { 336 j++ 337 } 338 if j >= len(hex) { 339 break 340 } 341 if j+2 > len(hex) || !isHex(hex[j]) || !isHex(hex[j+1]) { 342 return nil, false 343 } 344 raw = append(raw, unhex[hex[j]]<<4|unhex[hex[j+1]]) 345 j += 2 346 } 347 return raw, true 348 } 349 350 var unhex = [256]byte{ 351 '0': 0, 352 '1': 1, 353 '2': 2, 354 '3': 3, 355 '4': 4, 356 '5': 5, 357 '6': 6, 358 '7': 7, 359 '8': 8, 360 '9': 9, 361 'A': 10, 362 'B': 11, 363 'C': 12, 364 'D': 13, 365 'E': 14, 366 'F': 15, 367 'a': 10, 368 'b': 11, 369 'c': 12, 370 'd': 13, 371 'e': 14, 372 'f': 15, 373 } 374 375 // index is like bytes.Index(s, []byte(t)) but avoids the allocation. 376 func index(s []byte, t string) int { 377 i := 0 378 for { 379 j := bytes.IndexByte(s[i:], t[0]) 380 if j < 0 { 381 return -1 382 } 383 i = i + j 384 if i+len(t) > len(s) { 385 return -1 386 } 387 for k := 1; k < len(t); k++ { 388 if s[i+k] != t[k] { 389 goto nomatch 390 } 391 } 392 return i 393 nomatch: 394 i++ 395 } 396 } 397 398 // fixSpace rewrites runs of spaces, tabs, and newline characters into single spaces in s. 399 // If s must be rewritten, it is rewritten in place. 400 func fixSpace(s []byte) []byte { 401 s = trimSpace(s) 402 for i := 0; i < len(s); i++ { 403 if s[i] == '\t' || s[i] == '\n' || i > 0 && s[i] == ' ' && s[i-1] == ' ' { 404 goto Fix 405 } 406 } 407 return s 408 409 Fix: 410 b := s 411 w := 0 412 for i := 0; i < len(s); i++ { 413 c := s[i] 414 if c == '\t' || c == '\n' { 415 c = ' ' 416 } 417 if c == ' ' && w > 0 && b[w-1] == ' ' { 418 continue 419 } 420 b[w] = c 421 w++ 422 } 423 if w > 0 && b[w-1] == ' ' { 424 w-- 425 } 426 return b[:w] 427 } 428 429 // trimSpace trims leading and trailing space from s, returning a subslice of s. 430 func trimSpace(s []byte) []byte { 431 j := len(s) 432 for j > 0 && (s[j-1] == ' ' || s[j-1] == '\t' || s[j-1] == '\n') { 433 j-- 434 } 435 i := 0 436 for i < j && (s[i] == ' ' || s[i] == '\t') { 437 i++ 438 } 439 return s[i:j] 440 } 441 442 // pcrel matches instructions using relative addressing mode. 443 var ( 444 pcrel = regexp.MustCompile(`^((?:.* )?(?:b|bc)[^ac ]* (?:(?:[0-9]{1,2},)|(?:[0-7]\*)|\+|lt|gt|eq|so|cr[0-7]|,)*)0x([0-9a-f]+)$`) 445 ) 446 447 // Generators. 448 // 449 // The test cases are described as functions that invoke a callback repeatedly, 450 // with a new input sequence each time. These helpers make writing those 451 // a little easier. 452 453 // randomCases generates random instructions. 454 func randomCases(t *testing.T) func(func([]byte)) { 455 return func(try func([]byte)) { 456 // All the strides are relatively prime to 2 and therefore to 2²⁸, 457 // so we will not repeat any instructions until we have tried all 2²⁸. 458 // Using a stride other than 1 is meant to visit the instructions in a 459 // pseudorandom order, which gives better variety in the set of 460 // test cases chosen by -printtests. 461 stride := uint32(10007) 462 n := 1 << 28 / 7 463 if testing.Short() { 464 stride = 100003 465 n = 1 << 28 / 1001 466 } else if *longTest { 467 stride = 2000033 468 n = 1 << 29 469 } 470 x := uint32(0) 471 for i := 0; i < n; i++ { 472 enc := (x%15)<<28 | x&(1<<28-1) 473 try([]byte{byte(enc), byte(enc >> 8), byte(enc >> 16), byte(enc >> 24)}) 474 x += stride 475 } 476 } 477 } 478 479 // hexCases generates the cases written in hexadecimal in the encoded string. 480 // Spaces in 'encoded' separate entire test cases, not individual bytes. 481 func hexCases(t *testing.T, encoded string) func(func([]byte)) { 482 return func(try func([]byte)) { 483 for _, x := range strings.Fields(encoded) { 484 src, err := hex.DecodeString(x) 485 if err != nil { 486 t.Errorf("parsing %q: %v", x, err) 487 } 488 try(src) 489 } 490 } 491 } 492 493 // testdataCases generates the test cases recorded in testdata/decode.txt. 494 // It only uses the inputs; it ignores the answers recorded in that file. 495 func testdataCases(t *testing.T) func(func([]byte)) { 496 var codes [][]byte 497 data, err := ioutil.ReadFile("testdata/decode.txt") 498 if err != nil { 499 t.Fatal(err) 500 } 501 for _, line := range strings.Split(string(data), "\n") { 502 line = strings.TrimSpace(line) 503 if line == "" || strings.HasPrefix(line, "#") { 504 continue 505 } 506 f := strings.Fields(line)[0] 507 i := strings.Index(f, "|") 508 if i < 0 { 509 t.Errorf("parsing %q: missing | separator", f) 510 continue 511 } 512 if i%2 != 0 { 513 t.Errorf("parsing %q: misaligned | separator", f) 514 } 515 code, err := hex.DecodeString(f[:i] + f[i+1:]) 516 if err != nil { 517 t.Errorf("parsing %q: %v", f, err) 518 continue 519 } 520 codes = append(codes, code) 521 } 522 523 return func(try func([]byte)) { 524 for _, code := range codes { 525 try(code) 526 } 527 } 528 } 529 530 func caller(skip int) string { 531 pc, _, _, _ := runtime.Caller(skip) 532 f := runtime.FuncForPC(pc) 533 name := "?" 534 if f != nil { 535 name = f.Name() 536 if i := strings.LastIndex(name, "."); i >= 0 { 537 name = name[i+1:] 538 } 539 } 540 return name 541 }