golang.org/x/arch@v0.17.0/x86/xeddata/xeddata_test.go (about) 1 // Copyright 2018 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 package xeddata 6 7 import ( 8 "bytes" 9 "encoding/json" 10 "fmt" 11 "io" 12 "io/ioutil" 13 "path" 14 "reflect" 15 "strings" 16 "testing" 17 ) 18 19 // Small database to generate state/xtype/width input files and validate parse results. 20 // 21 // Tests should use only those symbols that are defined inside test maps. 22 // For example, if {"foo"=>"bar"} element is not in statesMap, tests 23 // can't expect that "foo" get's replaced by "bar". 24 var ( 25 statesMap = map[string]string{ 26 "not64": "MODE!=2", 27 "mode64": "MODE=2", 28 "mode32": "MODE=1", 29 "mode16": "MODE=0", 30 "rexw_prefix": "REXW=1 SKIP_OSZ=1", 31 "norexw_prefix": "REXW=0 SKIP_OSZ=1", 32 "W1": "REXW=1 SKIP_OSZ=1", 33 "W0": "REXW=0 SKIP_OSZ=1", 34 "VV1": "VEXVALID=1", 35 "V66": "VEX_PREFIX=1", 36 "VF2": "VEX_PREFIX=2", 37 "VF3": "VEX_PREFIX=3", 38 "V0F": "MAP=1", 39 "V0F38": "MAP=2", 40 "V0F3A": "MAP=3", 41 "VL128": "VL=0", 42 "VL256": "VL=1", 43 } 44 45 xtypesMap = map[string]*xtype{ 46 "int": {name: "int", baseType: "INT", size: "0"}, 47 "i8": {name: "i8", baseType: "INT", size: "8"}, 48 "i64": {name: "i64", baseType: "INT", size: "64"}, 49 "i32": {name: "i32", baseType: "INT", size: "32"}, 50 "u8": {name: "u8", baseType: "UINT", size: "8"}, 51 "f32": {name: "f32", baseType: "SIGNLE", size: "32"}, 52 "f64": {name: "f64", baseType: "DOUBLE", size: "64"}, 53 "var": {name: "var", baseType: "VARIABLE", size: "0"}, 54 } 55 56 widthsMap = map[string]*width{ 57 "q": {xtype: "i64", sizes: [3]string{"8", "8", "8"}}, 58 "z": {xtype: "int", sizes: [3]string{"2", "4", "4"}}, 59 "b": {xtype: "u8", sizes: [3]string{"1", "1", "1"}}, 60 "d": {xtype: "i32", sizes: [3]string{"4", "4", "4"}}, 61 "ps": {xtype: "f32", sizes: [3]string{"16", "16", "16"}}, 62 "dq": {xtype: "i32", sizes: [3]string{"16", "16", "16"}}, 63 "i32": {xtype: "i32", sizes: [3]string{"4", "4", "4"}}, 64 "i64": {xtype: "i64", sizes: [3]string{"8", "8", "8"}}, 65 "vv": {xtype: "var", sizes: [3]string{"0", "0", "0"}}, 66 "mskw": {xtype: "i1", sizes: [3]string{"64bits", "64bits", "64bits"}}, 67 "zf32": {xtype: "f32", sizes: [3]string{"512bits", "512bits", "512bits"}}, 68 "zf64": {xtype: "f64", sizes: [3]string{"512bits", "512bits", "512bits"}}, 69 "mem80real": {xtype: "f80", sizes: [3]string{"10", "10", "10"}}, 70 "mfpxenv": {xtype: "struct", sizes: [3]string{"512", "512", "512"}}, 71 } 72 73 extraWidthsMap = map[string]string{ 74 "AGEN": "pseudo", 75 "XED_REG_EAX": "d", 76 "GPR32_R()": "d", 77 } 78 ) 79 80 // newStatesSource returns a reader that mocks "all-state.txt" file. 81 // Input content is generated based on statesMap. 82 func newStatesSource() io.Reader { 83 var buf bytes.Buffer 84 i := 0 85 for k, v := range statesMap { 86 buf.WriteString("# Line comment\n") 87 buf.WriteString("#\n\n\n") 88 fmt.Fprintf(&buf, "\t%-20s%s", k, v) 89 if i%3 == 0 { 90 buf.WriteString("\t# Trailing comment") 91 } 92 buf.WriteByte('\n') 93 i++ 94 } 95 96 return &buf 97 } 98 99 // newWidthsSource returns a reader that mocks "all-widths.txt" file. 100 // Input content is generated based on widthsMap. 101 func newWidthsSource() io.Reader { 102 var buf bytes.Buffer 103 i := 0 104 for name, width := range widthsMap { 105 buf.WriteString("# Line comment\n") 106 buf.WriteString("#\n\n\n") 107 eqSizes := width.sizes[0] == width.sizes[1] && 108 width.sizes[0] == width.sizes[2] 109 if i%2 == 0 && eqSizes { 110 fmt.Fprintf(&buf, "\t%-16s%-12s%-8s", 111 name, width.xtype, width.sizes[0]) 112 } else { 113 fmt.Fprintf(&buf, "\t%-16s%-12s%-8s%-8s%-8s", 114 name, width.xtype, 115 width.sizes[0], width.sizes[1], width.sizes[2]) 116 } 117 if i%3 == 0 { 118 buf.WriteString("\t# Trailing comment") 119 } 120 buf.WriteByte('\n') 121 i++ 122 } 123 124 return &buf 125 } 126 127 func newExtraWidthsSource() io.Reader { 128 var buf bytes.Buffer 129 for name, width := range extraWidthsMap { 130 buf.WriteString("# Line comment\n") 131 buf.WriteString("#\n\n\n") 132 if reg, ok := strings.CutPrefix(name, "XED_REG_"); ok { 133 fmt.Fprintf(&buf, "reg %s %s\n", reg, width) 134 } else if nt, ok := strings.CutSuffix(name, "()"); ok { 135 fmt.Fprintf(&buf, "nt %s %s\n", nt, width) 136 } else { 137 fmt.Fprintf(&buf, "imm_const %s %s\n", name, width) 138 } 139 } 140 return &buf 141 } 142 143 // newXtypesSource returns a reader that mocks "all-element-types.txt" file. 144 // Input content is generated based on xtypesMap. 145 func newXtypesSource() io.Reader { 146 var buf bytes.Buffer 147 i := 0 148 for _, v := range xtypesMap { 149 buf.WriteString("# Line comment\n") 150 buf.WriteString("#\n\n\n") 151 152 fmt.Fprintf(&buf, "\t%s %s %s", 153 v.name, v.baseType, v.size) 154 155 if i%3 == 0 { 156 buf.WriteString("\t# Trailing comment") 157 } 158 buf.WriteByte('\n') 159 i++ 160 } 161 162 return &buf 163 } 164 165 func newTestDatabase(t *testing.T) *Database { 166 var db Database 167 err := db.LoadStates(newStatesSource()) 168 if err != nil { 169 t.Fatal(err) 170 } 171 err = db.LoadWidths(newWidthsSource()) 172 if err != nil { 173 t.Fatal(err) 174 } 175 db.extraWidths, err = parseExtraWidths(newExtraWidthsSource()) 176 if err != nil { 177 t.Fatal(err) 178 } 179 err = db.LoadXtypes(newXtypesSource()) 180 if err != nil { 181 t.Fatal(err) 182 } 183 return &db 184 } 185 186 func TestContainsWord(t *testing.T) { 187 tests := []struct { 188 attrs string 189 attrName string 190 output bool 191 }{ 192 {"ATT1", "ATT1", true}, 193 {" ATT1", "ATT1", true}, 194 {"ATT1 ", "ATT1", true}, 195 {" ATT1 ", "ATT1", true}, 196 {"ATT1 ATT2 ATT3", "ATT1", true}, 197 {"ATT1 ATT2 ATT3", "ATT2", true}, 198 {"ATT1 ATT2 ATT3", "ATT2", true}, 199 {"ATT1 ATT2 ATT3", "ATT4", false}, 200 {"ATT1ATT1", "ATT1", false}, 201 {".ATT1", "ATT1", false}, 202 {".ATT1.", "ATT1", false}, 203 {"ATT1.", "ATT1", false}, 204 {"", "ATT1", false}, 205 {"AT", "ATT1", false}, 206 {"ATT 1", "ATT1", false}, 207 {" ATT1 ", "TT", false}, 208 {" ATT1 ", "T1", false}, 209 {" ATT1 ", "AT", false}, 210 } 211 212 for _, test := range tests { 213 output := containsWord(test.attrs, test.attrName) 214 if output != test.output { 215 t.Errorf("containsWord(%q, %q)):\nhave: %v\nwant: %v", 216 test.attrs, test.attrName, output, test.output) 217 } 218 } 219 } 220 221 func TestParseWidths(t *testing.T) { 222 have, err := parseWidths(newWidthsSource()) 223 if err != nil { 224 t.Fatal(err) 225 } 226 for k := range widthsMap { 227 if have[k] == nil { 228 t.Fatalf("missing key %s", k) 229 } 230 if *have[k] != *widthsMap[k] { 231 t.Fatalf("key %s:\nhave: %#v\nwant: %#v", 232 k, have[k], widthsMap[k]) 233 } 234 } 235 if !reflect.DeepEqual(have, widthsMap) { 236 t.Errorf("widths output mismatch:\nhave: %#v\nwant: %#v", 237 have, widthsMap) 238 } 239 } 240 241 func TestParseStates(t *testing.T) { 242 have, err := parseStates(newStatesSource()) 243 if err != nil { 244 t.Fatal(err) 245 } 246 want := statesMap 247 if !reflect.DeepEqual(have, want) { 248 t.Errorf("states output mismatch:\nhave: %v\nwant: %v", have, want) 249 } 250 } 251 252 func TestParseXtypes(t *testing.T) { 253 have, err := parseXtypes(newXtypesSource()) 254 if err != nil { 255 t.Fatal(err) 256 } 257 for k := range xtypesMap { 258 if have[k] == nil { 259 t.Fatalf("missing key %s", k) 260 } 261 if *have[k] != *xtypesMap[k] { 262 t.Fatalf("key %s:\nhave: %#v\nwant: %#v", 263 k, have[k], xtypesMap[k]) 264 } 265 } 266 if !reflect.DeepEqual(have, xtypesMap) { 267 t.Fatalf("xtype maps are not equal") 268 } 269 } 270 271 func TestNewOperand(t *testing.T) { 272 tests := []struct { 273 input string 274 op Operand 275 }{ 276 // Simple cases. 277 { 278 "REG0=XMM_R():r", 279 Operand{Name: "REG0=XMM_R()", Action: "r"}, 280 }, 281 { 282 "REG0=XMM_R:w", 283 Operand{Name: "REG0=XMM_R", Action: "w"}, 284 }, 285 { 286 "MEM0:rw:q", 287 Operand{Name: "MEM0", Action: "rw", Width: "q", Xtype: "i64"}, 288 }, 289 { 290 "REG0=XMM_R():rcw:ps:f32", 291 Operand{Name: "REG0=XMM_R()", Action: "rcw", Width: "ps", Xtype: "f32"}, 292 }, 293 { 294 "IMM0:r:z", 295 Operand{Name: "IMM0", Action: "r", Width: "z", Xtype: "int"}, 296 }, 297 { 298 "IMM1:cw:b:i8", 299 Operand{Name: "IMM1", Action: "cw", Width: "b", Xtype: "i8"}, 300 }, 301 302 // Implied width code 303 { 304 "AGEN:r", 305 Operand{Name: "AGEN", Action: "r", Width: "pseudo"}, 306 }, 307 { 308 "REG0=XED_REG_EAX:r", 309 Operand{Name: "REG0=XED_REG_EAX", Action: "r", Width: "d", Xtype: "i32"}, 310 }, 311 { 312 "REG0=GPR32_R():r", 313 Operand{Name: "REG0=GPR32_R()", Action: "r", Width: "d", Xtype: "i32"}, 314 }, 315 316 // Optional fields and visibility. 317 { 318 "REG2:r:EXPL", 319 Operand{Name: "REG2", Action: "r", Visibility: VisExplicit}, 320 }, 321 { 322 "MEM1:w:d:IMPL", 323 Operand{Name: "MEM1", Action: "w", Width: "d", Xtype: "i32", Visibility: VisImplicit}, 324 }, 325 { 326 "MEM1:w:IMPL:d", 327 Operand{Name: "MEM1", Action: "w", Width: "d", Xtype: "i32", Visibility: VisImplicit}, 328 }, 329 { 330 "MEM1:w:d:SUPP:f32", 331 Operand{Name: "MEM1", Action: "w", Width: "d", Visibility: VisSuppressed, Xtype: "f32"}, 332 }, 333 { 334 "MEM1:w:SUPP:d:f32", 335 Operand{Name: "MEM1", Action: "w", Width: "d", Visibility: VisSuppressed, Xtype: "f32"}, 336 }, 337 338 // Ambiguity: xtypes that look like widths. 339 { 340 "REG0=XMM_R():w:dq:i64", 341 Operand{Name: "REG0=XMM_R()", Action: "w", Width: "dq", Xtype: "i64"}, 342 }, 343 344 // TXT=X field. 345 { 346 "REG1=MASK1():r:mskw:TXT=ZEROSTR", 347 Operand{Name: "REG1=MASK1()", Action: "r", Width: "mskw", Xtype: "i1", 348 Attributes: map[string]bool{"TXT=ZEROSTR": true}}, 349 }, 350 { 351 "MEM0:r:vv:f64:TXT=BCASTSTR", 352 Operand{Name: "MEM0", Action: "r", Width: "vv", Xtype: "f64", 353 Attributes: map[string]bool{"TXT=BCASTSTR": true}}, 354 }, 355 { 356 "REG0=ZMM_R3():w:zf32:TXT=SAESTR", 357 Operand{Name: "REG0=ZMM_R3()", Action: "w", Width: "zf32", Xtype: "f32", 358 Attributes: map[string]bool{"TXT=SAESTR": true}}, 359 }, 360 { 361 "REG0=ZMM_R3():w:zf64:TXT=ROUNDC", 362 Operand{Name: "REG0=ZMM_R3()", Action: "w", Width: "zf64", Xtype: "f64", 363 Attributes: map[string]bool{"TXT=ROUNDC": true}}, 364 }, 365 366 // Multi-source. 367 { 368 "REG2=ZMM_N3():r:zf32:MULTISOURCE4", 369 Operand{Name: "REG2=ZMM_N3()", Action: "r", Width: "zf32", Xtype: "f32", 370 Attributes: map[string]bool{"MULTISOURCE4": true}}, 371 }, 372 373 // Multi-source + EVEX.b context. 374 { 375 "REG2=ZMM_N3():r:zf32:MULTISOURCE4:TXT=SAESTR", 376 Operand{Name: "REG2=ZMM_N3()", Action: "r", Width: "zf32", Xtype: "f32", 377 Attributes: map[string]bool{"MULTISOURCE4": true, "TXT=SAESTR": true}}, 378 }, 379 } 380 381 db := newTestDatabase(t) 382 for _, test := range tests { 383 op, err := NewOperand(db, test.input) 384 if err != nil { 385 t.Fatal(err) 386 } 387 if !reflect.DeepEqual(*op, test.op) { 388 t.Errorf("parse(`%s`): output mismatch\nhave: %#v\nwant: %#v", 389 test.input, op, test.op, 390 ) 391 } 392 } 393 } 394 395 func TestReader(t *testing.T) { 396 type test struct { 397 name string 398 input string 399 output string 400 } 401 402 var tests []test 403 { 404 b, err := ioutil.ReadFile(path.Join("testdata", "xed_objects.txt")) 405 if err != nil { 406 t.Fatal(err) 407 } 408 cases := strings.Split(string(b), "------")[1:] 409 for _, c := range cases { 410 name := c[:strings.Index(c, "\n")] 411 parts := strings.Split(c[len(name):], "====") 412 413 tests = append(tests, test{ 414 name: strings.TrimSpace(name), 415 input: strings.TrimSpace(parts[0]), 416 output: strings.TrimSpace(parts[1]), 417 }) 418 } 419 } 420 421 for _, test := range tests { 422 r := NewReader(strings.NewReader(test.input)) 423 objects, err := r.ReadAll() 424 if strings.Contains(test.name, "INVALID") { 425 if err == nil { 426 t.Errorf("%s: expected non-nil error", test.name) 427 continue 428 } 429 if err.Error() != test.output { 430 t.Errorf("%s: error mismatch\nhave: `%s`\nwant: `%s`\n", 431 test.name, err.Error(), test.output) 432 } 433 t.Logf("PASS: %s", test.name) 434 continue 435 } 436 if err != nil { 437 t.Fatal(err) 438 } 439 440 var have []map[string]string 441 for _, o := range objects { 442 for _, inst := range o.Insts { 443 var result map[string]string 444 err := json.Unmarshal([]byte(inst.String()), &result) 445 if err != nil { 446 t.Fatal(err) 447 } 448 have = append(have, result) 449 } 450 } 451 var want []map[string]string 452 err = json.Unmarshal([]byte(test.output), &want) 453 if err != nil { 454 t.Fatal(err) 455 } 456 for i := range want { 457 for k := range want[i] { 458 if want[i][k] == have[i][k] { 459 continue 460 } 461 // i - index inside array of JSON objects. 462 // k - i'th object key (example: "Iclass"). 463 t.Errorf("%s: insts[%d].%s mismatch\nhave: `%s`\nwant: `%s`", 464 test.name, i, k, have[i][k], want[i][k]) 465 } 466 } 467 if !t.Failed() { 468 t.Logf("PASS: %s", test.name) 469 } 470 } 471 } 472 473 func TestReaderPos(t *testing.T) { 474 const data = `# Comment 475 { 476 ICLASS: iclass1 477 DISASM: disasm1 478 479 PATTERN: pat1 pat1 480 OPERANDS: ops1 ops1 481 }` 482 r := NewReader(namedReader{strings.NewReader(data), "test"}) 483 objects, err := r.ReadAll() 484 if err != nil { 485 t.Fatal(err) 486 } 487 488 if want := "test:2"; objects[0].Pos.String() != want { 489 t.Errorf("object Pos: got %q, want %q", objects[0].Pos, want) 490 } 491 if want := "test:6"; objects[0].Insts[0].Pos.String() != want { 492 t.Errorf("inst Pos: got %q, want %q", objects[0].Insts[0].Pos, want) 493 } 494 } 495 496 type namedReader struct { 497 r io.Reader 498 name string 499 } 500 501 func (n namedReader) Read(p []byte) (int, error) { 502 return n.r.Read(p) 503 } 504 505 func (n namedReader) Name() string { 506 return n.name 507 } 508 509 func TestMacroExpand(t *testing.T) { 510 tests := [...]struct { 511 input string 512 output string 513 }{ 514 0: { 515 "a not64 b c", 516 "a MODE!=2 b c", 517 }, 518 1: { 519 "mode16 W0", 520 "MODE=0 REXW=0 SKIP_OSZ=1", 521 }, 522 2: { 523 "W1 mode32", 524 "REXW=1 SKIP_OSZ=1 MODE=1", 525 }, 526 3: { 527 "W1 W1", 528 "REXW=1 SKIP_OSZ=1 REXW=1 SKIP_OSZ=1", 529 }, 530 4: { 531 "W1W1", 532 "W1W1", 533 }, 534 5: { 535 "mode64 1 2 3 rexw_prefix", 536 "MODE=2 1 2 3 REXW=1 SKIP_OSZ=1", 537 }, 538 6: { 539 "a b c", 540 "a b c", 541 }, 542 7: { 543 "mode16 mode32 mode16 mode16", 544 "MODE=0 MODE=1 MODE=0 MODE=0", 545 }, 546 8: { 547 "V0F38 V0FV0F V0FV0F38", 548 "MAP=2 V0FV0F V0FV0F38", 549 }, 550 9: { 551 "VV1 0x2E V66 V0F38 VL128 norexw_prefix MOD[mm] MOD!=3 REG[rrr] RM[nnn] MODRM()", 552 "VEXVALID=1 0x2E VEX_PREFIX=1 MAP=2 VL=0 REXW=0 SKIP_OSZ=1 MOD[mm] MOD!=3 REG[rrr] RM[nnn] MODRM()", 553 }, 554 } 555 556 db := newTestDatabase(t) 557 for id, test := range tests { 558 have := ExpandStates(db, test.input) 559 if test.output != have { 560 t.Errorf("test %d: output mismatch:\nhave: `%s`\nwant: `%s`", 561 id, have, test.output) 562 } 563 } 564 }