1
2
3
4
5
6
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
40
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
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
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 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
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
124 var (
125 totalTests = 0
126 totalSkips = 0
127 totalErrors = 0
128
129 errors = make([]string, 0, 100)
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
186
187
188
189
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
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
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
232
233 func disasm(syntax string, src []byte) (inst Inst, text string) {
234
235
236
237
238
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
251
252 case "gnu":
253 text = GNUSyntax(inst, 0)
254
255
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
276
277
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
296
297
298
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
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
326 func isHex(b byte) bool { return b == '0' || unhex[b] > 0 }
327
328
329
330
331
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
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
399
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
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
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
448
449
450
451
452
453
454 func randomCases(t *testing.T) func(func([]byte)) {
455 return func(try func([]byte)) {
456
457
458
459
460
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
480
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
494
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 }
542
View as plain text