golang.org/x/arch@v0.17.0/x86/xeddata/database.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  	"errors"
    10  	"fmt"
    11  	"io"
    12  	"io/ioutil"
    13  	"os"
    14  	"path/filepath"
    15  	"regexp"
    16  	"strings"
    17  )
    18  
    19  // Types for XED enum-like constants.
    20  type (
    21  	// OperandSizeMode describes operand size mode (66H prefix).
    22  	OperandSizeMode int
    23  
    24  	// AddressSizeMode describes address size mode (67H prefix).
    25  	AddressSizeMode int
    26  
    27  	// CPUMode describes availability in certain CPU mode.
    28  	CPUMode int
    29  )
    30  
    31  // Possible operand size modes. XED calls it OSZ.
    32  const (
    33  	OpSize16 OperandSizeMode = iota
    34  	OpSize32
    35  	OpSize64
    36  )
    37  
    38  // Possible address size modes. XED calls it ASZ.
    39  const (
    40  	AddrSize16 AddressSizeMode = iota
    41  	AddrSize32
    42  	AddrSize64
    43  )
    44  
    45  // Possible CPU modes. XED calls it MODE.
    46  const (
    47  	Mode16 CPUMode = iota
    48  	Mode32
    49  	Mode64
    50  )
    51  
    52  var sizeStrings = [...]string{"16", "32", "64"}
    53  
    54  // sizeString maps size enumeration value to it's string representation.
    55  func sizeString(size int) string {
    56  	// Panic more gracefully than with "index out of range".
    57  	// If client code specified invalid size enumeration,
    58  	// this is programming error that should be fixed, not "handled".
    59  	if size >= len(sizeStrings) {
    60  		panic(fmt.Sprintf("illegal size value: %d", size))
    61  	}
    62  	return sizeStrings[size]
    63  }
    64  
    65  // String returns osz bit size string. Panics on illegal enumerations.
    66  func (osz OperandSizeMode) String() string { return sizeString(int(osz)) }
    67  
    68  // String returns asz bit size string. Panics on illegal enumerations.
    69  func (asz AddressSizeMode) String() string { return sizeString(int(asz)) }
    70  
    71  // Database holds information that is required to
    72  // properly handle XED datafiles.
    73  type Database struct {
    74  	widths map[string]*width // all-widths.txt
    75  	states map[string]string // all-state.txt
    76  	xtypes map[string]*xtype // all-element-types.txt
    77  
    78  	// extraWidth is a "all-extra-widths.txt" record.
    79  	//
    80  	// It provides a default mapping from an operand type to a width code.
    81  	//
    82  	// The key is one of three things:
    83  	// - "XED_REG_<name>" for a register (e.g., "XED_REG_EAX")
    84  	// - "<name>()" for a non-terminal (e.g., "GPR32_R()"")
    85  	// - "<name>" for an immediate const (e.g., "AGEN")
    86  	extraWidths map[string]string // all-extra-widths.txt
    87  }
    88  
    89  // width is a "all-widths.txt" record.
    90  type width struct {
    91  	// Default xtype name (examples: int, i8, f32).
    92  	xtype string
    93  
    94  	// 16, 32 and 64 bit sizes (all may have same value).
    95  	sizes [3]string
    96  }
    97  
    98  // xtype is a "all-element-type.txt" record.
    99  type xtype struct {
   100  	// Name is xtype identifier.
   101  	name string
   102  
   103  	// baseType specifies xtype base type.
   104  	// See "all-element-type-base.txt".
   105  	baseType string
   106  
   107  	// Size is an operand data size in bits.
   108  	size string
   109  }
   110  
   111  // NewDatabase returns Database that loads everything
   112  // it can find in xedPath.
   113  // Missing lookup file is not an error, but error during
   114  // parsing of found file is.
   115  //
   116  // Lookup:
   117  //
   118  //	"$xedPath/all-state.txt" => db.LoadStates()
   119  //	"$xedPath/all-widths.txt" => db.LoadWidths()
   120  //	"$xedPath/all-element-types.txt" => db.LoadXtypes()
   121  //
   122  // $xedPath is the interpolated value of function argument.
   123  //
   124  // The call NewDatabase("") is valid and returns empty database.
   125  // Load methods can be used to read lookup files one-by-one.
   126  func NewDatabase(xedPath string) (*Database, error) {
   127  	var db Database
   128  
   129  	stat, err := os.Stat(xedPath)
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  	if !stat.IsDir() {
   134  		return nil, errors.New("xedPath is not directory")
   135  	}
   136  
   137  	states, err := os.Open(filepath.Join(xedPath, "all-state.txt"))
   138  	if err == nil {
   139  		err = db.LoadStates(states)
   140  		if err != nil {
   141  			return &db, err
   142  		}
   143  	}
   144  
   145  	widths, err := os.Open(filepath.Join(xedPath, "all-widths.txt"))
   146  	if err == nil {
   147  		err = db.LoadWidths(widths)
   148  		if err != nil {
   149  			return &db, err
   150  		}
   151  	}
   152  
   153  	extraWidths, err := os.Open(filepath.Join(xedPath, "all-extra-widths.txt"))
   154  	if err == nil {
   155  		db.extraWidths, err = parseExtraWidths(extraWidths)
   156  		if err != nil {
   157  			return &db, err
   158  		}
   159  	}
   160  
   161  	xtypes, err := os.Open(filepath.Join(xedPath, "all-element-types.txt"))
   162  	if err == nil {
   163  		err = db.LoadXtypes(xtypes)
   164  		if err != nil {
   165  			return &db, err
   166  		}
   167  	}
   168  
   169  	return &db, nil
   170  }
   171  
   172  // LoadWidths reads XED widths definitions from r and updates db.
   173  // "widths" are 16/32/64 bit mode type sizes.
   174  // See "$XED/obj/dgen/all-widths.txt".
   175  func (db *Database) LoadWidths(r io.Reader) error {
   176  	var err error
   177  	db.widths, err = parseWidths(r)
   178  	return err
   179  }
   180  
   181  // LoadStates reads XED states definitions from r and updates db.
   182  // "states" are simple macro substitutions without parameters.
   183  // See "$XED/obj/dgen/all-state.txt".
   184  func (db *Database) LoadStates(r io.Reader) error {
   185  	var err error
   186  	db.states, err = parseStates(r)
   187  	return err
   188  }
   189  
   190  // LoadXtypes reads XED xtypes definitions from r and updates db.
   191  // "xtypes" are low-level XED type names.
   192  // See "$XED/obj/dgen/all-element-types.txt".
   193  // See "$XED/obj/dgen/all-element-type-base.txt".
   194  func (db *Database) LoadXtypes(r io.Reader) error {
   195  	var err error
   196  	db.xtypes, err = parseXtypes(r)
   197  	return err
   198  }
   199  
   200  // WidthSize translates width string to size string using desired
   201  // SizeMode m. For some widths output is the same for any valid value of m.
   202  //
   203  // The size string may be a decimal number of bytes, like "8". It may of the
   204  // form "%dbits" to indicate a bit width. Or in some cases it's "0" for
   205  // "unusual" registers.
   206  func (db *Database) WidthSize(width string, m OperandSizeMode) string {
   207  	info := db.widths[width]
   208  	if info == nil {
   209  		return ""
   210  	}
   211  	return info.sizes[m]
   212  }
   213  
   214  func parseWidths(r io.Reader) (map[string]*width, error) {
   215  	data, err := ioutil.ReadAll(r)
   216  	if err != nil {
   217  		return nil, fmt.Errorf("parse widths: %v", err)
   218  	}
   219  
   220  	// Lines have two forms:
   221  	// 1. name xtype size [# comment]
   222  	// 2. name xtype size16, size32, size64 [# comment]
   223  	reLine := regexp.MustCompile(`(^\s*\w+\s+\w+\s+\w+\s+\w+\s+\w+)|(^\s*\w+\s+\w+\s+\w+)`)
   224  
   225  	widths := make(map[string]*width, 128)
   226  	for _, l := range bytes.Split(data, []byte("\n")) {
   227  		var name, xtype, size16, size32, size64 string
   228  
   229  		if m := reLine.FindSubmatch(l); m != nil {
   230  			var f [][]byte
   231  			if m[1] != nil {
   232  				f = bytes.Fields(m[1])
   233  			} else {
   234  				f = bytes.Fields(m[2])
   235  			}
   236  
   237  			name = string(f[0])
   238  			xtype = string(f[1])
   239  			if len(f) > 3 {
   240  				size16 = string(f[2])
   241  				size32 = string(f[3])
   242  				size64 = string(f[4])
   243  			} else {
   244  				size16 = string(f[2])
   245  				size32 = size16
   246  				size64 = size16
   247  			}
   248  		}
   249  		if name != "" {
   250  			widths[name] = &width{
   251  				xtype: xtype,
   252  				sizes: [3]string{size16, size32, size64},
   253  			}
   254  		}
   255  	}
   256  
   257  	return widths, nil
   258  }
   259  
   260  func parseExtraWidths(r io.Reader) (map[string]string, error) {
   261  	extraWidths := make(map[string]string)
   262  	for line, err := range readLines(r) {
   263  		if err != nil {
   264  			return nil, err
   265  		}
   266  		f := bytes.Fields(line.data)
   267  		if len(f) != 3 {
   268  			return nil, fmt.Errorf("want 3 fields, got %d", len(f))
   269  		}
   270  		switch string(f[0]) {
   271  		default:
   272  			return nil, fmt.Errorf("unknown extra width type %s", f[0])
   273  		case "imm_const":
   274  			extraWidths[string(f[1])] = string(f[2])
   275  		case "reg":
   276  			extraWidths["XED_REG_"+string(f[1])] = string(f[2])
   277  		case "nt":
   278  			extraWidths[string(f[1])+"()"] = string(f[2])
   279  		}
   280  	}
   281  	return extraWidths, nil
   282  }
   283  
   284  func parseStates(r io.Reader) (map[string]string, error) {
   285  	data, err := ioutil.ReadAll(r)
   286  	if err != nil {
   287  		return nil, fmt.Errorf("parse states: %v", err)
   288  	}
   289  
   290  	// Lines have form of "name ...replacements [# comment]".
   291  	// This regexp captures the name and everything until line end or comment.
   292  	lineRE := regexp.MustCompile(`^\s*(\w+)\s+([^#]+)`)
   293  
   294  	states := make(map[string]string, 128)
   295  	for _, l := range strings.Split(string(data), "\n") {
   296  		if m := lineRE.FindStringSubmatch(l); m != nil {
   297  			name, replacements := m[1], m[2]
   298  			states[name] = strings.TrimSpace(replacements)
   299  		}
   300  	}
   301  
   302  	return states, nil
   303  }
   304  
   305  func parseXtypes(r io.Reader) (map[string]*xtype, error) {
   306  	data, err := ioutil.ReadAll(r)
   307  	if err != nil {
   308  		return nil, fmt.Errorf("parse xtypes: %v", err)
   309  	}
   310  
   311  	// Lines have form of "name baseType size [# comment]".
   312  	lineRE := regexp.MustCompile(`^\s*(\w+)\s+(\w+)\s*(\d+)`)
   313  
   314  	xtypes := make(map[string]*xtype)
   315  	for _, l := range strings.Split(string(data), "\n") {
   316  		if m := lineRE.FindStringSubmatch(l); m != nil {
   317  			name, baseType, size := m[1], m[2], m[3]
   318  			xtypes[name] = &xtype{
   319  				name:     name,
   320  				baseType: baseType,
   321  				size:     size,
   322  			}
   323  		}
   324  	}
   325  
   326  	return xtypes, nil
   327  }