golang.org/x/arch@v0.17.0/arm/armasm/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 ../../x86/x86asm/ext_test.go. 7 8 package armasm 9 10 import ( 11 "bufio" 12 "bytes" 13 "encoding/hex" 14 "flag" 15 "fmt" 16 "io" 17 "io/ioutil" 18 "log" 19 "math/rand" 20 "os" 21 "os/exec" 22 "regexp" 23 "runtime" 24 "strings" 25 "testing" 26 "time" 27 ) 28 29 var ( 30 printTests = flag.Bool("printtests", false, "print test cases that exercise new code paths") 31 dumpTest = flag.Bool("dump", false, "dump all encodings") 32 mismatch = flag.Bool("mismatch", false, "log allowed mismatches") 33 longTest = flag.Bool("long", false, "long test") 34 keep = flag.Bool("keep", false, "keep object files around") 35 debug = false 36 ) 37 38 // An ExtInst represents a single decoded instruction parsed 39 // from an external disassembler's output. 40 type ExtInst struct { 41 addr uint32 42 enc [4]byte 43 nenc int 44 text string 45 } 46 47 func (r ExtInst) String() string { 48 return fmt.Sprintf("%#x: % x: %s", r.addr, r.enc, r.text) 49 } 50 51 // An ExtDis is a connection between an external disassembler and a test. 52 type ExtDis struct { 53 Arch Mode 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 arch Mode, 100 extdis func(ext *ExtDis) error, 101 generate func(f func([]byte)), 102 allowedMismatch func(text string, size int, inst *Inst, dec ExtInst) bool, 103 ) { 104 start := time.Now() 105 ext := &ExtDis{ 106 Dec: make(chan ExtInst), 107 Arch: arch, 108 } 109 errc := make(chan error) 110 111 // First pass: write instructions to input file for external disassembler. 112 file, f, size, err := writeInst(generate) 113 if err != nil { 114 t.Fatal(err) 115 } 116 ext.Size = size 117 ext.File = f 118 defer func() { 119 f.Close() 120 if !*keep { 121 os.Remove(file) 122 } 123 }() 124 125 // Second pass: compare disassembly against our decodings. 126 var ( 127 totalTests = 0 128 totalSkips = 0 129 totalErrors = 0 130 131 errors = make([]string, 0, 100) // sampled errors, at most cap 132 ) 133 go func() { 134 errc <- extdis(ext) 135 }() 136 generate(func(enc []byte) { 137 dec, ok := <-ext.Dec 138 if !ok { 139 t.Errorf("decoding stream ended early") 140 return 141 } 142 inst, text := disasm(syntax, arch, pad(enc)) 143 totalTests++ 144 if *dumpTest { 145 fmt.Printf("%x -> %s [%d]\n", enc[:len(enc)], dec.text, dec.nenc) 146 } 147 if text != dec.text || inst.Len != dec.nenc { 148 suffix := "" 149 if allowedMismatch(text, size, &inst, dec) { 150 totalSkips++ 151 if !*mismatch { 152 return 153 } 154 suffix += " (allowed mismatch)" 155 } 156 totalErrors++ 157 if len(errors) >= cap(errors) { 158 j := rand.Intn(totalErrors) 159 if j >= cap(errors) { 160 return 161 } 162 errors = append(errors[:j], errors[j+1:]...) 163 } 164 errors = append(errors, fmt.Sprintf("decode(%x) = %q, %d, want %q, %d%s", enc, text, inst.Len, dec.text, dec.nenc, suffix)) 165 } 166 }) 167 168 if *mismatch { 169 totalErrors -= totalSkips 170 } 171 172 for _, b := range errors { 173 t.Log(b) 174 } 175 176 if totalErrors > 0 { 177 t.Fail() 178 } 179 t.Logf("%d test cases, %d expected mismatches, %d failures; %.0f cases/second", totalTests, totalSkips, totalErrors, float64(totalTests)/time.Since(start).Seconds()) 180 181 if err := <-errc; err != nil { 182 t.Fatalf("external disassembler: %v", err) 183 } 184 185 } 186 187 const start = 0x8000 // start address of text 188 189 // writeInst writes the generated byte sequences to a new file 190 // starting at offset start. That file is intended to be the input to 191 // the external disassembler. 192 func writeInst(generate func(func([]byte))) (file string, f *os.File, size int, err error) { 193 f, err = ioutil.TempFile("", "armasm") 194 if err != nil { 195 return 196 } 197 198 file = f.Name() 199 200 f.Seek(start, io.SeekStart) 201 w := bufio.NewWriter(f) 202 defer w.Flush() 203 size = 0 204 generate(func(x []byte) { 205 if len(x) > 4 { 206 x = x[:4] 207 } 208 if debug { 209 fmt.Printf("%#x: %x%x\n", start+size, x, zeros[len(x):]) 210 } 211 w.Write(x) 212 w.Write(zeros[len(x):]) 213 size += len(zeros) 214 }) 215 return file, f, size, nil 216 } 217 218 var zeros = []byte{0, 0, 0, 0} 219 220 // pad pads the code sequence with pops. 221 func pad(enc []byte) []byte { 222 if len(enc) < 4 { 223 enc = append(enc[:len(enc):len(enc)], zeros[:4-len(enc)]...) 224 } 225 return enc 226 } 227 228 // disasm returns the decoded instruction and text 229 // for the given source bytes, using the given syntax and mode. 230 func disasm(syntax string, mode Mode, src []byte) (inst Inst, text string) { 231 // If printTests is set, we record the coverage value 232 // before and after, and we write out the inputs for which 233 // coverage went up, in the format expected in testdata/decode.text. 234 // This produces a fairly small set of test cases that exercise nearly 235 // all the code. 236 var cover float64 237 if *printTests { 238 cover -= coverage() 239 } 240 241 inst, err := Decode(src, mode) 242 if err != nil { 243 text = "error: " + err.Error() 244 } else { 245 text = inst.String() 246 switch syntax { 247 //case "arm": 248 // text = ARMSyntax(inst) 249 case "gnu": 250 text = GNUSyntax(inst) 251 //case "plan9": // [sic] 252 // text = GoSyntax(inst, 0, nil) 253 default: 254 text = "error: unknown syntax " + syntax 255 } 256 } 257 258 if *printTests { 259 cover += coverage() 260 if cover > 0 { 261 max := len(src) 262 if max > 4 && inst.Len <= 4 { 263 max = 4 264 } 265 fmt.Printf("%x|%x\t%d\t%s\t%s\n", src[:inst.Len], src[inst.Len:max], mode, syntax, text) 266 } 267 } 268 269 return 270 } 271 272 // coverage returns a floating point number denoting the 273 // test coverage until now. The number increases when new code paths are exercised, 274 // both in the Go program and in the decoder byte code. 275 func coverage() float64 { 276 /* 277 testing.Coverage is not in the main distribution. 278 The implementation, which must go in package testing, is: 279 280 // Coverage reports the current code coverage as a fraction in the range [0, 1]. 281 func Coverage() float64 { 282 var n, d int64 283 for _, counters := range cover.Counters { 284 for _, c := range counters { 285 if c > 0 { 286 n++ 287 } 288 d++ 289 } 290 } 291 if d == 0 { 292 return 0 293 } 294 return float64(n) / float64(d) 295 } 296 */ 297 298 var f float64 299 f += testing.Coverage() 300 f += decodeCoverage() 301 return f 302 } 303 304 func decodeCoverage() float64 { 305 n := 0 306 for _, t := range decoderCover { 307 if t { 308 n++ 309 } 310 } 311 return float64(1+n) / float64(1+len(decoderCover)) 312 } 313 314 // Helpers for writing disassembler output parsers. 315 316 // hasPrefix reports whether any of the space-separated words in the text s 317 // begins with any of the given prefixes. 318 func hasPrefix(s string, prefixes ...string) bool { 319 for _, prefix := range prefixes { 320 for s := s; s != ""; { 321 if strings.HasPrefix(s, prefix) { 322 return true 323 } 324 i := strings.Index(s, " ") 325 if i < 0 { 326 break 327 } 328 s = s[i+1:] 329 } 330 } 331 return false 332 } 333 334 // contains reports whether the text s contains any of the given substrings. 335 func contains(s string, substrings ...string) bool { 336 for _, sub := range substrings { 337 if strings.Contains(s, sub) { 338 return true 339 } 340 } 341 return false 342 } 343 344 // isHex reports whether b is a hexadecimal character (0-9A-Fa-f). 345 func isHex(b byte) bool { return b == '0' || unhex[b] > 0 } 346 347 // parseHex parses the hexadecimal byte dump in hex, 348 // appending the parsed bytes to raw and returning the updated slice. 349 // The returned bool signals whether any invalid hex was found. 350 // Spaces and tabs between bytes are okay but any other non-hex is not. 351 func parseHex(hex []byte, raw []byte) ([]byte, bool) { 352 hex = trimSpace(hex) 353 for j := 0; j < len(hex); { 354 for hex[j] == ' ' || hex[j] == '\t' { 355 j++ 356 } 357 if j >= len(hex) { 358 break 359 } 360 if j+2 > len(hex) || !isHex(hex[j]) || !isHex(hex[j+1]) { 361 return nil, false 362 } 363 raw = append(raw, unhex[hex[j]]<<4|unhex[hex[j+1]]) 364 j += 2 365 } 366 return raw, true 367 } 368 369 var unhex = [256]byte{ 370 '0': 0, 371 '1': 1, 372 '2': 2, 373 '3': 3, 374 '4': 4, 375 '5': 5, 376 '6': 6, 377 '7': 7, 378 '8': 8, 379 '9': 9, 380 'A': 10, 381 'B': 11, 382 'C': 12, 383 'D': 13, 384 'E': 14, 385 'F': 15, 386 'a': 10, 387 'b': 11, 388 'c': 12, 389 'd': 13, 390 'e': 14, 391 'f': 15, 392 } 393 394 // index is like bytes.Index(s, []byte(t)) but avoids the allocation. 395 func index(s []byte, t string) int { 396 i := 0 397 for { 398 j := bytes.IndexByte(s[i:], t[0]) 399 if j < 0 { 400 return -1 401 } 402 i = i + j 403 if i+len(t) > len(s) { 404 return -1 405 } 406 for k := 1; k < len(t); k++ { 407 if s[i+k] != t[k] { 408 goto nomatch 409 } 410 } 411 return i 412 nomatch: 413 i++ 414 } 415 } 416 417 // fixSpace rewrites runs of spaces, tabs, and newline characters into single spaces in s. 418 // If s must be rewritten, it is rewritten in place. 419 func fixSpace(s []byte) []byte { 420 s = trimSpace(s) 421 for i := 0; i < len(s); i++ { 422 if s[i] == '\t' || s[i] == '\n' || i > 0 && s[i] == ' ' && s[i-1] == ' ' { 423 goto Fix 424 } 425 } 426 return s 427 428 Fix: 429 b := s 430 w := 0 431 for i := 0; i < len(s); i++ { 432 c := s[i] 433 if c == '\t' || c == '\n' { 434 c = ' ' 435 } 436 if c == ' ' && w > 0 && b[w-1] == ' ' { 437 continue 438 } 439 b[w] = c 440 w++ 441 } 442 if w > 0 && b[w-1] == ' ' { 443 w-- 444 } 445 return b[:w] 446 } 447 448 // trimSpace trims leading and trailing space from s, returning a subslice of s. 449 func trimSpace(s []byte) []byte { 450 j := len(s) 451 for j > 0 && (s[j-1] == ' ' || s[j-1] == '\t' || s[j-1] == '\n') { 452 j-- 453 } 454 i := 0 455 for i < j && (s[i] == ' ' || s[i] == '\t') { 456 i++ 457 } 458 return s[i:j] 459 } 460 461 // pcrel matches instructions using relative addressing mode. 462 var ( 463 pcrel = regexp.MustCompile(`^((?:.* )?(?:b|bl)x?(?:eq|ne|cs|cc|mi|pl|vs|vc|hi|ls|ge|lt|gt|le)?) 0x([0-9a-f]+)$`) 464 ) 465 466 // Generators. 467 // 468 // The test cases are described as functions that invoke a callback repeatedly, 469 // with a new input sequence each time. These helpers make writing those 470 // a little easier. 471 472 // condCases generates conditional instructions. 473 func condCases(t *testing.T) func(func([]byte)) { 474 return func(try func([]byte)) { 475 // All the strides are relatively prime to 2 and therefore to 2²⁸, 476 // so we will not repeat any instructions until we have tried all 2²⁸. 477 // Using a stride other than 1 is meant to visit the instructions in a 478 // pseudorandom order, which gives better variety in the set of 479 // test cases chosen by -printtests. 480 stride := uint32(10007) 481 n := 1 << 28 / 7 482 if testing.Short() { 483 stride = 100003 484 n = 1 << 28 / 1001 485 } else if *longTest { 486 stride = 200000033 487 n = 1 << 28 488 } 489 x := uint32(0) 490 for i := 0; i < n; i++ { 491 enc := (x%15)<<28 | x&(1<<28-1) 492 try([]byte{byte(enc), byte(enc >> 8), byte(enc >> 16), byte(enc >> 24)}) 493 x += stride 494 } 495 } 496 } 497 498 // uncondCases generates unconditional instructions. 499 func uncondCases(t *testing.T) func(func([]byte)) { 500 return func(try func([]byte)) { 501 condCases(t)(func(enc []byte) { 502 enc[3] |= 0xF0 503 try(enc) 504 }) 505 } 506 } 507 508 func countBits(x uint32) int { 509 n := 0 510 for ; x != 0; x >>= 1 { 511 n += int(x & 1) 512 } 513 return n 514 } 515 516 func expandBits(x, m uint32) uint32 { 517 var out uint32 518 for i := uint(0); i < 32; i++ { 519 out >>= 1 520 if m&1 != 0 { 521 out |= (x & 1) << 31 522 x >>= 1 523 } 524 m >>= 1 525 } 526 return out 527 } 528 529 func tryCondMask(mask, val uint32, try func([]byte)) { 530 n := countBits(^mask) 531 bits := uint32(0) 532 for i := 0; i < 1<<uint(n); i++ { 533 bits += 848251 // arbitrary prime 534 x := val | expandBits(bits, ^mask) | uint32(i)%15<<28 535 try([]byte{byte(x), byte(x >> 8), byte(x >> 16), byte(x >> 24)}) 536 } 537 } 538 539 // vfpCases generates VFP instructions. 540 func vfpCases(t *testing.T) func(func([]byte)) { 541 const ( 542 vfpmask uint32 = 0xFF00FE10 543 vfp uint32 = 0x0E009A00 544 ) 545 return func(try func([]byte)) { 546 tryCondMask(0xff00fe10, 0x0e009a00, try) // standard VFP instruction space 547 tryCondMask(0xffc00f7f, 0x0e000b10, try) // VFP MOV core reg to/from float64 half 548 tryCondMask(0xffe00f7f, 0x0e000a10, try) // VFP MOV core reg to/from float32 549 tryCondMask(0xffef0fff, 0x0ee10a10, try) // VFP MOV core reg to/from cond codes 550 } 551 } 552 553 // hexCases generates the cases written in hexadecimal in the encoded string. 554 // Spaces in 'encoded' separate entire test cases, not individual bytes. 555 func hexCases(t *testing.T, encoded string) func(func([]byte)) { 556 return func(try func([]byte)) { 557 for _, x := range strings.Fields(encoded) { 558 src, err := hex.DecodeString(x) 559 if err != nil { 560 t.Errorf("parsing %q: %v", x, err) 561 } 562 try(src) 563 } 564 } 565 } 566 567 // testdataCases generates the test cases recorded in testdata/decode.txt. 568 // It only uses the inputs; it ignores the answers recorded in that file. 569 func testdataCases(t *testing.T) func(func([]byte)) { 570 var codes [][]byte 571 data, err := ioutil.ReadFile("testdata/decode.txt") 572 if err != nil { 573 t.Fatal(err) 574 } 575 for _, line := range strings.Split(string(data), "\n") { 576 line = strings.TrimSpace(line) 577 if line == "" || strings.HasPrefix(line, "#") { 578 continue 579 } 580 f := strings.Fields(line)[0] 581 i := strings.Index(f, "|") 582 if i < 0 { 583 t.Errorf("parsing %q: missing | separator", f) 584 continue 585 } 586 if i%2 != 0 { 587 t.Errorf("parsing %q: misaligned | separator", f) 588 } 589 code, err := hex.DecodeString(f[:i] + f[i+1:]) 590 if err != nil { 591 t.Errorf("parsing %q: %v", f, err) 592 continue 593 } 594 codes = append(codes, code) 595 } 596 597 return func(try func([]byte)) { 598 for _, code := range codes { 599 try(code) 600 } 601 } 602 } 603 604 func caller(skip int) string { 605 pc, _, _, _ := runtime.Caller(skip) 606 f := runtime.FuncForPC(pc) 607 name := "?" 608 if f != nil { 609 name = f.Name() 610 if i := strings.LastIndex(name, "."); i >= 0 { 611 name = name[i+1:] 612 } 613 } 614 return name 615 }