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  }