1
2
3
4
5
6
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
39
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
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
62
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
81 func (ext *ExtDis) Wait() error {
82 return ext.Cmd.Wait()
83 }
84
85
86
87
88
89
90
91
92
93
94
95
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
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
126 var (
127 totalTests = 0
128 totalSkips = 0
129 totalErrors = 0
130
131 errors = make([]string, 0, 100)
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
188
189
190
191
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
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
229
230 func disasm(syntax string, mode Mode, src []byte) (inst Inst, text string) {
231
232
233
234
235
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
248
249 case "gnu":
250 text = GNUSyntax(inst)
251
252
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
273
274
275 func coverage() float64 {
276
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
315
316
317
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
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
345 func isHex(b byte) bool { return b == '0' || unhex[b] > 0 }
346
347
348
349
350
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
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
418
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
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
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
467
468
469
470
471
472
473 func condCases(t *testing.T) func(func([]byte)) {
474 return func(try func([]byte)) {
475
476
477
478
479
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
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
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
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)
547 tryCondMask(0xffc00f7f, 0x0e000b10, try)
548 tryCondMask(0xffe00f7f, 0x0e000a10, try)
549 tryCondMask(0xffef0fff, 0x0ee10a10, try)
550 }
551 }
552
553
554
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
568
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 }
616
View as plain text