mirror of
				https://github.com/go-delve/delve.git
				synced 2025-10-31 18:57:18 +08:00 
			
		
		
		
	proc,vendor: show global variables in disassembly
updates vendored version of x86asm, adds a symbol lookup function to pass to the disassembler. This will show global symbol names in the disassembly like go tool objdump does.
This commit is contained in:
		
							
								
								
									
										2
									
								
								glide.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								glide.lock
									
									
									
										generated
									
									
									
								
							| @ -24,7 +24,7 @@ imports: | ||||
| - name: github.com/spf13/pflag | ||||
|   version: 9e4c21054fa135711121b557932b1887f2405d92 | ||||
| - name: golang.org/x/arch | ||||
|   version: 58ea1a195b1a354bcd572b7ef6bbbd264dc63732 | ||||
|   version: 077ac972c2e48fdb75168a5ba36a387f0c72d8aa | ||||
|   subpackages: | ||||
|   - x86/x86asm | ||||
| - name: golang.org/x/sys | ||||
|  | ||||
| @ -17,7 +17,7 @@ import: | ||||
| - package: github.com/spf13/pflag | ||||
|   version: 9e4c21054fa135711121b557932b1887f2405d92 | ||||
| - package: golang.org/x/arch | ||||
|   version: 58ea1a195b1a354bcd572b7ef6bbbd264dc63732 | ||||
|   version: 077ac972c2e48fdb75168a5ba36a387f0c72d8aa | ||||
|   subpackages: | ||||
|   - x86/x86asm | ||||
| - package: golang.org/x/sys | ||||
|  | ||||
| @ -38,10 +38,10 @@ type BinaryInfo struct { | ||||
| 	loclist       loclistReader | ||||
| 	compileUnits  []*compileUnit | ||||
| 	types         map[string]dwarf.Offset | ||||
| 	packageVars   map[string]dwarf.Offset | ||||
| 	packageVars   []packageVar // packageVars is a list of all global/package variables in debug_info, sorted by address | ||||
| 	gStructOffset uint64 | ||||
|  | ||||
| 	// Functions is a list of all DW_TAG_subprogram entries in debug_info. | ||||
| 	// Functions is a list of all DW_TAG_subprogram entries in debug_info, sorted by entry point | ||||
| 	Functions []Function | ||||
| 	// Sources is a list of all source files found in debug_line. | ||||
| 	Sources []string | ||||
| @ -149,6 +149,15 @@ type constantValue struct { | ||||
| 	singleBit bool | ||||
| } | ||||
|  | ||||
| // packageVar represents a package-level variable (or a C global variable). | ||||
| // If a global variable does not have an address (for example it's stored in | ||||
| // a register, or non-contiguously) addr will be 0. | ||||
| type packageVar struct { | ||||
| 	name   string | ||||
| 	offset dwarf.Offset | ||||
| 	addr   uint64 | ||||
| } | ||||
|  | ||||
| type loclistReader struct { | ||||
| 	data  []byte | ||||
| 	cur   int | ||||
|  | ||||
| @ -1,5 +1,7 @@ | ||||
| package proc | ||||
|  | ||||
| import "sort" | ||||
|  | ||||
| type AsmInstruction struct { | ||||
| 	Loc        Location | ||||
| 	DestLoc    *Location | ||||
| @ -14,6 +16,7 @@ type AssemblyFlavour int | ||||
| const ( | ||||
| 	GNUFlavour = AssemblyFlavour(iota) | ||||
| 	IntelFlavour | ||||
| 	GoFlavour | ||||
| ) | ||||
|  | ||||
| // Disassemble disassembles target memory between startPC and endPC, marking | ||||
| @ -83,3 +86,31 @@ func disassemble(memrw MemoryReadWriter, regs Registers, breakpoints *Breakpoint | ||||
| 	} | ||||
| 	return r, nil | ||||
| } | ||||
|  | ||||
| // Looks up symbol (either functions or global variables) at address addr. | ||||
| // Used by disassembly formatter. | ||||
| func (bi *BinaryInfo) symLookup(addr uint64) (string, uint64) { | ||||
| 	fn := bi.PCToFunc(addr) | ||||
| 	if fn != nil { | ||||
| 		if fn.Entry == addr { | ||||
| 			// only report the function name if it's the exact address because it's | ||||
| 			// easier to read the absolute address than function_name+offset. | ||||
| 			return fn.Name, fn.Entry | ||||
| 		} | ||||
| 		return "", 0 | ||||
| 	} | ||||
| 	i := sort.Search(len(bi.packageVars), func(i int) bool { | ||||
| 		return bi.packageVars[i].addr >= addr | ||||
| 	}) | ||||
| 	if i >= len(bi.packageVars) { | ||||
| 		return "", 0 | ||||
| 	} | ||||
| 	if bi.packageVars[i].addr > addr { | ||||
| 		// report previous variable + offset if i-th variable starts after addr | ||||
| 		i-- | ||||
| 	} | ||||
| 	if i > 0 { | ||||
| 		return bi.packageVars[i].name, bi.packageVars[i].addr | ||||
| 	} | ||||
| 	return "", 0 | ||||
| } | ||||
|  | ||||
| @ -34,7 +34,7 @@ func patchPCRel(pc uint64, inst *x86asm.Inst) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (inst *AsmInstruction) Text(flavour AssemblyFlavour) string { | ||||
| func (inst *AsmInstruction) Text(flavour AssemblyFlavour, bi *BinaryInfo) string { | ||||
| 	if inst.Inst == nil { | ||||
| 		return "?" | ||||
| 	} | ||||
| @ -43,15 +43,13 @@ func (inst *AsmInstruction) Text(flavour AssemblyFlavour) string { | ||||
|  | ||||
| 	switch flavour { | ||||
| 	case GNUFlavour: | ||||
| 		text = x86asm.GNUSyntax(x86asm.Inst(*inst.Inst)) | ||||
| 		text = x86asm.GNUSyntax(x86asm.Inst(*inst.Inst), inst.Loc.PC, bi.symLookup) | ||||
| 	case GoFlavour: | ||||
| 		text = x86asm.GoSyntax(x86asm.Inst(*inst.Inst), inst.Loc.PC, bi.symLookup) | ||||
| 	case IntelFlavour: | ||||
| 		fallthrough | ||||
| 	default: | ||||
| 		text = x86asm.IntelSyntax(x86asm.Inst(*inst.Inst)) | ||||
| 	} | ||||
|  | ||||
| 	if inst.IsCall() && inst.DestLoc != nil && inst.DestLoc.Fn != nil { | ||||
| 		text += " " + inst.DestLoc.Fn.Name | ||||
| 		text = x86asm.IntelSyntax(x86asm.Inst(*inst.Inst), inst.Loc.PC, bi.symLookup) | ||||
| 	} | ||||
|  | ||||
| 	return text | ||||
|  | ||||
| @ -3455,3 +3455,21 @@ func TestIssue1145(t *testing.T) { | ||||
| 		} | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func TestDisassembleGlobalVars(t *testing.T) { | ||||
| 	withTestProcess("teststepconcurrent", t, func(p proc.Process, fixture protest.Fixture) { | ||||
| 		mainfn := p.BinInfo().LookupFunc["main.main"] | ||||
| 		text, err := proc.Disassemble(p, nil, mainfn.Entry, mainfn.End) | ||||
| 		assertNoError(err, t, "Disassemble") | ||||
| 		found := false | ||||
| 		for i := range text { | ||||
| 			if strings.Index(text[i].Text(proc.IntelFlavour, p.BinInfo()), "main.v") > 0 { | ||||
| 				found = true | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 		if !found { | ||||
| 			t.Fatalf("could not find main.v reference in disassembly") | ||||
| 		} | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| @ -3,6 +3,7 @@ package proc | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"debug/dwarf" | ||||
| 	"encoding/binary" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"go/ast" | ||||
| @ -18,6 +19,7 @@ import ( | ||||
|  | ||||
| 	"github.com/derekparker/delve/pkg/dwarf/godwarf" | ||||
| 	"github.com/derekparker/delve/pkg/dwarf/line" | ||||
| 	"github.com/derekparker/delve/pkg/dwarf/op" | ||||
| 	"github.com/derekparker/delve/pkg/dwarf/reader" | ||||
| ) | ||||
|  | ||||
| @ -169,12 +171,18 @@ func (v compileUnitsByLowpc) Len() int               { return len(v) } | ||||
| func (v compileUnitsByLowpc) Less(i int, j int) bool { return v[i].LowPC < v[j].LowPC } | ||||
| func (v compileUnitsByLowpc) Swap(i int, j int)      { v[i], v[j] = v[j], v[i] } | ||||
|  | ||||
| type packageVarsByAddr []packageVar | ||||
|  | ||||
| func (v packageVarsByAddr) Len() int               { return len(v) } | ||||
| func (v packageVarsByAddr) Less(i int, j int) bool { return v[i].addr < v[j].addr } | ||||
| func (v packageVarsByAddr) Swap(i int, j int)      { v[i], v[j] = v[j], v[i] } | ||||
|  | ||||
| func (bi *BinaryInfo) loadDebugInfoMaps(debugLineBytes []byte, wg *sync.WaitGroup) { | ||||
| 	if wg != nil { | ||||
| 		defer wg.Done() | ||||
| 	} | ||||
| 	bi.types = make(map[string]dwarf.Offset) | ||||
| 	bi.packageVars = make(map[string]dwarf.Offset) | ||||
| 	bi.packageVars = []packageVar{} | ||||
| 	bi.Functions = []Function{} | ||||
| 	bi.compileUnits = []*compileUnit{} | ||||
| 	bi.consts = make(map[dwarf.Offset]*constantType) | ||||
| @ -226,7 +234,13 @@ func (bi *BinaryInfo) loadDebugInfoMaps(debugLineBytes []byte, wg *sync.WaitGrou | ||||
| 				if !cu.isgo { | ||||
| 					n = "C." + n | ||||
| 				} | ||||
| 				bi.packageVars[n] = entry.Offset | ||||
| 				var addr uint64 | ||||
| 				if loc, ok := entry.Val(dwarf.AttrLocation).([]byte); ok { | ||||
| 					if len(loc) == bi.Arch.PtrSize()+1 && op.Opcode(loc[0]) == op.DW_OP_addr { | ||||
| 						addr = binary.LittleEndian.Uint64(loc[1:]) | ||||
| 					} | ||||
| 				} | ||||
| 				bi.packageVars = append(bi.packageVars, packageVar{n, entry.Offset, addr}) | ||||
| 			} | ||||
|  | ||||
| 		case dwarf.TagConstant: | ||||
| @ -271,6 +285,7 @@ func (bi *BinaryInfo) loadDebugInfoMaps(debugLineBytes []byte, wg *sync.WaitGrou | ||||
| 	} | ||||
| 	sort.Sort(compileUnitsByLowpc(bi.compileUnits)) | ||||
| 	sort.Sort(functionsDebugInfoByEntry(bi.Functions)) | ||||
| 	sort.Sort(packageVarsByAddr(bi.packageVars)) | ||||
|  | ||||
| 	bi.LookupFunc = make(map[string]*Function) | ||||
| 	for i := range bi.Functions { | ||||
|  | ||||
| @ -653,10 +653,10 @@ func (scope *EvalScope) PackageVariables(cfg LoadConfig) ([]*Variable, error) { | ||||
| } | ||||
|  | ||||
| func (scope *EvalScope) findGlobal(name string) (*Variable, error) { | ||||
| 	for n, off := range scope.BinInfo.packageVars { | ||||
| 		if n == name || strings.HasSuffix(n, "/"+name) { | ||||
| 	for _, pkgvar := range scope.BinInfo.packageVars { | ||||
| 		if pkgvar.name == name || strings.HasSuffix(pkgvar.name, "/"+name) { | ||||
| 			reader := scope.DwarfReader() | ||||
| 			reader.Seek(off) | ||||
| 			reader.Seek(pkgvar.offset) | ||||
| 			entry, err := reader.Next() | ||||
| 			if err != nil { | ||||
| 				return nil, err | ||||
|  | ||||
| @ -944,7 +944,7 @@ func (d *Debugger) Disassemble(scope api.EvalScope, startPC, endPC uint64, flavo | ||||
| 	disass := make(api.AsmInstructions, len(insts)) | ||||
|  | ||||
| 	for i := range insts { | ||||
| 		disass[i] = api.ConvertAsmInstruction(insts[i], insts[i].Text(proc.AssemblyFlavour(flavour))) | ||||
| 		disass[i] = api.ConvertAsmInstruction(insts[i], insts[i].Text(proc.AssemblyFlavour(flavour), d.target.BinInfo())) | ||||
| 	} | ||||
|  | ||||
| 	return disass, nil | ||||
|  | ||||
							
								
								
									
										3
									
								
								vendor/golang.org/x/arch/AUTHORS
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								vendor/golang.org/x/arch/AUTHORS
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | ||||
| # This source code refers to The Go Authors for copyright purposes. | ||||
| # The master list of authors is in the main Go distribution, | ||||
| # visible at https://tip.golang.org/AUTHORS. | ||||
							
								
								
									
										31
									
								
								vendor/golang.org/x/arch/CONTRIBUTING.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								vendor/golang.org/x/arch/CONTRIBUTING.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | ||||
| # Contributing to Go | ||||
|  | ||||
| Go is an open source project. | ||||
|  | ||||
| It is the work of hundreds of contributors. We appreciate your help! | ||||
|  | ||||
|  | ||||
| ## Filing issues | ||||
|  | ||||
| When [filing an issue](https://golang.org/issue/new), make sure to answer these five questions: | ||||
|  | ||||
| 1. What version of Go are you using (`go version`)? | ||||
| 2. What operating system and processor architecture are you using? | ||||
| 3. What did you do? | ||||
| 4. What did you expect to see? | ||||
| 5. What did you see instead? | ||||
|  | ||||
| General questions should go to the [golang-nuts mailing list](https://groups.google.com/group/golang-nuts) instead of the issue tracker. | ||||
| The gophers there will answer or ask you to file an issue if you've tripped over a bug. | ||||
|  | ||||
| ## Contributing code | ||||
|  | ||||
| Please read the [Contribution Guidelines](https://golang.org/doc/contribute.html) | ||||
| before sending patches. | ||||
|  | ||||
| **We do not accept GitHub pull requests** | ||||
| (we use [Gerrit](https://code.google.com/p/gerrit/) instead for code review). | ||||
|  | ||||
| Unless otherwise noted, the Go source files are distributed under | ||||
| the BSD-style license found in the LICENSE file. | ||||
|  | ||||
							
								
								
									
										3
									
								
								vendor/golang.org/x/arch/CONTRIBUTORS
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								vendor/golang.org/x/arch/CONTRIBUTORS
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | ||||
| # This source code was written by the Go contributors. | ||||
| # The master list of contributors is in the main Go distribution, | ||||
| # visible at https://tip.golang.org/CONTRIBUTORS. | ||||
							
								
								
									
										6
									
								
								vendor/golang.org/x/arch/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								vendor/golang.org/x/arch/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| # arch | ||||
|  | ||||
| This repository holds machine architecture information used by the Go toolchain. | ||||
| The parts needed in the main Go repository are copied in. | ||||
|  | ||||
| This repository requires Go 1.5+ with the vendor experiment enabled. | ||||
							
								
								
									
										1
									
								
								vendor/golang.org/x/arch/codereview.cfg
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								vendor/golang.org/x/arch/codereview.cfg
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| issuerepo: golang/go | ||||
							
								
								
									
										3
									
								
								vendor/golang.org/x/arch/x86/x86asm/Makefile
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								vendor/golang.org/x/arch/x86/x86asm/Makefile
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | ||||
| tables.go: ../x86map/map.go ../x86.csv  | ||||
| 	go run ../x86map/map.go -fmt=decoder ../x86.csv >_tables.go && gofmt _tables.go >tables.go && rm _tables.go | ||||
|  | ||||
							
								
								
									
										71
									
								
								vendor/golang.org/x/arch/x86/x86asm/decode_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								vendor/golang.org/x/arch/x86/x86asm/decode_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,71 @@ | ||||
| // Copyright 2014 The Go Authors.  All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package x86asm | ||||
|  | ||||
| import ( | ||||
| 	"encoding/hex" | ||||
| 	"io/ioutil" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func TestDecode(t *testing.T) { | ||||
| 	data, err := ioutil.ReadFile("testdata/decode.txt") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	all := string(data) | ||||
| 	for strings.Contains(all, "\t\t") { | ||||
| 		all = strings.Replace(all, "\t\t", "\t", -1) | ||||
| 	} | ||||
| 	for _, line := range strings.Split(all, "\n") { | ||||
| 		line = strings.TrimSpace(line) | ||||
| 		if line == "" || strings.HasPrefix(line, "#") { | ||||
| 			continue | ||||
| 		} | ||||
| 		f := strings.SplitN(line, "\t", 4) | ||||
| 		i := strings.Index(f[0], "|") | ||||
| 		if i < 0 { | ||||
| 			t.Errorf("parsing %q: missing | separator", f[0]) | ||||
| 			continue | ||||
| 		} | ||||
| 		if i%2 != 0 { | ||||
| 			t.Errorf("parsing %q: misaligned | separator", f[0]) | ||||
| 		} | ||||
| 		size := i / 2 | ||||
| 		code, err := hex.DecodeString(f[0][:i] + f[0][i+1:]) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("parsing %q: %v", f[0], err) | ||||
| 			continue | ||||
| 		} | ||||
| 		mode, err := strconv.Atoi(f[1]) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("invalid mode %q in: %s", f[1], line) | ||||
| 			continue | ||||
| 		} | ||||
| 		syntax, asm := f[2], f[3] | ||||
| 		inst, err := Decode(code, mode) | ||||
| 		var out string | ||||
| 		if err != nil { | ||||
| 			out = "error: " + err.Error() | ||||
| 		} else { | ||||
| 			switch syntax { | ||||
| 			case "gnu": | ||||
| 				out = GNUSyntax(inst, 0, nil) | ||||
| 			case "intel": | ||||
| 				out = IntelSyntax(inst, 0, nil) | ||||
| 			case "plan9": // [sic] | ||||
| 				out = GoSyntax(inst, 0, nil) | ||||
| 			default: | ||||
| 				t.Errorf("unknown syntax %q", syntax) | ||||
| 				continue | ||||
| 			} | ||||
| 		} | ||||
| 		if out != asm || inst.Len != size { | ||||
| 			t.Errorf("Decode(%s) [%s] = %s, %d, want %s, %d", f[0], syntax, out, inst.Len, asm, size) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										810
									
								
								vendor/golang.org/x/arch/x86/x86asm/ext_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										810
									
								
								vendor/golang.org/x/arch/x86/x86asm/ext_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,810 @@ | ||||
| // Copyright 2014 The Go Authors.  All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| // Support for testing against external disassembler program. | ||||
|  | ||||
| package x86asm | ||||
|  | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"bytes" | ||||
| 	"encoding/hex" | ||||
| 	"flag" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| 	"math/rand" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"regexp" | ||||
| 	"runtime" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	printTests = flag.Bool("printtests", false, "print test cases that exercise new code paths") | ||||
| 	dumpTest   = flag.Bool("dump", false, "dump all encodings") | ||||
| 	mismatch   = flag.Bool("mismatch", false, "log allowed mismatches") | ||||
| 	longTest   = flag.Bool("long", false, "long test") | ||||
| 	keep       = flag.Bool("keep", false, "keep object files around") | ||||
| 	debug      = false | ||||
| ) | ||||
|  | ||||
| // An ExtInst represents a single decoded instruction parsed | ||||
| // from an external disassembler's output. | ||||
| type ExtInst struct { | ||||
| 	addr uint32 | ||||
| 	enc  [32]byte | ||||
| 	nenc int | ||||
| 	text string | ||||
| } | ||||
|  | ||||
| func (r ExtInst) String() string { | ||||
| 	return fmt.Sprintf("%#x: % x: %s", r.addr, r.enc, r.text) | ||||
| } | ||||
|  | ||||
| // An ExtDis is a connection between an external disassembler and a test. | ||||
| type ExtDis struct { | ||||
| 	Arch     int | ||||
| 	Dec      chan ExtInst | ||||
| 	File     *os.File | ||||
| 	Size     int | ||||
| 	KeepFile bool | ||||
| 	Cmd      *exec.Cmd | ||||
| } | ||||
|  | ||||
| // Run runs the given command - the external disassembler - and returns | ||||
| // a buffered reader of its standard output. | ||||
| func (ext *ExtDis) Run(cmd ...string) (*bufio.Reader, error) { | ||||
| 	if *keep { | ||||
| 		log.Printf("%s\n", strings.Join(cmd, " ")) | ||||
| 	} | ||||
| 	ext.Cmd = exec.Command(cmd[0], cmd[1:]...) | ||||
| 	out, err := ext.Cmd.StdoutPipe() | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("stdoutpipe: %v", err) | ||||
| 	} | ||||
| 	if err := ext.Cmd.Start(); err != nil { | ||||
| 		return nil, fmt.Errorf("exec: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	b := bufio.NewReaderSize(out, 1<<20) | ||||
| 	return b, nil | ||||
| } | ||||
|  | ||||
| // Wait waits for the command started with Run to exit. | ||||
| func (ext *ExtDis) Wait() error { | ||||
| 	return ext.Cmd.Wait() | ||||
| } | ||||
|  | ||||
| // testExtDis tests a set of byte sequences against an external disassembler. | ||||
| // The disassembler is expected to produce the given syntax and be run | ||||
| // in the given architecture mode (16, 32, or 64-bit). | ||||
| // The extdis function must start the external disassembler | ||||
| // and then parse its output, sending the parsed instructions on ext.Dec. | ||||
| // The generate function calls its argument f once for each byte sequence | ||||
| // to be tested. The generate function itself will be called twice, and it must | ||||
| // make the same sequence of calls to f each time. | ||||
| // When a disassembly does not match the internal decoding, | ||||
| // allowedMismatch determines whether this mismatch should be | ||||
| // allowed, or else considered an error. | ||||
| func testExtDis( | ||||
| 	t *testing.T, | ||||
| 	syntax string, | ||||
| 	arch int, | ||||
| 	extdis func(ext *ExtDis) error, | ||||
| 	generate func(f func([]byte)), | ||||
| 	allowedMismatch func(text string, size int, inst *Inst, dec ExtInst) bool, | ||||
| ) { | ||||
| 	start := time.Now() | ||||
| 	ext := &ExtDis{ | ||||
| 		Dec:  make(chan ExtInst), | ||||
| 		Arch: arch, | ||||
| 	} | ||||
| 	errc := make(chan error) | ||||
|  | ||||
| 	// First pass: write instructions to input file for external disassembler. | ||||
| 	file, f, size, err := writeInst(generate) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	ext.Size = size | ||||
| 	ext.File = f | ||||
| 	defer func() { | ||||
| 		f.Close() | ||||
| 		if !*keep { | ||||
| 			os.Remove(file) | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	// Second pass: compare disassembly against our decodings. | ||||
| 	var ( | ||||
| 		totalTests  = 0 | ||||
| 		totalSkips  = 0 | ||||
| 		totalErrors = 0 | ||||
|  | ||||
| 		errors = make([]string, 0, 100) // sampled errors, at most cap | ||||
| 	) | ||||
| 	go func() { | ||||
| 		errc <- extdis(ext) | ||||
| 	}() | ||||
| 	generate(func(enc []byte) { | ||||
| 		dec, ok := <-ext.Dec | ||||
| 		if !ok { | ||||
| 			t.Errorf("decoding stream ended early") | ||||
| 			return | ||||
| 		} | ||||
| 		inst, text := disasm(syntax, arch, pad(enc)) | ||||
| 		totalTests++ | ||||
| 		if *dumpTest { | ||||
| 			fmt.Printf("%x -> %s [%d]\n", enc[:len(enc)], dec.text, dec.nenc) | ||||
| 		} | ||||
| 		if text != dec.text || inst.Len != dec.nenc { | ||||
| 			suffix := "" | ||||
| 			if allowedMismatch(text, size, &inst, dec) { | ||||
| 				totalSkips++ | ||||
| 				if !*mismatch { | ||||
| 					return | ||||
| 				} | ||||
| 				suffix += " (allowed mismatch)" | ||||
| 			} | ||||
| 			totalErrors++ | ||||
| 			if len(errors) >= cap(errors) { | ||||
| 				j := rand.Intn(totalErrors) | ||||
| 				if j >= cap(errors) { | ||||
| 					return | ||||
| 				} | ||||
| 				errors = append(errors[:j], errors[j+1:]...) | ||||
| 			} | ||||
| 			errors = append(errors, fmt.Sprintf("decode(%x) = %q, %d, want %q, %d%s", enc, text, inst.Len, dec.text, dec.nenc, suffix)) | ||||
| 		} | ||||
| 	}) | ||||
|  | ||||
| 	if *mismatch { | ||||
| 		totalErrors -= totalSkips | ||||
| 	} | ||||
|  | ||||
| 	for _, b := range errors { | ||||
| 		t.Log(b) | ||||
| 	} | ||||
|  | ||||
| 	if totalErrors > 0 { | ||||
| 		t.Fail() | ||||
| 	} | ||||
| 	t.Logf("%d test cases, %d expected mismatches, %d failures; %.0f cases/second", totalTests, totalSkips, totalErrors, float64(totalTests)/time.Since(start).Seconds()) | ||||
|  | ||||
| 	if err := <-errc; err != nil { | ||||
| 		t.Fatalf("external disassembler: %v", err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| const start = 0x8000 // start address of text | ||||
|  | ||||
| // writeInst writes the generated byte sequences to a new file | ||||
| // starting at offset start. That file is intended to be the input to | ||||
| // the external disassembler. | ||||
| func writeInst(generate func(func([]byte))) (file string, f *os.File, size int, err error) { | ||||
| 	f, err = ioutil.TempFile("", "x86map") | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	file = f.Name() | ||||
|  | ||||
| 	f.Seek(start, 0) | ||||
| 	w := bufio.NewWriter(f) | ||||
| 	defer w.Flush() | ||||
| 	size = 0 | ||||
| 	generate(func(x []byte) { | ||||
| 		if len(x) > 16 { | ||||
| 			x = x[:16] | ||||
| 		} | ||||
| 		if debug { | ||||
| 			fmt.Printf("%#x: %x%x\n", start+size, x, pops[len(x):]) | ||||
| 		} | ||||
| 		w.Write(x) | ||||
| 		w.Write(pops[len(x):]) | ||||
| 		size += len(pops) | ||||
| 	}) | ||||
| 	return file, f, size, nil | ||||
| } | ||||
|  | ||||
| // 0x5F is a single-byte pop instruction. | ||||
| // We pad the bytes we want decoded with enough 0x5Fs | ||||
| // that no matter what state the instruction stream is in | ||||
| // after reading our bytes, the pops will get us back to | ||||
| // a forced instruction boundary. | ||||
| var pops = []byte{ | ||||
| 	0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, | ||||
| 	0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, | ||||
| 	0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, | ||||
| 	0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, | ||||
| } | ||||
|  | ||||
| // pad pads the code sequence with pops. | ||||
| func pad(enc []byte) []byte { | ||||
| 	return append(enc[:len(enc):len(enc)], pops...) | ||||
| } | ||||
|  | ||||
| // disasm returns the decoded instruction and text | ||||
| // for the given source bytes, using the given syntax and mode. | ||||
| func disasm(syntax string, mode int, src []byte) (inst Inst, text string) { | ||||
| 	// If printTests is set, we record the coverage value | ||||
| 	// before and after, and we write out the inputs for which | ||||
| 	// coverage went up, in the format expected in testdata/decode.text. | ||||
| 	// This produces a fairly small set of test cases that exercise nearly | ||||
| 	// all the code. | ||||
| 	var cover float64 | ||||
| 	if *printTests { | ||||
| 		cover -= coverage() | ||||
| 	} | ||||
|  | ||||
| 	inst, err := decode1(src, mode, syntax == "gnu") | ||||
| 	if err != nil { | ||||
| 		text = "error: " + err.Error() | ||||
| 	} else { | ||||
| 		switch syntax { | ||||
| 		case "gnu": | ||||
| 			text = GNUSyntax(inst, 0, nil) | ||||
| 		case "intel": | ||||
| 			text = IntelSyntax(inst, 0, nil) | ||||
| 		case "plan9": // [sic] | ||||
| 			text = GoSyntax(inst, 0, nil) | ||||
| 		default: | ||||
| 			text = "error: unknown syntax " + syntax | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if *printTests { | ||||
| 		cover += coverage() | ||||
| 		if cover > 0 { | ||||
| 			max := len(src) | ||||
| 			if max > 16 && inst.Len <= 16 { | ||||
| 				max = 16 | ||||
| 			} | ||||
| 			fmt.Printf("%x|%x\t%d\t%s\t%s\n", src[:inst.Len], src[inst.Len:max], mode, syntax, text) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // coverage returns a floating point number denoting the | ||||
| // test coverage until now. The number increases when new code paths are exercised, | ||||
| // both in the Go program and in the decoder byte code. | ||||
| func coverage() float64 { | ||||
| 	/* | ||||
| 		testing.Coverage is not in the main distribution. | ||||
| 		The implementation, which must go in package testing, is: | ||||
|  | ||||
| 		// Coverage reports the current code coverage as a fraction in the range [0, 1]. | ||||
| 		func Coverage() float64 { | ||||
| 			var n, d int64 | ||||
| 			for _, counters := range cover.Counters { | ||||
| 				for _, c := range counters { | ||||
| 					if c > 0 { | ||||
| 						n++ | ||||
| 					} | ||||
| 					d++ | ||||
| 				} | ||||
| 			} | ||||
| 			if d == 0 { | ||||
| 				return 0 | ||||
| 			} | ||||
| 			return float64(n) / float64(d) | ||||
| 		} | ||||
| 	*/ | ||||
|  | ||||
| 	var f float64 | ||||
| 	// f += testing.Coverage() | ||||
| 	f += decodeCoverage() | ||||
| 	return f | ||||
| } | ||||
|  | ||||
| func decodeCoverage() float64 { | ||||
| 	n := 0 | ||||
| 	for _, t := range decoderCover { | ||||
| 		if t { | ||||
| 			n++ | ||||
| 		} | ||||
| 	} | ||||
| 	return float64(1+n) / float64(1+len(decoderCover)) | ||||
| } | ||||
|  | ||||
| // Helpers for writing disassembler output parsers. | ||||
|  | ||||
| // isPrefix reports whether text is the name of an instruction prefix. | ||||
| func isPrefix(text string) bool { | ||||
| 	return prefixByte[text] > 0 | ||||
| } | ||||
|  | ||||
| // prefixByte maps instruction prefix text to actual prefix byte values. | ||||
| var prefixByte = map[string]byte{ | ||||
| 	"es":       0x26, | ||||
| 	"cs":       0x2e, | ||||
| 	"ss":       0x36, | ||||
| 	"ds":       0x3e, | ||||
| 	"fs":       0x64, | ||||
| 	"gs":       0x65, | ||||
| 	"data16":   0x66, | ||||
| 	"addr16":   0x67, | ||||
| 	"lock":     0xf0, | ||||
| 	"repn":     0xf2, | ||||
| 	"repne":    0xf2, | ||||
| 	"rep":      0xf3, | ||||
| 	"repe":     0xf3, | ||||
| 	"xacquire": 0xf2, | ||||
| 	"xrelease": 0xf3, | ||||
| 	"bnd":      0xf2, | ||||
| 	"addr32":   0x66, | ||||
| 	"data32":   0x67, | ||||
| } | ||||
|  | ||||
| // hasPrefix reports whether any of the space-separated words in the text s | ||||
| // begins with any of the given prefixes. | ||||
| func hasPrefix(s string, prefixes ...string) bool { | ||||
| 	for _, prefix := range prefixes { | ||||
| 		for s := s; s != ""; { | ||||
| 			if strings.HasPrefix(s, prefix) { | ||||
| 				return true | ||||
| 			} | ||||
| 			i := strings.Index(s, " ") | ||||
| 			if i < 0 { | ||||
| 				break | ||||
| 			} | ||||
| 			s = s[i+1:] | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // contains reports whether the text s contains any of the given substrings. | ||||
| func contains(s string, substrings ...string) bool { | ||||
| 	for _, sub := range substrings { | ||||
| 		if strings.Contains(s, sub) { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // isHex reports whether b is a hexadecimal character (0-9A-Fa-f). | ||||
| func isHex(b byte) bool { return b == '0' || unhex[b] > 0 } | ||||
|  | ||||
| // parseHex parses the hexadecimal byte dump in hex, | ||||
| // appending the parsed bytes to raw and returning the updated slice. | ||||
| // The returned bool signals whether any invalid hex was found. | ||||
| // Spaces and tabs between bytes are okay but any other non-hex is not. | ||||
| func parseHex(hex []byte, raw []byte) ([]byte, bool) { | ||||
| 	hex = trimSpace(hex) | ||||
| 	for j := 0; j < len(hex); { | ||||
| 		for hex[j] == ' ' || hex[j] == '\t' { | ||||
| 			j++ | ||||
| 		} | ||||
| 		if j >= len(hex) { | ||||
| 			break | ||||
| 		} | ||||
| 		if j+2 > len(hex) || !isHex(hex[j]) || !isHex(hex[j+1]) { | ||||
| 			return nil, false | ||||
| 		} | ||||
| 		raw = append(raw, unhex[hex[j]]<<4|unhex[hex[j+1]]) | ||||
| 		j += 2 | ||||
| 	} | ||||
| 	return raw, true | ||||
| } | ||||
|  | ||||
| var unhex = [256]byte{ | ||||
| 	'0': 0, | ||||
| 	'1': 1, | ||||
| 	'2': 2, | ||||
| 	'3': 3, | ||||
| 	'4': 4, | ||||
| 	'5': 5, | ||||
| 	'6': 6, | ||||
| 	'7': 7, | ||||
| 	'8': 8, | ||||
| 	'9': 9, | ||||
| 	'A': 10, | ||||
| 	'B': 11, | ||||
| 	'C': 12, | ||||
| 	'D': 13, | ||||
| 	'E': 14, | ||||
| 	'F': 15, | ||||
| 	'a': 10, | ||||
| 	'b': 11, | ||||
| 	'c': 12, | ||||
| 	'd': 13, | ||||
| 	'e': 14, | ||||
| 	'f': 15, | ||||
| } | ||||
|  | ||||
| // index is like bytes.Index(s, []byte(t)) but avoids the allocation. | ||||
| func index(s []byte, t string) int { | ||||
| 	i := 0 | ||||
| 	for { | ||||
| 		j := bytes.IndexByte(s[i:], t[0]) | ||||
| 		if j < 0 { | ||||
| 			return -1 | ||||
| 		} | ||||
| 		i = i + j | ||||
| 		if i+len(t) > len(s) { | ||||
| 			return -1 | ||||
| 		} | ||||
| 		for k := 1; k < len(t); k++ { | ||||
| 			if s[i+k] != t[k] { | ||||
| 				goto nomatch | ||||
| 			} | ||||
| 		} | ||||
| 		return i | ||||
| 	nomatch: | ||||
| 		i++ | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // fixSpace rewrites runs of spaces, tabs, and newline characters into single spaces in s. | ||||
| // If s must be rewritten, it is rewritten in place. | ||||
| func fixSpace(s []byte) []byte { | ||||
| 	s = trimSpace(s) | ||||
| 	for i := 0; i < len(s); i++ { | ||||
| 		if s[i] == '\t' || s[i] == '\n' || i > 0 && s[i] == ' ' && s[i-1] == ' ' { | ||||
| 			goto Fix | ||||
| 		} | ||||
| 	} | ||||
| 	return s | ||||
|  | ||||
| Fix: | ||||
| 	b := s | ||||
| 	w := 0 | ||||
| 	for i := 0; i < len(s); i++ { | ||||
| 		c := s[i] | ||||
| 		if c == '\t' || c == '\n' { | ||||
| 			c = ' ' | ||||
| 		} | ||||
| 		if c == ' ' && w > 0 && b[w-1] == ' ' { | ||||
| 			continue | ||||
| 		} | ||||
| 		b[w] = c | ||||
| 		w++ | ||||
| 	} | ||||
| 	if w > 0 && b[w-1] == ' ' { | ||||
| 		w-- | ||||
| 	} | ||||
| 	return b[:w] | ||||
| } | ||||
|  | ||||
| // trimSpace trims leading and trailing space from s, returning a subslice of s. | ||||
| func trimSpace(s []byte) []byte { | ||||
| 	j := len(s) | ||||
| 	for j > 0 && (s[j-1] == ' ' || s[j-1] == '\t' || s[j-1] == '\n') { | ||||
| 		j-- | ||||
| 	} | ||||
| 	i := 0 | ||||
| 	for i < j && (s[i] == ' ' || s[i] == '\t') { | ||||
| 		i++ | ||||
| 	} | ||||
| 	return s[i:j] | ||||
| } | ||||
|  | ||||
| // pcrel and pcrelw match instructions using relative addressing mode. | ||||
| var ( | ||||
| 	pcrel  = regexp.MustCompile(`^((?:.* )?(?:j[a-z]+|call|ljmp|loopn?e?w?|xbegin)q?(?:,p[nt])?) 0x([0-9a-f]+)$`) | ||||
| 	pcrelw = regexp.MustCompile(`^((?:.* )?(?:callw|jmpw|xbeginw|ljmpw)(?:,p[nt])?) 0x([0-9a-f]+)$`) | ||||
| ) | ||||
|  | ||||
| // Generators. | ||||
| // | ||||
| // The test cases are described as functions that invoke a callback repeatedly, | ||||
| // with a new input sequence each time. These helpers make writing those | ||||
| // a little easier. | ||||
|  | ||||
| // hexCases generates the cases written in hexadecimal in the encoded string. | ||||
| // Spaces in 'encoded' separate entire test cases, not individual bytes. | ||||
| func hexCases(t *testing.T, encoded string) func(func([]byte)) { | ||||
| 	return func(try func([]byte)) { | ||||
| 		for _, x := range strings.Fields(encoded) { | ||||
| 			src, err := hex.DecodeString(x) | ||||
| 			if err != nil { | ||||
| 				t.Errorf("parsing %q: %v", x, err) | ||||
| 			} | ||||
| 			try(src) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // testdataCases generates the test cases recorded in testdata/decode.txt. | ||||
| // It only uses the inputs; it ignores the answers recorded in that file. | ||||
| func testdataCases(t *testing.T) func(func([]byte)) { | ||||
| 	var codes [][]byte | ||||
| 	data, err := ioutil.ReadFile("testdata/decode.txt") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	for _, line := range strings.Split(string(data), "\n") { | ||||
| 		line = strings.TrimSpace(line) | ||||
| 		if line == "" || strings.HasPrefix(line, "#") { | ||||
| 			continue | ||||
| 		} | ||||
| 		f := strings.Fields(line)[0] | ||||
| 		i := strings.Index(f, "|") | ||||
| 		if i < 0 { | ||||
| 			t.Errorf("parsing %q: missing | separator", f) | ||||
| 			continue | ||||
| 		} | ||||
| 		if i%2 != 0 { | ||||
| 			t.Errorf("parsing %q: misaligned | separator", f) | ||||
| 		} | ||||
| 		code, err := hex.DecodeString(f[:i] + f[i+1:]) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("parsing %q: %v", f, err) | ||||
| 			continue | ||||
| 		} | ||||
| 		codes = append(codes, code) | ||||
| 	} | ||||
|  | ||||
| 	return func(try func([]byte)) { | ||||
| 		for _, code := range codes { | ||||
| 			try(code) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // manyPrefixes generates all possible 2⁹ combinations of nine chosen prefixes. | ||||
| // The relative ordering of the prefixes within the combinations varies deterministically. | ||||
| func manyPrefixes(try func([]byte)) { | ||||
| 	var prefixBytes = []byte{0x66, 0x67, 0xF0, 0xF2, 0xF3, 0x3E, 0x36, 0x66, 0x67} | ||||
| 	var enc []byte | ||||
| 	for i := 0; i < 1<<uint(len(prefixBytes)); i++ { | ||||
| 		enc = enc[:0] | ||||
| 		for j, p := range prefixBytes { | ||||
| 			if i&(1<<uint(j)) != 0 { | ||||
| 				enc = append(enc, p) | ||||
| 			} | ||||
| 		} | ||||
| 		if len(enc) > 0 { | ||||
| 			k := i % len(enc) | ||||
| 			enc[0], enc[k] = enc[k], enc[0] | ||||
| 		} | ||||
| 		try(enc) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // basicPrefixes geneartes 8 different possible prefix cases: no prefix | ||||
| // and then one each of seven different prefix bytes. | ||||
| func basicPrefixes(try func([]byte)) { | ||||
| 	try(nil) | ||||
| 	for _, b := range []byte{0x66, 0x67, 0xF0, 0xF2, 0xF3, 0x3E, 0x36} { | ||||
| 		try([]byte{b}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func rexPrefixes(try func([]byte)) { | ||||
| 	try(nil) | ||||
| 	for _, b := range []byte{0x40, 0x48, 0x43, 0x4C} { | ||||
| 		try([]byte{b}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // concat takes two generators and returns a generator for the | ||||
| // cross product of the two, concatenating the results from each. | ||||
| func concat(gen1, gen2 func(func([]byte))) func(func([]byte)) { | ||||
| 	return func(try func([]byte)) { | ||||
| 		gen1(func(enc1 []byte) { | ||||
| 			gen2(func(enc2 []byte) { | ||||
| 				try(append(enc1[:len(enc1):len(enc1)], enc2...)) | ||||
| 			}) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // concat3 takes three generators and returns a generator for the | ||||
| // cross product of the three, concatenating the results from each. | ||||
| func concat3(gen1, gen2, gen3 func(func([]byte))) func(func([]byte)) { | ||||
| 	return func(try func([]byte)) { | ||||
| 		gen1(func(enc1 []byte) { | ||||
| 			gen2(func(enc2 []byte) { | ||||
| 				gen3(func(enc3 []byte) { | ||||
| 					try(append(append(enc1[:len(enc1):len(enc1)], enc2...), enc3...)) | ||||
| 				}) | ||||
| 			}) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // concat4 takes four generators and returns a generator for the | ||||
| // cross product of the four, concatenating the results from each. | ||||
| func concat4(gen1, gen2, gen3, gen4 func(func([]byte))) func(func([]byte)) { | ||||
| 	return func(try func([]byte)) { | ||||
| 		gen1(func(enc1 []byte) { | ||||
| 			gen2(func(enc2 []byte) { | ||||
| 				gen3(func(enc3 []byte) { | ||||
| 					gen4(func(enc4 []byte) { | ||||
| 						try(append(append(append(enc1[:len(enc1):len(enc1)], enc2...), enc3...), enc4...)) | ||||
| 					}) | ||||
| 				}) | ||||
| 			}) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // filter generates the sequences from gen that satisfy ok. | ||||
| func filter(gen func(func([]byte)), ok func([]byte) bool) func(func([]byte)) { | ||||
| 	return func(try func([]byte)) { | ||||
| 		gen(func(enc []byte) { | ||||
| 			if ok(enc) { | ||||
| 				try(enc) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // enum8bit generates all possible 1-byte sequences, followed by distinctive padding. | ||||
| func enum8bit(try func([]byte)) { | ||||
| 	for i := 0; i < 1<<8; i++ { | ||||
| 		try([]byte{byte(i), 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // enum8bit generates all possible 2-byte sequences, followed by distinctive padding. | ||||
| func enum16bit(try func([]byte)) { | ||||
| 	for i := 0; i < 1<<16; i++ { | ||||
| 		try([]byte{byte(i), byte(i >> 8), 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // enum24bit generates all possible 3-byte sequences, followed by distinctive padding. | ||||
| func enum24bit(try func([]byte)) { | ||||
| 	for i := 0; i < 1<<24; i++ { | ||||
| 		try([]byte{byte(i), byte(i >> 8), byte(i >> 16), 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // enumModRM generates all possible modrm bytes and, for modrm values that indicate | ||||
| // a following sib byte, all possible modrm, sib combinations. | ||||
| func enumModRM(try func([]byte)) { | ||||
| 	for i := 0; i < 256; i++ { | ||||
| 		if (i>>3)&07 == 04 && i>>6 != 3 { // has sib | ||||
| 			for j := 0; j < 256; j++ { | ||||
| 				try([]byte{0, byte(i), byte(j), 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}) // byte encodings | ||||
| 				try([]byte{1, byte(i), byte(j), 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}) // word encodings | ||||
| 			} | ||||
| 		} else { | ||||
| 			try([]byte{0, byte(i), 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}) // byte encodings | ||||
| 			try([]byte{1, byte(i), 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}) // word encodings | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // fixed generates the single case b. | ||||
| // It's mainly useful to prepare an argument for concat or concat3. | ||||
| func fixed(b ...byte) func(func([]byte)) { | ||||
| 	return func(try func([]byte)) { | ||||
| 		try(b) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // testBasic runs the given test function with cases all using opcode as the initial opcode bytes. | ||||
| // It runs three phases: | ||||
| // | ||||
| // First, zero-or-one prefixes followed by opcode followed by all possible 1-byte values. | ||||
| // If in -short mode, that's all. | ||||
| // | ||||
| // Second, zero-or-one prefixes followed by opcode followed by all possible 2-byte values. | ||||
| // If not in -long mode, that's all. This phase and the next run in parallel with other tests | ||||
| // (using t.Parallel). | ||||
| // | ||||
| // Finally, opcode followed by all possible 3-byte values. The test can take a very long time | ||||
| // and prints progress messages to package log. | ||||
| func testBasic(t *testing.T, testfn func(*testing.T, func(func([]byte))), opcode ...byte) { | ||||
| 	testfn(t, concat3(basicPrefixes, fixed(opcode...), enum8bit)) | ||||
| 	if testing.Short() { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	t.Parallel() | ||||
| 	testfn(t, concat3(basicPrefixes, fixed(opcode...), enum16bit)) | ||||
| 	if !*longTest { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	name := caller(2) | ||||
| 	op1 := make([]byte, len(opcode)+1) | ||||
| 	copy(op1, opcode) | ||||
| 	for i := 0; i < 256; i++ { | ||||
| 		log.Printf("%s 24-bit: %d/256\n", name, i) | ||||
| 		op1[len(opcode)] = byte(i) | ||||
| 		testfn(t, concat(fixed(op1...), enum16bit)) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func testBasicREX(t *testing.T, testfn func(*testing.T, func(func([]byte))), opcode ...byte) { | ||||
| 	testfn(t, filter(concat4(basicPrefixes, rexPrefixes, fixed(opcode...), enum8bit), isValidREX)) | ||||
| 	if testing.Short() { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	t.Parallel() | ||||
| 	testfn(t, filter(concat4(basicPrefixes, rexPrefixes, fixed(opcode...), enum16bit), isValidREX)) | ||||
| 	if !*longTest { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	name := caller(2) | ||||
| 	op1 := make([]byte, len(opcode)+1) | ||||
| 	copy(op1, opcode) | ||||
| 	for i := 0; i < 256; i++ { | ||||
| 		log.Printf("%s 24-bit: %d/256\n", name, i) | ||||
| 		op1[len(opcode)] = byte(i) | ||||
| 		testfn(t, filter(concat3(rexPrefixes, fixed(op1...), enum16bit), isValidREX)) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // testPrefix runs the given test function for all many prefix possibilities | ||||
| // followed by all possible 1-byte sequences. | ||||
| // | ||||
| // If in -long mode, it then runs a test of all the prefix possibilities followed | ||||
| // by all possible 2-byte sequences. | ||||
| func testPrefix(t *testing.T, testfn func(*testing.T, func(func([]byte)))) { | ||||
| 	t.Parallel() | ||||
| 	testfn(t, concat(manyPrefixes, enum8bit)) | ||||
| 	if testing.Short() || !*longTest { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	name := caller(2) | ||||
| 	for i := 0; i < 256; i++ { | ||||
| 		log.Printf("%s 16-bit: %d/256\n", name, i) | ||||
| 		testfn(t, concat3(manyPrefixes, fixed(byte(i)), enum8bit)) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func testPrefixREX(t *testing.T, testfn func(*testing.T, func(func([]byte)))) { | ||||
| 	t.Parallel() | ||||
| 	testfn(t, filter(concat3(manyPrefixes, rexPrefixes, enum8bit), isValidREX)) | ||||
| 	if testing.Short() || !*longTest { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	name := caller(2) | ||||
| 	for i := 0; i < 256; i++ { | ||||
| 		log.Printf("%s 16-bit: %d/256\n", name, i) | ||||
| 		testfn(t, filter(concat4(manyPrefixes, rexPrefixes, fixed(byte(i)), enum8bit), isValidREX)) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func caller(skip int) string { | ||||
| 	pc, _, _, _ := runtime.Caller(skip) | ||||
| 	f := runtime.FuncForPC(pc) | ||||
| 	name := "?" | ||||
| 	if f != nil { | ||||
| 		name = f.Name() | ||||
| 		if i := strings.LastIndex(name, "."); i >= 0 { | ||||
| 			name = name[i+1:] | ||||
| 		} | ||||
| 	} | ||||
| 	return name | ||||
| } | ||||
|  | ||||
| func isValidREX(x []byte) bool { | ||||
| 	i := 0 | ||||
| 	for i < len(x) && isPrefixByte(x[i]) { | ||||
| 		i++ | ||||
| 	} | ||||
| 	if i < len(x) && Prefix(x[i]).IsREX() { | ||||
| 		i++ | ||||
| 		if i < len(x) { | ||||
| 			return !isPrefixByte(x[i]) && !Prefix(x[i]).IsREX() | ||||
| 		} | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| func isPrefixByte(b byte) bool { | ||||
| 	switch b { | ||||
| 	case 0x26, 0x2E, 0x36, 0x3E, 0x64, 0x65, 0x66, 0x67, 0xF0, 0xF2, 0xF3: | ||||
| 		return true | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
							
								
								
									
										68
									
								
								vendor/golang.org/x/arch/x86/x86asm/format_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								vendor/golang.org/x/arch/x86/x86asm/format_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,68 @@ | ||||
| // Copyright 2017 The Go Authors.  All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package x86asm | ||||
|  | ||||
| import ( | ||||
| 	"encoding/hex" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func testFormattingSymname(addr uint64) (string, uint64) { | ||||
| 	switch addr { | ||||
| 	case 0x424080: | ||||
| 		return "runtime.printint", 0x424080 | ||||
| 	case 0x4c8068: | ||||
| 		return "main.A", 0x4c8068 | ||||
| 	} | ||||
| 	return "", 0 | ||||
| } | ||||
|  | ||||
| func TestFormatting(t *testing.T) { | ||||
| 	testCases := []struct { | ||||
| 		PC    uint64 | ||||
| 		bytes string | ||||
|  | ||||
| 		goSyntax, intelSyntax, gnuSyntax string | ||||
| 	}{ | ||||
| 		{0x4816b2, "0f8677010000", | ||||
| 			"JBE 0x48182f", | ||||
| 			"jbe 0x48182f", | ||||
| 			"jbe 0x48182f"}, | ||||
| 		{0x45065b, "488b442408", | ||||
| 			"MOVQ 0x8(SP), AX", | ||||
| 			"mov rax, qword ptr [rsp+0x8]", | ||||
| 			"mov 0x8(%rsp),%rax"}, | ||||
| 		{0x450678, "488b05e9790700", | ||||
| 			"MOVQ main.A(SB), AX", | ||||
| 			"mov rax, qword ptr [main.A]", | ||||
| 			"mov main.A,%rax"}, | ||||
| 		{0x450664, "e8173afdff", | ||||
| 			"CALL runtime.printint(SB)", | ||||
| 			"call runtime.printint", | ||||
| 			"callq runtime.printint"}, | ||||
| 		{0x45069b, "488d0575d90100", | ||||
| 			"LEAQ 0x1d975(IP), AX", | ||||
| 			"lea rax, ptr [rip+0x1d975]", | ||||
| 			"lea 0x1d975(%rip),%rax"}, | ||||
| 	} | ||||
|  | ||||
| 	for _, testCase := range testCases { | ||||
| 		t.Logf("%#x %s %s", testCase.PC, testCase.bytes, testCase.goSyntax) | ||||
| 		bs, _ := hex.DecodeString(testCase.bytes) | ||||
| 		inst, err := Decode(bs, 64) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("decode error %v", err) | ||||
| 		} | ||||
| 		if out := GoSyntax(inst, testCase.PC, testFormattingSymname); out != testCase.goSyntax { | ||||
| 			t.Errorf("GoSyntax: %q", out) | ||||
| 		} | ||||
| 		if out := IntelSyntax(inst, testCase.PC, testFormattingSymname); out != testCase.intelSyntax { | ||||
| 			t.Errorf("IntelSyntax: %q expected: %q", out, testCase.intelSyntax) | ||||
| 		} | ||||
| 		if out := GNUSyntax(inst, testCase.PC, testFormattingSymname); out != testCase.gnuSyntax { | ||||
| 			t.Errorf("GNUSyntax: %q expected: %q", out, testCase.gnuSyntax) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										36
									
								
								vendor/golang.org/x/arch/x86/x86asm/gnu.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										36
									
								
								vendor/golang.org/x/arch/x86/x86asm/gnu.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -11,12 +11,16 @@ import ( | ||||
|  | ||||
| // GNUSyntax returns the GNU assembler syntax for the instruction, as defined by GNU binutils. | ||||
| // This general form is often called ``AT&T syntax'' as a reference to AT&T System V Unix. | ||||
| func GNUSyntax(inst Inst) string { | ||||
| func GNUSyntax(inst Inst, pc uint64, symname SymLookup) string { | ||||
| 	// Rewrite instruction to mimic GNU peculiarities. | ||||
| 	// Note that inst has been passed by value and contains | ||||
| 	// no pointers, so any changes we make here are local | ||||
| 	// and will not propagate back out to the caller. | ||||
|  | ||||
| 	if symname == nil { | ||||
| 		symname = func(uint64) (string, uint64) { return "", 0 } | ||||
| 	} | ||||
|  | ||||
| 	// Adjust opcode [sic]. | ||||
| 	switch inst.Op { | ||||
| 	case FDIV, FDIVR, FSUB, FSUBR, FDIVP, FDIVRP, FSUBP, FSUBRP: | ||||
| @ -403,7 +407,7 @@ SuffixLoop: | ||||
| 		if a == Imm(1) && (inst.Opcode>>24)&^1 == 0xD0 { | ||||
| 			continue | ||||
| 		} | ||||
| 		args = append(args, gnuArg(&inst, a, &usedPrefixes)) | ||||
| 		args = append(args, gnuArg(&inst, pc, symname, a, &usedPrefixes)) | ||||
| 	} | ||||
|  | ||||
| 	// The default is to print the arguments in reverse Intel order. | ||||
| @ -513,7 +517,7 @@ SuffixLoop: | ||||
| // gnuArg returns the GNU syntax for the argument x from the instruction inst. | ||||
| // If *usedPrefixes is false and x is a Mem, then the formatting | ||||
| // includes any segment prefixes and sets *usedPrefixes to true. | ||||
| func gnuArg(inst *Inst, x Arg, usedPrefixes *bool) string { | ||||
| func gnuArg(inst *Inst, pc uint64, symname SymLookup, x Arg, usedPrefixes *bool) string { | ||||
| 	if x == nil { | ||||
| 		return "<nil>" | ||||
| 	} | ||||
| @ -535,6 +539,13 @@ func gnuArg(inst *Inst, x Arg, usedPrefixes *bool) string { | ||||
| 		} | ||||
| 		return gccRegName[x] | ||||
| 	case Mem: | ||||
| 		if s, disp := memArgToSymbol(x, pc, inst.Len, symname); s != "" { | ||||
| 			suffix := "" | ||||
| 			if disp != 0 { | ||||
| 				suffix = fmt.Sprintf("%+d", disp) | ||||
| 			} | ||||
| 			return fmt.Sprintf("%s%s", s, suffix) | ||||
| 		} | ||||
| 		seg := "" | ||||
| 		var haveCS, haveDS, haveES, haveFS, haveGS, haveSS bool | ||||
| 		switch x.Segment { | ||||
| @ -644,8 +655,25 @@ func gnuArg(inst *Inst, x Arg, usedPrefixes *bool) string { | ||||
| 		} | ||||
| 		return fmt.Sprintf("%s%s(%s,%s,%d)", seg, disp, base, index, x.Scale) | ||||
| 	case Rel: | ||||
| 		return fmt.Sprintf(".%+#x", int32(x)) | ||||
| 		if pc == 0 { | ||||
| 			return fmt.Sprintf(".%+#x", int64(x)) | ||||
| 		} else { | ||||
| 			addr := pc + uint64(inst.Len) + uint64(x) | ||||
| 			if s, base := symname(addr); s != "" && addr == base { | ||||
| 				return fmt.Sprintf("%s", s) | ||||
| 			} else { | ||||
| 				addr := pc + uint64(inst.Len) + uint64(x) | ||||
| 				return fmt.Sprintf("%#x", addr) | ||||
| 			} | ||||
| 		} | ||||
| 	case Imm: | ||||
| 		if s, base := symname(uint64(x)); s != "" { | ||||
| 			suffix := "" | ||||
| 			if uint64(x) != base { | ||||
| 				suffix = fmt.Sprintf("%+d", uint64(x)-base) | ||||
| 			} | ||||
| 			return fmt.Sprintf("$%s%s", s, suffix) | ||||
| 		} | ||||
| 		if inst.Mode == 32 { | ||||
| 			return fmt.Sprintf("$%#x", uint32(x)) | ||||
| 		} | ||||
|  | ||||
							
								
								
									
										20
									
								
								vendor/golang.org/x/arch/x86/x86asm/inst_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								vendor/golang.org/x/arch/x86/x86asm/inst_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | ||||
| // Copyright 2014 The Go Authors.  All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package x86asm | ||||
|  | ||||
| import ( | ||||
| 	"strings" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func TestRegString(t *testing.T) { | ||||
| 	for r := Reg(1); r <= regMax; r++ { | ||||
| 		if regNames[r] == "" { | ||||
| 			t.Errorf("regNames[%d] is missing", int(r)) | ||||
| 		} else if s := r.String(); strings.Contains(s, "Reg(") { | ||||
| 			t.Errorf("Reg(%d).String() = %s, want proper name", int(r), s) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										40
									
								
								vendor/golang.org/x/arch/x86/x86asm/intel.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										40
									
								
								vendor/golang.org/x/arch/x86/x86asm/intel.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -10,7 +10,11 @@ import ( | ||||
| ) | ||||
|  | ||||
| // IntelSyntax returns the Intel assembler syntax for the instruction, as defined by Intel's XED tool. | ||||
| func IntelSyntax(inst Inst) string { | ||||
| func IntelSyntax(inst Inst, pc uint64, symname SymLookup) string { | ||||
| 	if symname == nil { | ||||
| 		symname = func(uint64) (string, uint64) { return "", 0 } | ||||
| 	} | ||||
|  | ||||
| 	var iargs []Arg | ||||
| 	for _, a := range inst.Args { | ||||
| 		if a == nil { | ||||
| @ -256,7 +260,7 @@ func IntelSyntax(inst Inst) string { | ||||
| 		if a == nil { | ||||
| 			break | ||||
| 		} | ||||
| 		args = append(args, intelArg(&inst, a)) | ||||
| 		args = append(args, intelArg(&inst, pc, symname, a)) | ||||
| 	} | ||||
|  | ||||
| 	var op string | ||||
| @ -334,9 +338,16 @@ func IntelSyntax(inst Inst) string { | ||||
| 	return prefix + op | ||||
| } | ||||
|  | ||||
| func intelArg(inst *Inst, arg Arg) string { | ||||
| func intelArg(inst *Inst, pc uint64, symname SymLookup, arg Arg) string { | ||||
| 	switch a := arg.(type) { | ||||
| 	case Imm: | ||||
| 		if s, base := symname(uint64(a)); s != "" { | ||||
| 			suffix := "" | ||||
| 			if uint64(a) != base { | ||||
| 				suffix = fmt.Sprintf("%+d", uint64(a)-base) | ||||
| 			} | ||||
| 			return fmt.Sprintf("$%s%s", s, suffix) | ||||
| 		} | ||||
| 		if inst.Mode == 32 { | ||||
| 			return fmt.Sprintf("%#x", uint32(a)) | ||||
| 		} | ||||
| @ -417,18 +428,25 @@ func intelArg(inst *Inst, arg Arg) string { | ||||
| 		} | ||||
|  | ||||
| 		prefix += "ptr " | ||||
| 		if s, disp := memArgToSymbol(a, pc, inst.Len, symname); s != "" { | ||||
| 			suffix := "" | ||||
| 			if disp != 0 { | ||||
| 				suffix = fmt.Sprintf("%+d", disp) | ||||
| 			} | ||||
| 			return prefix + fmt.Sprintf("[%s%s]", s, suffix) | ||||
| 		} | ||||
| 		if a.Segment != 0 { | ||||
| 			prefix += strings.ToLower(a.Segment.String()) + ":" | ||||
| 		} | ||||
| 		prefix += "[" | ||||
| 		if a.Base != 0 { | ||||
| 			prefix += intelArg(inst, a.Base) | ||||
| 			prefix += intelArg(inst, pc, symname, a.Base) | ||||
| 		} | ||||
| 		if a.Scale != 0 && a.Index != 0 { | ||||
| 			if a.Base != 0 { | ||||
| 				prefix += "+" | ||||
| 			} | ||||
| 			prefix += fmt.Sprintf("%s*%d", intelArg(inst, a.Index), a.Scale) | ||||
| 			prefix += fmt.Sprintf("%s*%d", intelArg(inst, pc, symname, a.Index), a.Scale) | ||||
| 		} | ||||
| 		if a.Disp != 0 { | ||||
| 			if prefix[len(prefix)-1] == '[' && (a.Disp >= 0 || int64(int32(a.Disp)) != a.Disp) { | ||||
| @ -440,7 +458,17 @@ func intelArg(inst *Inst, arg Arg) string { | ||||
| 		prefix += "]" | ||||
| 		return prefix | ||||
| 	case Rel: | ||||
| 		return fmt.Sprintf(".%+#x", int64(a)) | ||||
| 		if pc == 0 { | ||||
| 			return fmt.Sprintf(".%+#x", int64(a)) | ||||
| 		} else { | ||||
| 			addr := pc + uint64(inst.Len) + uint64(a) | ||||
| 			if s, base := symname(addr); s != "" && addr == base { | ||||
| 				return fmt.Sprintf("%s", s) | ||||
| 			} else { | ||||
| 				addr := pc + uint64(inst.Len) + uint64(a) | ||||
| 				return fmt.Sprintf("%#x", addr) | ||||
| 			} | ||||
| 		} | ||||
| 	case Reg: | ||||
| 		if int(a) < len(intelReg) && intelReg[a] != "" { | ||||
| 			switch inst.Op { | ||||
|  | ||||
							
								
								
									
										385
									
								
								vendor/golang.org/x/arch/x86/x86asm/objdump_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										385
									
								
								vendor/golang.org/x/arch/x86/x86asm/objdump_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,385 @@ | ||||
| // Copyright 2014 The Go Authors.  All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package x86asm | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func TestObjdump32Manual(t *testing.T)   { testObjdump32(t, hexCases(t, objdumpManualTests)) } | ||||
| func TestObjdump32Testdata(t *testing.T) { testObjdump32(t, concat(basicPrefixes, testdataCases(t))) } | ||||
| func TestObjdump32ModRM(t *testing.T)    { testObjdump32(t, concat(basicPrefixes, enumModRM)) } | ||||
| func TestObjdump32OneByte(t *testing.T)  { testBasic(t, testObjdump32) } | ||||
| func TestObjdump320F(t *testing.T)       { testBasic(t, testObjdump32, 0x0F) } | ||||
| func TestObjdump320F38(t *testing.T)     { testBasic(t, testObjdump32, 0x0F, 0x38) } | ||||
| func TestObjdump320F3A(t *testing.T)     { testBasic(t, testObjdump32, 0x0F, 0x3A) } | ||||
| func TestObjdump32Prefix(t *testing.T)   { testPrefix(t, testObjdump32) } | ||||
|  | ||||
| func TestObjdump64Manual(t *testing.T)   { testObjdump64(t, hexCases(t, objdumpManualTests)) } | ||||
| func TestObjdump64Testdata(t *testing.T) { testObjdump64(t, concat(basicPrefixes, testdataCases(t))) } | ||||
| func TestObjdump64ModRM(t *testing.T)    { testObjdump64(t, concat(basicPrefixes, enumModRM)) } | ||||
| func TestObjdump64OneByte(t *testing.T)  { testBasic(t, testObjdump64) } | ||||
| func TestObjdump640F(t *testing.T)       { testBasic(t, testObjdump64, 0x0F) } | ||||
| func TestObjdump640F38(t *testing.T)     { testBasic(t, testObjdump64, 0x0F, 0x38) } | ||||
| func TestObjdump640F3A(t *testing.T)     { testBasic(t, testObjdump64, 0x0F, 0x3A) } | ||||
| func TestObjdump64Prefix(t *testing.T)   { testPrefix(t, testObjdump64) } | ||||
|  | ||||
| func TestObjdump64REXTestdata(t *testing.T) { | ||||
| 	testObjdump64(t, filter(concat3(basicPrefixes, rexPrefixes, testdataCases(t)), isValidREX)) | ||||
| } | ||||
| func TestObjdump64REXModRM(t *testing.T) { | ||||
| 	testObjdump64(t, concat3(basicPrefixes, rexPrefixes, enumModRM)) | ||||
| } | ||||
| func TestObjdump64REXOneByte(t *testing.T) { testBasicREX(t, testObjdump64) } | ||||
| func TestObjdump64REX0F(t *testing.T)      { testBasicREX(t, testObjdump64, 0x0F) } | ||||
| func TestObjdump64REX0F38(t *testing.T)    { testBasicREX(t, testObjdump64, 0x0F, 0x38) } | ||||
| func TestObjdump64REX0F3A(t *testing.T)    { testBasicREX(t, testObjdump64, 0x0F, 0x3A) } | ||||
| func TestObjdump64REXPrefix(t *testing.T)  { testPrefixREX(t, testObjdump64) } | ||||
|  | ||||
| // objdumpManualTests holds test cases that will be run by TestObjdumpManual. | ||||
| // If you are debugging a few cases that turned up in a longer run, it can be useful | ||||
| // to list them here and then use -run=ObjdumpManual, particularly with tracing enabled. | ||||
| var objdumpManualTests = ` | ||||
| 4883FE017413 | ||||
| 488DFC2500000000 | ||||
| 488D3D00000000 | ||||
| ` | ||||
|  | ||||
| // allowedMismatchObjdump reports whether the mismatch between text and dec | ||||
| // should be allowed by the test. | ||||
| func allowedMismatchObjdump(text string, size int, inst *Inst, dec ExtInst) bool { | ||||
| 	if size == 15 && dec.nenc == 15 && contains(text, "truncated") && contains(dec.text, "(bad)") { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	if i := strings.LastIndex(dec.text, " "); isPrefix(dec.text[i+1:]) && size == 1 && isPrefix(text) { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	if size == dec.nenc && contains(dec.text, "movupd") && contains(dec.text, "data32") { | ||||
| 		s := strings.Replace(dec.text, "data32 ", "", -1) | ||||
| 		if text == s { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Simplify our invalid instruction text. | ||||
| 	if text == "error: unrecognized instruction" { | ||||
| 		text = "BAD" | ||||
| 	} | ||||
|  | ||||
| 	// Invalid instructions for which libopcodes prints %? register. | ||||
| 	// FF E8 11 22 33 44: | ||||
| 	// Invalid instructions for which libopcodes prints "internal disassembler error". | ||||
| 	// Invalid instructions for which libopcodes prints 8087 only (e.g., DB E0) | ||||
| 	// or prints 287 only (e.g., DB E4). | ||||
| 	if contains(dec.text, "%?", "<internal disassembler error>", "(8087 only)", "(287 only)") { | ||||
| 		dec.text = "(bad)" | ||||
| 	} | ||||
|  | ||||
| 	// 0F 19 11, 0F 1C 11, 0F 1D 11, 0F 1E 11, 0F 1F 11: libopcodes says nop, | ||||
| 	// but the Intel manuals say that the only NOP there is 0F 1F /0. | ||||
| 	// Perhaps libopcodes is reporting an older encoding. | ||||
| 	i := bytes.IndexByte(dec.enc[:], 0x0F) | ||||
| 	if contains(dec.text, "nop") && i >= 0 && i+2 < len(dec.enc) && dec.enc[i+1]&^7 == 0x18 && (dec.enc[i+1] != 0x1F || (dec.enc[i+2]>>3)&7 != 0) { | ||||
| 		dec.text = "(bad)" | ||||
| 	} | ||||
|  | ||||
| 	// Any invalid instruction. | ||||
| 	if text == "BAD" && contains(dec.text, "(bad)") { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// Instructions libopcodes knows but we do not (e.g., 0F 19 11). | ||||
| 	if (text == "BAD" || size == 1 && isPrefix(text)) && hasPrefix(dec.text, unsupported...) { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// Instructions we know but libopcodes does not (e.g., 0F D0 11). | ||||
| 	if (contains(dec.text, "(bad)") || dec.nenc == 1 && isPrefix(dec.text)) && hasPrefix(text, libopcodesUnsupported...) { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// Libopcodes rejects F2 90 as NOP. Not sure why. | ||||
| 	if (contains(dec.text, "(bad)") || dec.nenc == 1 && isPrefix(dec.text)) && inst.Opcode>>24 == 0x90 && countPrefix(inst, 0xF2) > 0 { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// 0F 20 11, 0F 21 11, 0F 22 11, 0F 23 11, 0F 24 11: | ||||
| 	// Moves into and out of some control registers seem to be unsupported by libopcodes. | ||||
| 	// TODO(rsc): Are they invalid somehow? | ||||
| 	if (contains(dec.text, "(bad)") || dec.nenc == 1 && isPrefix(dec.text)) && contains(text, "%cr", "%db", "%tr") { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	if contains(dec.text, "fwait") && dec.nenc == 1 && dec.enc[0] != 0x9B { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// 9B D9 11: libopcodes reports FSTSW instead of FWAIT + FNSTSW. | ||||
| 	// This is correct in that FSTSW is a pseudo-op for the pair, but it really | ||||
| 	// is a pair of instructions: execution can stop between them. | ||||
| 	// Our decoder chooses to separate them. | ||||
| 	if (text == "fwait" || strings.HasSuffix(text, " fwait")) && dec.nenc >= len(strings.Fields(text)) && dec.enc[len(strings.Fields(text))-1] == 0x9B { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// 0F 18 77 11: | ||||
| 	// Invalid instructions for which libopcodes prints "nop/reserved". | ||||
| 	// Perhaps libopcodes is reporting an older encoding. | ||||
| 	if text == "BAD" && contains(dec.text, "nop/reserved") { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// 0F C7 B0 11 22 33 44: libopcodes says vmptrld 0x44332211(%eax); we say rdrand %eax. | ||||
| 	// TODO(rsc): Fix, since we are probably wrong, but we don't have vmptrld in the manual. | ||||
| 	if contains(text, "rdrand") && contains(dec.text, "vmptrld", "vmxon", "vmclear") { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// DD C8: libopcodes says FNOP but the Intel manual is clear FNOP is only D9 D0. | ||||
| 	// Perhaps libopcodes is reporting an older encoding. | ||||
| 	if text == "BAD" && contains(dec.text, "fnop") && (dec.enc[0] != 0xD9 || dec.enc[1] != 0xD0) { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// 66 90: libopcodes says xchg %ax,%ax; we say 'data16 nop'. | ||||
| 	// The 16-bit swap will preserve the high bits of the register, | ||||
| 	// so they are the same. | ||||
| 	if contains(text, "nop") && contains(dec.text, "xchg %ax,%ax") { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// If there are multiple prefixes, allow libopcodes to use an alternate name. | ||||
| 	if size == 1 && dec.nenc == 1 && prefixByte[text] > 0 && prefixByte[text] == prefixByte[dec.text] { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// 26 9B: libopcodes reports "fwait"/1, ignoring segment prefix. | ||||
| 	// https://sourceware.org/bugzilla/show_bug.cgi?id=16891 | ||||
| 	// F0 82: Decode="lock"/1 but libopcodes="lock (bad)"/2. | ||||
| 	if size == 1 && dec.nenc >= 1 && prefixByte[text] == dec.enc[0] && contains(dec.text, "(bad)", "fwait", "fnop") { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// libopcodes interprets 660f801122 as taking a rel16 but | ||||
| 	// truncating the address at 16 bits. Not sure what is correct. | ||||
| 	if contains(text, ".+0x2211", ".+0x11") && contains(dec.text, " .-") { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// 66 F3 0F D6 C5, 66 F2 0F D6 C0: libopcodes reports use of XMM register instead of MMX register, | ||||
| 	// but only when the instruction has a 66 prefix. Maybe they know something we don't. | ||||
| 	if countPrefix(inst, 0x66) > 0 && contains(dec.text, "movdq2q", "movq2dq") && !contains(dec.text, "%mm") { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// 0F 01 F8, 0F 05, 0F 07: these are 64-bit instructions but libopcodes accepts them. | ||||
| 	if (text == "BAD" || size == 1 && isPrefix(text)) && contains(dec.text, "swapgs", "syscall", "sysret", "rdfsbase", "rdgsbase", "wrfsbase", "wrgsbase") { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // Instructions known to libopcodes (or xed) but not to us. | ||||
| // Most of these come from supplementary manuals of one form or another. | ||||
| var unsupported = strings.Fields(` | ||||
| 	bndc | ||||
| 	bndl | ||||
| 	bndm | ||||
| 	bnds | ||||
| 	clac | ||||
| 	clgi | ||||
| 	femms | ||||
| 	fldln | ||||
| 	fldz | ||||
| 	getsec | ||||
| 	invlpga | ||||
| 	kmov | ||||
| 	montmul | ||||
| 	pavg | ||||
| 	pf2i | ||||
| 	pfacc | ||||
| 	pfadd | ||||
| 	pfcmp | ||||
| 	pfmax | ||||
| 	pfmin | ||||
| 	pfmul | ||||
| 	pfna | ||||
| 	pfpnac | ||||
| 	pfrc | ||||
| 	pfrs | ||||
| 	pfsub | ||||
| 	phadd | ||||
| 	phsub | ||||
| 	pi2f | ||||
| 	pmulhr | ||||
| 	prefetch | ||||
| 	pswap | ||||
| 	ptest | ||||
| 	rdseed | ||||
| 	sha1 | ||||
| 	sha256 | ||||
| 	skinit | ||||
| 	stac | ||||
| 	stgi | ||||
| 	vadd | ||||
| 	vand | ||||
| 	vcmp | ||||
| 	vcomis | ||||
| 	vcvt | ||||
| 	vcvt | ||||
| 	vdiv | ||||
| 	vhadd | ||||
| 	vhsub | ||||
| 	vld | ||||
| 	vmax | ||||
| 	vmcall | ||||
| 	vmfunc | ||||
| 	vmin | ||||
| 	vmlaunch | ||||
| 	vmload | ||||
| 	vmmcall | ||||
| 	vmov | ||||
| 	vmov | ||||
| 	vmov | ||||
| 	vmptrld | ||||
| 	vmptrst | ||||
| 	vmread | ||||
| 	vmresume | ||||
| 	vmrun | ||||
| 	vmsave | ||||
| 	vmul | ||||
| 	vmwrite | ||||
| 	vmxoff | ||||
| 	vor | ||||
| 	vpack | ||||
| 	vpadd | ||||
| 	vpand | ||||
| 	vpavg | ||||
| 	vpcmp | ||||
| 	vpcmp | ||||
| 	vpins | ||||
| 	vpmadd | ||||
| 	vpmax | ||||
| 	vpmin | ||||
| 	vpmul | ||||
| 	vpmul | ||||
| 	vpor | ||||
| 	vpsad | ||||
| 	vpshuf | ||||
| 	vpsll | ||||
| 	vpsra | ||||
| 	vpsrad | ||||
| 	vpsrl | ||||
| 	vpsub | ||||
| 	vpunp | ||||
| 	vpxor | ||||
| 	vrcp | ||||
| 	vrsqrt | ||||
| 	vshuf | ||||
| 	vsqrt | ||||
| 	vsub | ||||
| 	vucomis | ||||
| 	vunp | ||||
| 	vxor | ||||
| 	vzero | ||||
| 	xcrypt | ||||
| 	xsha1 | ||||
| 	xsha256 | ||||
| 	xstore-rng | ||||
| 	insertq | ||||
| 	extrq | ||||
| 	vmclear | ||||
| 	invvpid | ||||
| 	adox | ||||
| 	vmxon | ||||
| 	invept | ||||
| 	adcx | ||||
| 	vmclear | ||||
| 	prefetchwt1 | ||||
| 	enclu | ||||
| 	encls | ||||
| 	salc | ||||
| 	fstpnce | ||||
| 	fdisi8087_nop | ||||
| 	fsetpm287_nop | ||||
| 	feni8087_nop | ||||
| 	syscall | ||||
| 	sysret | ||||
| `) | ||||
|  | ||||
| // Instructions known to us but not to libopcodes (at least in binutils 2.24). | ||||
| var libopcodesUnsupported = strings.Fields(` | ||||
| 	addsubps | ||||
| 	aes | ||||
| 	blend | ||||
| 	cvttpd2dq | ||||
| 	dpp | ||||
| 	extract | ||||
| 	haddps | ||||
| 	hsubps | ||||
| 	insert | ||||
| 	invpcid | ||||
| 	lddqu | ||||
| 	movmsk | ||||
| 	movnt | ||||
| 	movq2dq | ||||
| 	mps | ||||
| 	pack | ||||
| 	pblend | ||||
| 	pclmul | ||||
| 	pcmp | ||||
| 	pext | ||||
| 	phmin | ||||
| 	pins | ||||
| 	pmax | ||||
| 	pmin | ||||
| 	pmov | ||||
| 	pmovmsk | ||||
| 	pmul | ||||
| 	popcnt | ||||
| 	pslld | ||||
| 	psllq | ||||
| 	psllw | ||||
| 	psrad | ||||
| 	psraw | ||||
| 	psrl | ||||
| 	ptest | ||||
| 	punpck | ||||
| 	round | ||||
| 	xrstor | ||||
| 	xsavec | ||||
| 	xsaves | ||||
| 	comis | ||||
| 	ucomis | ||||
| 	movhps | ||||
| 	movntps | ||||
| 	rsqrt | ||||
| 	rcpp | ||||
| 	puncpck | ||||
| 	bsf | ||||
| 	movq2dq | ||||
| 	cvttpd2dq | ||||
| 	movq | ||||
| 	hsubpd | ||||
| 	movdqa | ||||
| 	movhpd | ||||
| 	addsubpd | ||||
| 	movd | ||||
| 	haddpd | ||||
| 	cvtps2dq | ||||
| 	bsr | ||||
| 	cvtdq2ps | ||||
| 	rdrand | ||||
| 	maskmov | ||||
| 	movq2dq | ||||
| 	movlhps | ||||
| 	movbe | ||||
| 	movlpd | ||||
| `) | ||||
							
								
								
									
										313
									
								
								vendor/golang.org/x/arch/x86/x86asm/objdumpext_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										313
									
								
								vendor/golang.org/x/arch/x86/x86asm/objdumpext_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,313 @@ | ||||
| // Copyright 2014 The Go Authors.  All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package x86asm | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"debug/elf" | ||||
| 	"encoding/binary" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| // Apologies for the proprietary path, but we need objdump 2.24 + some committed patches that will land in 2.25. | ||||
| const objdumpPath = "/Users/rsc/bin/objdump2" | ||||
|  | ||||
| func testObjdump32(t *testing.T, generate func(func([]byte))) { | ||||
| 	testObjdumpArch(t, generate, 32) | ||||
| } | ||||
|  | ||||
| func testObjdump64(t *testing.T, generate func(func([]byte))) { | ||||
| 	testObjdumpArch(t, generate, 64) | ||||
| } | ||||
|  | ||||
| func testObjdumpArch(t *testing.T, generate func(func([]byte)), arch int) { | ||||
| 	if testing.Short() { | ||||
| 		t.Skip("skipping objdump test in short mode") | ||||
| 	} | ||||
| 	if _, err := os.Stat(objdumpPath); err != nil { | ||||
| 		t.Skip(err) | ||||
| 	} | ||||
|  | ||||
| 	testExtDis(t, "gnu", arch, objdump, generate, allowedMismatchObjdump) | ||||
| } | ||||
|  | ||||
| func objdump(ext *ExtDis) error { | ||||
| 	// File already written with instructions; add ELF header. | ||||
| 	if ext.Arch == 32 { | ||||
| 		if err := writeELF32(ext.File, ext.Size); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} else { | ||||
| 		if err := writeELF64(ext.File, ext.Size); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	b, err := ext.Run(objdumpPath, "-d", "-z", ext.File.Name()) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	var ( | ||||
| 		nmatch  int | ||||
| 		reading bool | ||||
| 		next    uint32 = start | ||||
| 		addr    uint32 | ||||
| 		encbuf  [32]byte | ||||
| 		enc     []byte | ||||
| 		text    string | ||||
| 	) | ||||
| 	flush := func() { | ||||
| 		if addr == next { | ||||
| 			switch text { | ||||
| 			case "repz": | ||||
| 				text = "rep" | ||||
| 			case "repnz": | ||||
| 				text = "repn" | ||||
| 			default: | ||||
| 				text = strings.Replace(text, "repz ", "rep ", -1) | ||||
| 				text = strings.Replace(text, "repnz ", "repn ", -1) | ||||
| 			} | ||||
| 			if m := pcrelw.FindStringSubmatch(text); m != nil { | ||||
| 				targ, _ := strconv.ParseUint(m[2], 16, 64) | ||||
| 				text = fmt.Sprintf("%s .%+#x", m[1], int16(uint32(targ)-uint32(uint16(addr))-uint32(len(enc)))) | ||||
| 			} | ||||
| 			if m := pcrel.FindStringSubmatch(text); m != nil { | ||||
| 				targ, _ := strconv.ParseUint(m[2], 16, 64) | ||||
| 				text = fmt.Sprintf("%s .%+#x", m[1], int32(uint32(targ)-addr-uint32(len(enc)))) | ||||
| 			} | ||||
| 			text = strings.Replace(text, "0x0(", "(", -1) | ||||
| 			text = strings.Replace(text, "%st(0)", "%st", -1) | ||||
|  | ||||
| 			ext.Dec <- ExtInst{addr, encbuf, len(enc), text} | ||||
| 			encbuf = [32]byte{} | ||||
| 			enc = nil | ||||
| 			next += 32 | ||||
| 		} | ||||
| 	} | ||||
| 	var textangle = []byte("<.text>:") | ||||
| 	for { | ||||
| 		line, err := b.ReadSlice('\n') | ||||
| 		if err != nil { | ||||
| 			if err == io.EOF { | ||||
| 				break | ||||
| 			} | ||||
| 			return fmt.Errorf("reading objdump output: %v", err) | ||||
| 		} | ||||
| 		if bytes.Contains(line, textangle) { | ||||
| 			reading = true | ||||
| 			continue | ||||
| 		} | ||||
| 		if !reading { | ||||
| 			continue | ||||
| 		} | ||||
| 		if debug { | ||||
| 			os.Stdout.Write(line) | ||||
| 		} | ||||
| 		if enc1 := parseContinuation(line, encbuf[:len(enc)]); enc1 != nil { | ||||
| 			enc = enc1 | ||||
| 			continue | ||||
| 		} | ||||
| 		flush() | ||||
| 		nmatch++ | ||||
| 		addr, enc, text = parseLine(line, encbuf[:0]) | ||||
| 		if addr > next { | ||||
| 			return fmt.Errorf("address out of sync expected <= %#x at %q in:\n%s", next, line, line) | ||||
| 		} | ||||
| 	} | ||||
| 	flush() | ||||
| 	if next != start+uint32(ext.Size) { | ||||
| 		return fmt.Errorf("not enough results found [%d %d]", next, start+ext.Size) | ||||
| 	} | ||||
| 	if err := ext.Wait(); err != nil { | ||||
| 		return fmt.Errorf("exec: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func parseLine(line []byte, encstart []byte) (addr uint32, enc []byte, text string) { | ||||
| 	oline := line | ||||
| 	i := index(line, ":\t") | ||||
| 	if i < 0 { | ||||
| 		log.Fatalf("cannot parse disassembly: %q", oline) | ||||
| 	} | ||||
| 	x, err := strconv.ParseUint(string(trimSpace(line[:i])), 16, 32) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("cannot parse disassembly: %q", oline) | ||||
| 	} | ||||
| 	addr = uint32(x) | ||||
| 	line = line[i+2:] | ||||
| 	i = bytes.IndexByte(line, '\t') | ||||
| 	if i < 0 { | ||||
| 		log.Fatalf("cannot parse disassembly: %q", oline) | ||||
| 	} | ||||
| 	enc, ok := parseHex(line[:i], encstart) | ||||
| 	if !ok { | ||||
| 		log.Fatalf("cannot parse disassembly: %q", oline) | ||||
| 	} | ||||
| 	line = trimSpace(line[i:]) | ||||
| 	if i := bytes.IndexByte(line, '#'); i >= 0 { | ||||
| 		line = trimSpace(line[:i]) | ||||
| 	} | ||||
| 	text = string(fixSpace(line)) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func parseContinuation(line []byte, enc []byte) []byte { | ||||
| 	i := index(line, ":\t") | ||||
| 	if i < 0 { | ||||
| 		return nil | ||||
| 	} | ||||
| 	line = line[i+1:] | ||||
| 	enc, _ = parseHex(line, enc) | ||||
| 	return enc | ||||
| } | ||||
|  | ||||
| // writeELF32 writes an ELF32 header to the file, | ||||
| // describing a text segment that starts at start | ||||
| // and extends for size bytes. | ||||
| func writeELF32(f *os.File, size int) error { | ||||
| 	f.Seek(0, 0) | ||||
| 	var hdr elf.Header32 | ||||
| 	var prog elf.Prog32 | ||||
| 	var sect elf.Section32 | ||||
| 	var buf bytes.Buffer | ||||
| 	binary.Write(&buf, binary.LittleEndian, &hdr) | ||||
| 	off1 := buf.Len() | ||||
| 	binary.Write(&buf, binary.LittleEndian, &prog) | ||||
| 	off2 := buf.Len() | ||||
| 	binary.Write(&buf, binary.LittleEndian, §) | ||||
| 	off3 := buf.Len() | ||||
| 	buf.Reset() | ||||
| 	data := byte(elf.ELFDATA2LSB) | ||||
| 	hdr = elf.Header32{ | ||||
| 		Ident:     [16]byte{0x7F, 'E', 'L', 'F', 1, data, 1}, | ||||
| 		Type:      2, | ||||
| 		Machine:   uint16(elf.EM_386), | ||||
| 		Version:   1, | ||||
| 		Entry:     start, | ||||
| 		Phoff:     uint32(off1), | ||||
| 		Shoff:     uint32(off2), | ||||
| 		Flags:     0x05000002, | ||||
| 		Ehsize:    uint16(off1), | ||||
| 		Phentsize: uint16(off2 - off1), | ||||
| 		Phnum:     1, | ||||
| 		Shentsize: uint16(off3 - off2), | ||||
| 		Shnum:     3, | ||||
| 		Shstrndx:  2, | ||||
| 	} | ||||
| 	binary.Write(&buf, binary.LittleEndian, &hdr) | ||||
| 	prog = elf.Prog32{ | ||||
| 		Type:   1, | ||||
| 		Off:    start, | ||||
| 		Vaddr:  start, | ||||
| 		Paddr:  start, | ||||
| 		Filesz: uint32(size), | ||||
| 		Memsz:  uint32(size), | ||||
| 		Flags:  5, | ||||
| 		Align:  start, | ||||
| 	} | ||||
| 	binary.Write(&buf, binary.LittleEndian, &prog) | ||||
| 	binary.Write(&buf, binary.LittleEndian, §) // NULL section | ||||
| 	sect = elf.Section32{ | ||||
| 		Name:      1, | ||||
| 		Type:      uint32(elf.SHT_PROGBITS), | ||||
| 		Addr:      start, | ||||
| 		Off:       start, | ||||
| 		Size:      uint32(size), | ||||
| 		Flags:     uint32(elf.SHF_ALLOC | elf.SHF_EXECINSTR), | ||||
| 		Addralign: 4, | ||||
| 	} | ||||
| 	binary.Write(&buf, binary.LittleEndian, §) // .text | ||||
| 	sect = elf.Section32{ | ||||
| 		Name:      uint32(len("\x00.text\x00")), | ||||
| 		Type:      uint32(elf.SHT_STRTAB), | ||||
| 		Addr:      0, | ||||
| 		Off:       uint32(off2 + (off3-off2)*3), | ||||
| 		Size:      uint32(len("\x00.text\x00.shstrtab\x00")), | ||||
| 		Addralign: 1, | ||||
| 	} | ||||
| 	binary.Write(&buf, binary.LittleEndian, §) | ||||
| 	buf.WriteString("\x00.text\x00.shstrtab\x00") | ||||
| 	f.Write(buf.Bytes()) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // writeELF64 writes an ELF64 header to the file, | ||||
| // describing a text segment that starts at start | ||||
| // and extends for size bytes. | ||||
| func writeELF64(f *os.File, size int) error { | ||||
| 	f.Seek(0, 0) | ||||
| 	var hdr elf.Header64 | ||||
| 	var prog elf.Prog64 | ||||
| 	var sect elf.Section64 | ||||
| 	var buf bytes.Buffer | ||||
| 	binary.Write(&buf, binary.LittleEndian, &hdr) | ||||
| 	off1 := buf.Len() | ||||
| 	binary.Write(&buf, binary.LittleEndian, &prog) | ||||
| 	off2 := buf.Len() | ||||
| 	binary.Write(&buf, binary.LittleEndian, §) | ||||
| 	off3 := buf.Len() | ||||
| 	buf.Reset() | ||||
| 	data := byte(elf.ELFDATA2LSB) | ||||
| 	hdr = elf.Header64{ | ||||
| 		Ident:     [16]byte{0x7F, 'E', 'L', 'F', 2, data, 1}, | ||||
| 		Type:      2, | ||||
| 		Machine:   uint16(elf.EM_X86_64), | ||||
| 		Version:   1, | ||||
| 		Entry:     start, | ||||
| 		Phoff:     uint64(off1), | ||||
| 		Shoff:     uint64(off2), | ||||
| 		Flags:     0x05000002, | ||||
| 		Ehsize:    uint16(off1), | ||||
| 		Phentsize: uint16(off2 - off1), | ||||
| 		Phnum:     1, | ||||
| 		Shentsize: uint16(off3 - off2), | ||||
| 		Shnum:     3, | ||||
| 		Shstrndx:  2, | ||||
| 	} | ||||
| 	binary.Write(&buf, binary.LittleEndian, &hdr) | ||||
| 	prog = elf.Prog64{ | ||||
| 		Type:   1, | ||||
| 		Off:    start, | ||||
| 		Vaddr:  start, | ||||
| 		Paddr:  start, | ||||
| 		Filesz: uint64(size), | ||||
| 		Memsz:  uint64(size), | ||||
| 		Flags:  5, | ||||
| 		Align:  start, | ||||
| 	} | ||||
| 	binary.Write(&buf, binary.LittleEndian, &prog) | ||||
| 	binary.Write(&buf, binary.LittleEndian, §) // NULL section | ||||
| 	sect = elf.Section64{ | ||||
| 		Name:      1, | ||||
| 		Type:      uint32(elf.SHT_PROGBITS), | ||||
| 		Addr:      start, | ||||
| 		Off:       start, | ||||
| 		Size:      uint64(size), | ||||
| 		Flags:     uint64(elf.SHF_ALLOC | elf.SHF_EXECINSTR), | ||||
| 		Addralign: 4, | ||||
| 	} | ||||
| 	binary.Write(&buf, binary.LittleEndian, §) // .text | ||||
| 	sect = elf.Section64{ | ||||
| 		Name:      uint32(len("\x00.text\x00")), | ||||
| 		Type:      uint32(elf.SHT_STRTAB), | ||||
| 		Addr:      0, | ||||
| 		Off:       uint64(off2 + (off3-off2)*3), | ||||
| 		Size:      uint64(len("\x00.text\x00.shstrtab\x00")), | ||||
| 		Addralign: 1, | ||||
| 	} | ||||
| 	binary.Write(&buf, binary.LittleEndian, §) | ||||
| 	buf.WriteString("\x00.text\x00.shstrtab\x00") | ||||
| 	f.Write(buf.Bytes()) | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										119
									
								
								vendor/golang.org/x/arch/x86/x86asm/plan9ext_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								vendor/golang.org/x/arch/x86/x86asm/plan9ext_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,119 @@ | ||||
| // Copyright 2014 The Go Authors.  All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package x86asm | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"strconv" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| const plan9Path = "testdata/libmach8db" | ||||
|  | ||||
| func testPlan9Arch(t *testing.T, arch int, generate func(func([]byte))) { | ||||
| 	if testing.Short() { | ||||
| 		t.Skip("skipping libmach test in short mode") | ||||
| 	} | ||||
| 	if _, err := os.Stat(plan9Path); err != nil { | ||||
| 		t.Skip(err) | ||||
| 	} | ||||
|  | ||||
| 	testExtDis(t, "plan9", arch, plan9, generate, allowedMismatchPlan9) | ||||
| } | ||||
|  | ||||
| func testPlan932(t *testing.T, generate func(func([]byte))) { | ||||
| 	testPlan9Arch(t, 32, generate) | ||||
| } | ||||
|  | ||||
| func testPlan964(t *testing.T, generate func(func([]byte))) { | ||||
| 	testPlan9Arch(t, 64, generate) | ||||
| } | ||||
|  | ||||
| func plan9(ext *ExtDis) error { | ||||
| 	flag := "-8" | ||||
| 	if ext.Arch == 64 { | ||||
| 		flag = "-6" | ||||
| 	} | ||||
| 	b, err := ext.Run(plan9Path, flag, ext.File.Name()) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	nmatch := 0 | ||||
| 	next := uint32(start) | ||||
| 	var ( | ||||
| 		addr   uint32 | ||||
| 		encbuf [32]byte | ||||
| 		enc    []byte | ||||
| 		text   string | ||||
| 	) | ||||
|  | ||||
| 	for { | ||||
| 		line, err := b.ReadSlice('\n') | ||||
| 		if err != nil { | ||||
| 			if err == io.EOF { | ||||
| 				break | ||||
| 			} | ||||
| 			return fmt.Errorf("reading libmach8db output: %v", err) | ||||
| 		} | ||||
| 		if debug { | ||||
| 			os.Stdout.Write(line) | ||||
| 		} | ||||
| 		nmatch++ | ||||
| 		addr, enc, text = parseLinePlan9(line, encbuf[:0]) | ||||
| 		if addr > next { | ||||
| 			return fmt.Errorf("address out of sync expected <= %#x at %q in:\n%s", next, line, line) | ||||
| 		} | ||||
| 		if addr < next { | ||||
| 			continue | ||||
| 		} | ||||
| 		if m := pcrelw.FindStringSubmatch(text); m != nil { | ||||
| 			targ, _ := strconv.ParseUint(m[2], 16, 64) | ||||
| 			text = fmt.Sprintf("%s .%+#x", m[1], int16(uint32(targ)-uint32(uint16(addr))-uint32(len(enc)))) | ||||
| 		} | ||||
| 		if m := pcrel.FindStringSubmatch(text); m != nil { | ||||
| 			targ, _ := strconv.ParseUint(m[2], 16, 64) | ||||
| 			text = fmt.Sprintf("%s .%+#x", m[1], int32(uint32(targ)-addr-uint32(len(enc)))) | ||||
| 		} | ||||
| 		ext.Dec <- ExtInst{addr, encbuf, len(enc), text} | ||||
| 		encbuf = [32]byte{} | ||||
| 		enc = nil | ||||
| 		next += 32 | ||||
| 	} | ||||
| 	if next != start+uint32(ext.Size) { | ||||
| 		return fmt.Errorf("not enough results found [%d %d]", next, start+ext.Size) | ||||
| 	} | ||||
| 	if err := ext.Wait(); err != nil { | ||||
| 		return fmt.Errorf("exec: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func parseLinePlan9(line []byte, encstart []byte) (addr uint32, enc []byte, text string) { | ||||
| 	i := bytes.IndexByte(line, ' ') | ||||
| 	if i < 0 || line[0] != '0' || line[1] != 'x' { | ||||
| 		log.Fatalf("cannot parse disassembly: %q", line) | ||||
| 	} | ||||
| 	j := bytes.IndexByte(line[i+1:], ' ') | ||||
| 	if j < 0 { | ||||
| 		log.Fatalf("cannot parse disassembly: %q", line) | ||||
| 	} | ||||
| 	j += i + 1 | ||||
| 	x, err := strconv.ParseUint(string(trimSpace(line[2:i])), 16, 32) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("cannot parse disassembly: %q", line) | ||||
| 	} | ||||
| 	addr = uint32(x) | ||||
| 	enc, ok := parseHex(line[i+1:j], encstart) | ||||
| 	if !ok { | ||||
| 		log.Fatalf("cannot parse disassembly: %q", line) | ||||
| 	} | ||||
| 	return addr, enc, string(fixSpace(line[j+1:])) | ||||
| } | ||||
							
								
								
									
										35
									
								
								vendor/golang.org/x/arch/x86/x86asm/plan9x.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										35
									
								
								vendor/golang.org/x/arch/x86/x86asm/plan9x.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -9,6 +9,8 @@ import ( | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| type SymLookup func(uint64) (string, uint64) | ||||
|  | ||||
| // GoSyntax returns the Go assembler syntax for the instruction. | ||||
| // The syntax was originally defined by Plan 9. | ||||
| // The pc is the program counter of the instruction, used for expanding | ||||
| @ -16,7 +18,7 @@ import ( | ||||
| // The symname function queries the symbol table for the program | ||||
| // being disassembled. Given a target address it returns the name and base | ||||
| // address of the symbol containing the target, if any; otherwise it returns "", 0. | ||||
| func GoSyntax(inst Inst, pc uint64, symname func(uint64) (string, uint64)) string { | ||||
| func GoSyntax(inst Inst, pc uint64, symname SymLookup) string { | ||||
| 	if symname == nil { | ||||
| 		symname = func(uint64) (string, uint64) { return "", 0 } | ||||
| 	} | ||||
| @ -119,14 +121,12 @@ func plan9Arg(inst *Inst, pc uint64, symname func(uint64) (string, uint64), arg | ||||
| 		} | ||||
| 		return fmt.Sprintf("$%#x", uint64(a)) | ||||
| 	case Mem: | ||||
| 		if a.Segment == 0 && a.Disp != 0 && a.Base == 0 && (a.Index == 0 || a.Scale == 0) { | ||||
| 			if s, base := symname(uint64(a.Disp)); s != "" { | ||||
| 				suffix := "" | ||||
| 				if uint64(a.Disp) != base { | ||||
| 					suffix = fmt.Sprintf("%+d", uint64(a.Disp)-base) | ||||
| 				} | ||||
| 				return fmt.Sprintf("%s%s(SB)", s, suffix) | ||||
| 		if s, disp := memArgToSymbol(a, pc, inst.Len, symname); s != "" { | ||||
| 			suffix := "" | ||||
| 			if disp != 0 { | ||||
| 				suffix = fmt.Sprintf("%+d", disp) | ||||
| 			} | ||||
| 			return fmt.Sprintf("%s%s(SB)", s, suffix) | ||||
| 		} | ||||
| 		s := "" | ||||
| 		if a.Segment != 0 { | ||||
| @ -148,6 +148,25 @@ func plan9Arg(inst *Inst, pc uint64, symname func(uint64) (string, uint64), arg | ||||
| 	return arg.String() | ||||
| } | ||||
|  | ||||
| func memArgToSymbol(a Mem, pc uint64, instrLen int, symname SymLookup) (string, int64) { | ||||
| 	if a.Segment != 0 || a.Disp == 0 || a.Index != 0 || a.Scale != 0 { | ||||
| 		return "", 0 | ||||
| 	} | ||||
|  | ||||
| 	var disp uint64 | ||||
| 	switch a.Base { | ||||
| 	case IP, EIP, RIP: | ||||
| 		disp = uint64(a.Disp + int64(pc) + int64(instrLen)) | ||||
| 	case 0: | ||||
| 		disp = uint64(a.Disp) | ||||
| 	default: | ||||
| 		return "", 0 | ||||
| 	} | ||||
|  | ||||
| 	s, base := symname(disp) | ||||
| 	return s, int64(disp) - int64(base) | ||||
| } | ||||
|  | ||||
| var plan9Suffix = [maxOp + 1]bool{ | ||||
| 	ADC:       true, | ||||
| 	ADD:       true, | ||||
|  | ||||
							
								
								
									
										54
									
								
								vendor/golang.org/x/arch/x86/x86asm/plan9x_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								vendor/golang.org/x/arch/x86/x86asm/plan9x_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,54 @@ | ||||
| // Copyright 2014 The Go Authors.  All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package x86asm | ||||
|  | ||||
| import ( | ||||
| 	"strings" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func TestPlan932Manual(t *testing.T)   { testPlan932(t, hexCases(t, plan9ManualTests)) } | ||||
| func TestPlan932Testdata(t *testing.T) { testPlan932(t, concat(basicPrefixes, testdataCases(t))) } | ||||
| func TestPlan932ModRM(t *testing.T)    { testPlan932(t, concat(basicPrefixes, enumModRM)) } | ||||
| func TestPlan932OneByte(t *testing.T)  { testBasic(t, testPlan932) } | ||||
| func TestPlan9320F(t *testing.T)       { testBasic(t, testPlan932, 0x0F) } | ||||
| func TestPlan9320F38(t *testing.T)     { testBasic(t, testPlan932, 0x0F, 0x38) } | ||||
| func TestPlan9320F3A(t *testing.T)     { testBasic(t, testPlan932, 0x0F, 0x3A) } | ||||
| func TestPlan932Prefix(t *testing.T)   { testPrefix(t, testPlan932) } | ||||
|  | ||||
| func TestPlan964Manual(t *testing.T)   { testPlan964(t, hexCases(t, plan9ManualTests)) } | ||||
| func TestPlan964Testdata(t *testing.T) { testPlan964(t, concat(basicPrefixes, testdataCases(t))) } | ||||
| func TestPlan964ModRM(t *testing.T)    { testPlan964(t, concat(basicPrefixes, enumModRM)) } | ||||
| func TestPlan964OneByte(t *testing.T)  { testBasic(t, testPlan964) } | ||||
| func TestPlan9640F(t *testing.T)       { testBasic(t, testPlan964, 0x0F) } | ||||
| func TestPlan9640F38(t *testing.T)     { testBasic(t, testPlan964, 0x0F, 0x38) } | ||||
| func TestPlan9640F3A(t *testing.T)     { testBasic(t, testPlan964, 0x0F, 0x3A) } | ||||
| func TestPlan964Prefix(t *testing.T)   { testPrefix(t, testPlan964) } | ||||
|  | ||||
| func TestPlan964REXTestdata(t *testing.T) { | ||||
| 	testPlan964(t, filter(concat3(basicPrefixes, rexPrefixes, testdataCases(t)), isValidREX)) | ||||
| } | ||||
| func TestPlan964REXModRM(t *testing.T)   { testPlan964(t, concat3(basicPrefixes, rexPrefixes, enumModRM)) } | ||||
| func TestPlan964REXOneByte(t *testing.T) { testBasicREX(t, testPlan964) } | ||||
| func TestPlan964REX0F(t *testing.T)      { testBasicREX(t, testPlan964, 0x0F) } | ||||
| func TestPlan964REX0F38(t *testing.T)    { testBasicREX(t, testPlan964, 0x0F, 0x38) } | ||||
| func TestPlan964REX0F3A(t *testing.T)    { testBasicREX(t, testPlan964, 0x0F, 0x3A) } | ||||
| func TestPlan964REXPrefix(t *testing.T)  { testPrefixREX(t, testPlan964) } | ||||
|  | ||||
| // plan9ManualTests holds test cases that will be run by TestPlan9Manual32 and TestPlan9Manual64. | ||||
| // If you are debugging a few cases that turned up in a longer run, it can be useful | ||||
| // to list them here and then use -run=Plan9Manual, particularly with tracing enabled. | ||||
| var plan9ManualTests = ` | ||||
| ` | ||||
|  | ||||
| // allowedMismatchPlan9 reports whether the mismatch between text and dec | ||||
| // should be allowed by the test. | ||||
| func allowedMismatchPlan9(text string, size int, inst *Inst, dec ExtInst) bool { | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // Instructions known to us but not to plan9. | ||||
| var plan9Unsupported = strings.Fields(` | ||||
| `) | ||||
							
								
								
									
										12
									
								
								vendor/golang.org/x/arch/x86/x86asm/testdata/Makefile
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								vendor/golang.org/x/arch/x86/x86asm/testdata/Makefile
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| libmach8db: libmach8db.c | ||||
| 	9c libmach8db.c && 9l -o libmach8db libmach8db.o; rm libmach8db.o | ||||
|  | ||||
| newdecode.txt: | ||||
| 	cd ..; go test -cover -run 'Objdump.*32' -v -timeout 10h -printtests 2>&1 | tee log | ||||
| 	cd ..; go test -cover -run 'Objdump.*64' -v -timeout 10h -printtests 2>&1 | tee -a log | ||||
| 	cd ..; go test -cover -run 'Xed.*32' -v -timeout 10h -printtests 2>&1 | tee -a log | ||||
| 	cd ..; go test -cover -run 'Xed.*64' -v -timeout 10h -printtests 2>&1 | tee -a log | ||||
| 	cd ..; go test -cover -run 'Plan9.*32' -v -timeout 10h -printtests 2>&1 | tee -a log | ||||
| 	cd ..; go test -cover -run 'Plan9.*64' -v -timeout 10h -printtests 2>&1 | tee -a log | ||||
| 	egrep '	(gnu|intel|plan9)	' ../log |sort >newdecode.txt | ||||
|  | ||||
							
								
								
									
										2075
									
								
								vendor/golang.org/x/arch/x86/x86asm/testdata/libmach8db.c
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2075
									
								
								vendor/golang.org/x/arch/x86/x86asm/testdata/libmach8db.c
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										211
									
								
								vendor/golang.org/x/arch/x86/x86asm/xed_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										211
									
								
								vendor/golang.org/x/arch/x86/x86asm/xed_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,211 @@ | ||||
| // Copyright 2014 The Go Authors.  All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package x86asm | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func TestXed32Manual(t *testing.T)   { testXed32(t, hexCases(t, xedManualTests)) } | ||||
| func TestXed32Testdata(t *testing.T) { testXed32(t, concat(basicPrefixes, testdataCases(t))) } | ||||
| func TestXed32ModRM(t *testing.T)    { testXed32(t, concat(basicPrefixes, enumModRM)) } | ||||
| func TestXed32OneByte(t *testing.T)  { testBasic(t, testXed32) } | ||||
| func TestXed320F(t *testing.T)       { testBasic(t, testXed32, 0x0F) } | ||||
| func TestXed320F38(t *testing.T)     { testBasic(t, testXed32, 0x0F, 0x38) } | ||||
| func TestXed320F3A(t *testing.T)     { testBasic(t, testXed32, 0x0F, 0x3A) } | ||||
| func TestXed32Prefix(t *testing.T)   { testPrefix(t, testXed32) } | ||||
|  | ||||
| func TestXed64Manual(t *testing.T)   { testXed64(t, hexCases(t, xedManualTests)) } | ||||
| func TestXed64Testdata(t *testing.T) { testXed64(t, concat(basicPrefixes, testdataCases(t))) } | ||||
| func TestXed64ModRM(t *testing.T)    { testXed64(t, concat(basicPrefixes, enumModRM)) } | ||||
| func TestXed64OneByte(t *testing.T)  { testBasic(t, testXed64) } | ||||
| func TestXed640F(t *testing.T)       { testBasic(t, testXed64, 0x0F) } | ||||
| func TestXed640F38(t *testing.T)     { testBasic(t, testXed64, 0x0F, 0x38) } | ||||
| func TestXed640F3A(t *testing.T)     { testBasic(t, testXed64, 0x0F, 0x3A) } | ||||
| func TestXed64Prefix(t *testing.T)   { testPrefix(t, testXed64) } | ||||
|  | ||||
| func TestXed64REXTestdata(t *testing.T) { | ||||
| 	testXed64(t, filter(concat3(basicPrefixes, rexPrefixes, testdataCases(t)), isValidREX)) | ||||
| } | ||||
| func TestXed64REXModRM(t *testing.T)   { testXed64(t, concat3(basicPrefixes, rexPrefixes, enumModRM)) } | ||||
| func TestXed64REXOneByte(t *testing.T) { testBasicREX(t, testXed64) } | ||||
| func TestXed64REX0F(t *testing.T)      { testBasicREX(t, testXed64, 0x0F) } | ||||
| func TestXed64REX0F38(t *testing.T)    { testBasicREX(t, testXed64, 0x0F, 0x38) } | ||||
| func TestXed64REX0F3A(t *testing.T)    { testBasicREX(t, testXed64, 0x0F, 0x3A) } | ||||
| func TestXed64REXPrefix(t *testing.T)  { testPrefixREX(t, testXed64) } | ||||
|  | ||||
| // xedManualTests holds test cases that will be run by TestXedManual32 and TestXedManual64. | ||||
| // If you are debugging a few cases that turned up in a longer run, it can be useful | ||||
| // to list them here and then use -run=XedManual, particularly with tracing enabled. | ||||
| var xedManualTests = ` | ||||
| 6690 | ||||
| ` | ||||
|  | ||||
| // allowedMismatchXed reports whether the mismatch between text and dec | ||||
| // should be allowed by the test. | ||||
| func allowedMismatchXed(text string, size int, inst *Inst, dec ExtInst) bool { | ||||
| 	if (contains(text, "error:") || isPrefix(text) && size == 1) && contains(dec.text, "GENERAL_ERROR", "INSTR_TOO_LONG", "BAD_LOCK_PREFIX") { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	if contains(dec.text, "BAD_LOCK_PREFIX") && countExactPrefix(inst, PrefixLOCK|PrefixInvalid) > 0 { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	if contains(dec.text, "BAD_LOCK_PREFIX", "GENERAL_ERROR") && countExactPrefix(inst, PrefixLOCK|PrefixImplicit) > 0 { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	if text == "lock" && size == 1 && contains(dec.text, "BAD_LOCK_PREFIX") { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// Instructions not known to us. | ||||
| 	if (contains(text, "error:") || isPrefix(text) && size == 1) && contains(dec.text, unsupported...) { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// Instructions not known to xed. | ||||
| 	if contains(text, xedUnsupported...) && contains(dec.text, "ERROR") { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	if (contains(text, "error:") || isPrefix(text) && size == 1) && contains(dec.text, "shl ") && (inst.Opcode>>16)&0xEC38 == 0xC030 { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// 82 11 22: xed says 'adc byte ptr [ecx], 0x22' but there is no justification in the manuals for that. | ||||
| 	// C0 30 11: xed says 'shl byte ptr [eax], 0x11' but there is no justification in the manuals for that. | ||||
| 	// F6 08 11: xed says 'test byte ptr [eax], 0x11' but there is no justification in the manuals for that. | ||||
| 	if (contains(text, "error:") || isPrefix(text) && size == 1) && hasByte(dec.enc[:dec.nenc], 0x82, 0xC0, 0xC1, 0xD0, 0xD1, 0xD2, 0xD3, 0xF6, 0xF7) { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// F3 11 22 and many others: xed allows and drops misused rep/repn prefix. | ||||
| 	if (text == "rep" && dec.enc[0] == 0xF3 || (text == "repn" || text == "repne") && dec.enc[0] == 0xF2) && (!contains(dec.text, "ins", "outs", "movs", "lods", "cmps", "scas") || contains(dec.text, "xmm")) { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// 0F C7 30: xed says vmptrld qword ptr [eax]; we say rdrand eax. | ||||
| 	// TODO(rsc): Fix, since we are probably wrong, but we don't have vmptrld in the manual. | ||||
| 	if contains(text, "rdrand") && contains(dec.text, "vmptrld", "vmxon", "vmclear") { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// F3 0F AE 00: we say 'rdfsbase dword ptr [eax]' but RDFSBASE needs a register. | ||||
| 	// Also, this is a 64-bit only instruction. | ||||
| 	// TODO(rsc): Fix to reject this encoding. | ||||
| 	if contains(text, "rdfsbase", "rdgsbase", "wrfsbase", "wrgsbase") && contains(dec.text, "ERROR") { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// 0F 01 F8: we say swapgs but that's only valid in 64-bit mode. | ||||
| 	// TODO(rsc): Fix. | ||||
| 	if contains(text, "swapgs") { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// 0F 24 11: 'mov ecx, tr2' except there is no TR2. | ||||
| 	// Or maybe the MOV to TR registers doesn't use RMF. | ||||
| 	if contains(text, "cr1", "cr5", "cr6", "cr7", "tr0", "tr1", "tr2", "tr3", "tr4", "tr5", "tr6", "tr7") && contains(dec.text, "ERROR") { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// 0F 19 11, 0F 1C 11, 0F 1D 11, 0F 1E 11, 0F 1F 11: xed says nop, | ||||
| 	// but the Intel manuals say that the only NOP there is 0F 1F /0. | ||||
| 	// Perhaps xed is reporting an older encoding. | ||||
| 	if (contains(text, "error:") || isPrefix(text) && size == 1) && contains(dec.text, "nop ") && (inst.Opcode>>8)&0xFFFF38 != 0x0F1F00 { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// 66 0F AE 38: clflushopt but we only know clflush | ||||
| 	if contains(text, "clflush") && contains(dec.text, "clflushopt") { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// 0F 20 04 11: MOV SP, CR0 but has mod!=3 despite register argument. | ||||
| 	// (This encoding ignores the mod bits.) The decoder sees the non-register | ||||
| 	// mod and reads farther ahead to decode the memory reference that | ||||
| 	// isn't really there, causing the size to be too large. | ||||
| 	// TODO(rsc): Fix. | ||||
| 	if text == dec.text && size > dec.nenc && contains(text, " cr", " dr", " tr") { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// 0F AE E9: xed says lfence, which is wrong (only 0F AE E8 is lfence). And so on. | ||||
| 	if contains(dec.text, "fence") && hasByte(dec.enc[:dec.nenc], 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF) { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// DD C9, DF C9: xed says 'fxch st0, st1' but that instruction is D9 C9. | ||||
| 	if (contains(text, "error:") || isPrefix(text) && size == 1) && contains(dec.text, "fxch ") && hasByte(dec.enc[:dec.nenc], 0xDD, 0xDF) { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// DC D4: xed says 'fcom st0, st4' but that instruction is D8 D4. | ||||
| 	if (contains(text, "error:") || isPrefix(text) && size == 1) && contains(dec.text, "fcom ") && hasByte(dec.enc[:dec.nenc], 0xD8, 0xDC) { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// DE D4: xed says 'fcomp st0, st4' but that instruction is D8 D4. | ||||
| 	if (contains(text, "error:") || isPrefix(text) && size == 1) && contains(dec.text, "fcomp ") && hasByte(dec.enc[:dec.nenc], 0xDC, 0xDE) { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// DF D4: xed says 'fstp st4, st0' but that instruction is DD D4. | ||||
| 	if (contains(text, "error:") || isPrefix(text) && size == 1) && contains(dec.text, "fstp ") && hasByte(dec.enc[:dec.nenc], 0xDF) { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func countExactPrefix(inst *Inst, target Prefix) int { | ||||
| 	n := 0 | ||||
| 	for _, p := range inst.Prefix { | ||||
| 		if p == target { | ||||
| 			n++ | ||||
| 		} | ||||
| 	} | ||||
| 	return n | ||||
| } | ||||
|  | ||||
| func hasByte(src []byte, target ...byte) bool { | ||||
| 	for _, b := range target { | ||||
| 		if bytes.IndexByte(src, b) >= 0 { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // Instructions known to us but not to xed. | ||||
| var xedUnsupported = strings.Fields(` | ||||
| 	xrstor | ||||
| 	xsave | ||||
| 	xsave | ||||
| 	ud1 | ||||
| 	xgetbv | ||||
| 	xsetbv | ||||
| 	fxsave | ||||
| 	fxrstor | ||||
| 	clflush | ||||
| 	lfence | ||||
| 	mfence | ||||
| 	sfence | ||||
| 	rsqrtps | ||||
| 	rcpps | ||||
| 	emms | ||||
| 	ldmxcsr | ||||
| 	stmxcsr | ||||
| 	movhpd | ||||
| 	movnti | ||||
| 	rdrand | ||||
| 	movbe | ||||
| 	movlpd | ||||
| 	sysret | ||||
| `) | ||||
							
								
								
									
										205
									
								
								vendor/golang.org/x/arch/x86/x86asm/xedext_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										205
									
								
								vendor/golang.org/x/arch/x86/x86asm/xedext_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,205 @@ | ||||
| package x86asm | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| // xed binary from Intel sde-external-6.22.0-2014-03-06. | ||||
| const xedPath = "/Users/rsc/bin/xed" | ||||
|  | ||||
| func testXedArch(t *testing.T, arch int, generate func(func([]byte))) { | ||||
| 	if testing.Short() { | ||||
| 		t.Skip("skipping xed test in short mode") | ||||
| 	} | ||||
| 	if _, err := os.Stat(xedPath); err != nil { | ||||
| 		t.Skip(err) | ||||
| 	} | ||||
|  | ||||
| 	testExtDis(t, "intel", arch, xed, generate, allowedMismatchXed) | ||||
| } | ||||
|  | ||||
| func testXed32(t *testing.T, generate func(func([]byte))) { | ||||
| 	testXedArch(t, 32, generate) | ||||
| } | ||||
|  | ||||
| func testXed64(t *testing.T, generate func(func([]byte))) { | ||||
| 	testXedArch(t, 64, generate) | ||||
| } | ||||
|  | ||||
| func xed(ext *ExtDis) error { | ||||
| 	b, err := ext.Run(xedPath, fmt.Sprintf("-%d", ext.Arch), "-n", "1G", "-ir", ext.File.Name()) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	nmatch := 0 | ||||
| 	next := uint32(start) | ||||
| 	var ( | ||||
| 		addr   uint32 | ||||
| 		encbuf [32]byte | ||||
| 		enc    []byte | ||||
| 		text   string | ||||
| 	) | ||||
|  | ||||
| 	var xedEnd = []byte("# end of text section") | ||||
| 	var xedEnd1 = []byte("# Errors") | ||||
|  | ||||
| 	eof := false | ||||
| 	for { | ||||
| 		line, err := b.ReadSlice('\n') | ||||
| 		if err != nil { | ||||
| 			if err == io.EOF { | ||||
| 				break | ||||
| 			} | ||||
| 			return fmt.Errorf("reading objdump output: %v", err) | ||||
| 		} | ||||
| 		if debug { | ||||
| 			os.Stdout.Write(line) | ||||
| 		} | ||||
| 		if bytes.HasPrefix(line, xedEnd) || bytes.HasPrefix(line, xedEnd1) { | ||||
| 			eof = true | ||||
| 		} | ||||
| 		if eof { | ||||
| 			continue | ||||
| 		} | ||||
| 		nmatch++ | ||||
| 		addr, enc, text = parseLineXed(line, encbuf[:0]) | ||||
| 		if addr > next { | ||||
| 			return fmt.Errorf("address out of sync expected <= %#x at %q in:\n%s", next, line, line) | ||||
| 		} | ||||
| 		if addr < next { | ||||
| 			continue | ||||
| 		} | ||||
| 		switch text { | ||||
| 		case "repz": | ||||
| 			text = "rep" | ||||
| 		case "repnz": | ||||
| 			text = "repn" | ||||
| 		default: | ||||
| 			text = strings.Replace(text, "repz ", "rep ", -1) | ||||
| 			text = strings.Replace(text, "repnz ", "repn ", -1) | ||||
| 		} | ||||
| 		if m := pcrelw.FindStringSubmatch(text); m != nil { | ||||
| 			targ, _ := strconv.ParseUint(m[2], 16, 64) | ||||
| 			text = fmt.Sprintf("%s .%+#x", m[1], int16(uint32(targ)-uint32(uint16(addr))-uint32(len(enc)))) | ||||
| 		} | ||||
| 		if m := pcrel.FindStringSubmatch(text); m != nil { | ||||
| 			targ, _ := strconv.ParseUint(m[2], 16, 64) | ||||
| 			text = fmt.Sprintf("%s .%+#x", m[1], int32(uint32(targ)-addr-uint32(len(enc)))) | ||||
| 		} | ||||
| 		ext.Dec <- ExtInst{addr, encbuf, len(enc), text} | ||||
| 		encbuf = [32]byte{} | ||||
| 		enc = nil | ||||
| 		next += 32 | ||||
| 	} | ||||
| 	if next != start+uint32(ext.Size) { | ||||
| 		return fmt.Errorf("not enough results found [%d %d]", next, start+ext.Size) | ||||
| 	} | ||||
| 	if err := ext.Wait(); err != nil { | ||||
| 		return fmt.Errorf("exec: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	xedInRaw    = []byte("In raw...") | ||||
| 	xedDots     = []byte("...") | ||||
| 	xdis        = []byte("XDIS ") | ||||
| 	xedError    = []byte("ERROR: ") | ||||
| 	xedNoDecode = []byte("Could not decode at offset: 0x") | ||||
| ) | ||||
|  | ||||
| func parseLineXed(line []byte, encstart []byte) (addr uint32, enc []byte, text string) { | ||||
| 	oline := line | ||||
| 	if bytes.HasPrefix(line, xedInRaw) || bytes.HasPrefix(line, xedDots) { | ||||
| 		return 0, nil, "" | ||||
| 	} | ||||
| 	if bytes.HasPrefix(line, xedError) { | ||||
| 		i := bytes.IndexByte(line[len(xedError):], ' ') | ||||
| 		if i < 0 { | ||||
| 			log.Fatalf("cannot parse error: %q", oline) | ||||
| 		} | ||||
| 		errstr := string(line[len(xedError):]) | ||||
| 		i = bytes.Index(line, xedNoDecode) | ||||
| 		if i < 0 { | ||||
| 			log.Fatalf("cannot parse error: %q", oline) | ||||
| 		} | ||||
| 		i += len(xedNoDecode) | ||||
| 		j := bytes.IndexByte(line[i:], ' ') | ||||
| 		if j < 0 { | ||||
| 			log.Fatalf("cannot parse error: %q", oline) | ||||
| 		} | ||||
| 		x, err := strconv.ParseUint(string(trimSpace(line[i:i+j])), 16, 32) | ||||
| 		if err != nil { | ||||
| 			log.Fatalf("cannot parse disassembly: %q", oline) | ||||
| 		} | ||||
| 		addr = uint32(x) | ||||
| 		return addr, nil, errstr | ||||
| 	} | ||||
|  | ||||
| 	if !bytes.HasPrefix(line, xdis) { | ||||
| 		log.Fatalf("cannot parse disassembly: %q", oline) | ||||
| 	} | ||||
|  | ||||
| 	i := bytes.IndexByte(line, ':') | ||||
| 	if i < 0 { | ||||
| 		log.Fatalf("cannot parse disassembly: %q", oline) | ||||
| 	} | ||||
| 	x, err := strconv.ParseUint(string(trimSpace(line[len(xdis):i])), 16, 32) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("cannot parse disassembly: %q", oline) | ||||
| 	} | ||||
| 	addr = uint32(x) | ||||
|  | ||||
| 	// spaces | ||||
| 	i++ | ||||
| 	for i < len(line) && line[i] == ' ' { | ||||
| 		i++ | ||||
| 	} | ||||
| 	// instruction class, spaces | ||||
| 	for i < len(line) && line[i] != ' ' { | ||||
| 		i++ | ||||
| 	} | ||||
| 	for i < len(line) && line[i] == ' ' { | ||||
| 		i++ | ||||
| 	} | ||||
| 	// instruction set, spaces | ||||
| 	for i < len(line) && line[i] != ' ' { | ||||
| 		i++ | ||||
| 	} | ||||
| 	for i < len(line) && line[i] == ' ' { | ||||
| 		i++ | ||||
| 	} | ||||
|  | ||||
| 	// hex | ||||
| 	hexStart := i | ||||
| 	for i < len(line) && line[i] != ' ' { | ||||
| 		i++ | ||||
| 	} | ||||
| 	hexEnd := i | ||||
| 	for i < len(line) && line[i] == ' ' { | ||||
| 		i++ | ||||
| 	} | ||||
|  | ||||
| 	// text | ||||
| 	textStart := i | ||||
| 	for i < len(line) && line[i] != '\n' { | ||||
| 		i++ | ||||
| 	} | ||||
| 	textEnd := i | ||||
|  | ||||
| 	enc, ok := parseHex(line[hexStart:hexEnd], encstart) | ||||
| 	if !ok { | ||||
| 		log.Fatalf("cannot parse disassembly: %q", oline) | ||||
| 	} | ||||
|  | ||||
| 	return addr, enc, string(fixSpace(line[textStart:textEnd])) | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 aarzilli
					aarzilli