github.com/slayercat/go@v0.0.0-20170428012452-c51559813f61/src/cmd/internal/objfile/disasm.go (about)

     1  // Copyright 2014 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package objfile
     6  
     7  import (
     8  	"bufio"
     9  	"bytes"
    10  	"cmd/internal/src"
    11  	"container/list"
    12  	"debug/gosym"
    13  	"encoding/binary"
    14  	"fmt"
    15  	"io"
    16  	"io/ioutil"
    17  	"os"
    18  	"path/filepath"
    19  	"regexp"
    20  	"sort"
    21  	"strings"
    22  	"text/tabwriter"
    23  
    24  	"golang.org/x/arch/arm/armasm"
    25  	"golang.org/x/arch/ppc64/ppc64asm"
    26  	"golang.org/x/arch/x86/x86asm"
    27  )
    28  
    29  // Disasm is a disassembler for a given File.
    30  type Disasm struct {
    31  	syms      []Sym            //symbols in file, sorted by address
    32  	pcln      Liner            // pcln table
    33  	text      []byte           // bytes of text segment (actual instructions)
    34  	textStart uint64           // start PC of text
    35  	textEnd   uint64           // end PC of text
    36  	goarch    string           // GOARCH string
    37  	disasm    disasmFunc       // disassembler function for goarch
    38  	byteOrder binary.ByteOrder // byte order for goarch
    39  }
    40  
    41  // Disasm returns a disassembler for the file f.
    42  func (f *File) Disasm() (*Disasm, error) {
    43  	syms, err := f.Symbols()
    44  	if err != nil {
    45  		return nil, err
    46  	}
    47  
    48  	pcln, err := f.PCLineTable()
    49  	if err != nil {
    50  		return nil, err
    51  	}
    52  
    53  	textStart, textBytes, err := f.Text()
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  
    58  	goarch := f.GOARCH()
    59  	disasm := disasms[goarch]
    60  	byteOrder := byteOrders[goarch]
    61  	if disasm == nil || byteOrder == nil {
    62  		return nil, fmt.Errorf("unsupported architecture")
    63  	}
    64  
    65  	// Filter out section symbols, overwriting syms in place.
    66  	keep := syms[:0]
    67  	for _, sym := range syms {
    68  		switch sym.Name {
    69  		case "runtime.text", "text", "_text", "runtime.etext", "etext", "_etext":
    70  			// drop
    71  		default:
    72  			keep = append(keep, sym)
    73  		}
    74  	}
    75  	syms = keep
    76  	d := &Disasm{
    77  		syms:      syms,
    78  		pcln:      pcln,
    79  		text:      textBytes,
    80  		textStart: textStart,
    81  		textEnd:   textStart + uint64(len(textBytes)),
    82  		goarch:    goarch,
    83  		disasm:    disasm,
    84  		byteOrder: byteOrder,
    85  	}
    86  
    87  	return d, nil
    88  }
    89  
    90  // lookup finds the symbol name containing addr.
    91  func (d *Disasm) lookup(addr uint64) (name string, base uint64) {
    92  	i := sort.Search(len(d.syms), func(i int) bool { return addr < d.syms[i].Addr })
    93  	if i > 0 {
    94  		s := d.syms[i-1]
    95  		if s.Addr != 0 && s.Addr <= addr && addr < s.Addr+uint64(s.Size) {
    96  			return s.Name, s.Addr
    97  		}
    98  	}
    99  	return "", 0
   100  }
   101  
   102  // base returns the final element in the path.
   103  // It works on both Windows and Unix paths,
   104  // regardless of host operating system.
   105  func base(path string) string {
   106  	path = path[strings.LastIndex(path, "/")+1:]
   107  	path = path[strings.LastIndex(path, `\`)+1:]
   108  	return path
   109  }
   110  
   111  // CachedFile contains the content of a file split into lines.
   112  type CachedFile struct {
   113  	FileName string
   114  	Lines    [][]byte
   115  }
   116  
   117  // FileCache is a simple LRU cache of file contents.
   118  type FileCache struct {
   119  	files  *list.List
   120  	maxLen int
   121  }
   122  
   123  // NewFileCache returns a FileCache which can contain up to maxLen cached file contents.
   124  func NewFileCache(maxLen int) *FileCache {
   125  	return &FileCache{
   126  		files:  list.New(),
   127  		maxLen: maxLen,
   128  	}
   129  }
   130  
   131  // Line returns the source code line for the given file and line number.
   132  // If the file is not already cached, reads it , inserts it into the cache,
   133  // and removes the least recently used file if necessary.
   134  // If the file is in cache, moves it up to the front of the list.
   135  func (fc *FileCache) Line(filename string, line int) ([]byte, error) {
   136  	if filepath.Ext(filename) != ".go" {
   137  		return nil, nil
   138  	}
   139  
   140  	// Clean filenames returned by src.Pos.SymFilename()
   141  	// or src.PosBase.SymFilename() removing
   142  	// the leading src.FileSymPrefix.
   143  	if strings.HasPrefix(filename, src.FileSymPrefix) {
   144  		filename = filename[len(src.FileSymPrefix):]
   145  	}
   146  
   147  	// Expand literal "$GOROOT" rewrited by obj.AbsFile()
   148  	filename = filepath.Clean(os.ExpandEnv(filename))
   149  
   150  	var cf *CachedFile
   151  	var e *list.Element
   152  
   153  	for e = fc.files.Front(); e != nil; e = e.Next() {
   154  		cf = e.Value.(*CachedFile)
   155  		if cf.FileName == filename {
   156  			break
   157  		}
   158  	}
   159  
   160  	if e == nil {
   161  		content, err := ioutil.ReadFile(filename)
   162  		if err != nil {
   163  			return nil, err
   164  		}
   165  
   166  		cf = &CachedFile{
   167  			FileName: filename,
   168  			Lines:    bytes.Split(content, []byte{'\n'}),
   169  		}
   170  		fc.files.PushFront(cf)
   171  
   172  		if fc.files.Len() >= fc.maxLen {
   173  			fc.files.Remove(fc.files.Back())
   174  		}
   175  	} else {
   176  		fc.files.MoveToFront(e)
   177  	}
   178  
   179  	return cf.Lines[line-1], nil
   180  }
   181  
   182  // Print prints a disassembly of the file to w.
   183  // If filter is non-nil, the disassembly only includes functions with names matching filter.
   184  // If printCode is true, the disassembly includs corresponding source lines.
   185  // The disassembly only includes functions that overlap the range [start, end).
   186  func (d *Disasm) Print(w io.Writer, filter *regexp.Regexp, start, end uint64, printCode bool) {
   187  	if start < d.textStart {
   188  		start = d.textStart
   189  	}
   190  	if end > d.textEnd {
   191  		end = d.textEnd
   192  	}
   193  	printed := false
   194  	bw := bufio.NewWriter(w)
   195  
   196  	var fc *FileCache
   197  	if printCode {
   198  		fc = NewFileCache(8)
   199  	}
   200  
   201  	for _, sym := range d.syms {
   202  		symStart := sym.Addr
   203  		symEnd := sym.Addr + uint64(sym.Size)
   204  		relocs := sym.Relocs
   205  		if sym.Code != 'T' && sym.Code != 't' ||
   206  			symStart < d.textStart ||
   207  			symEnd <= start || end <= symStart ||
   208  			filter != nil && !filter.MatchString(sym.Name) {
   209  			continue
   210  		}
   211  		if printed {
   212  			fmt.Fprintf(bw, "\n")
   213  		}
   214  		printed = true
   215  
   216  		file, _, _ := d.pcln.PCToLine(sym.Addr)
   217  		fmt.Fprintf(bw, "TEXT %s(SB) %s\n", sym.Name, file)
   218  
   219  		tw := tabwriter.NewWriter(bw, 18, 8, 1, '\t', tabwriter.StripEscape)
   220  		if symEnd > end {
   221  			symEnd = end
   222  		}
   223  		code := d.text[:end-d.textStart]
   224  
   225  		var lastFile string
   226  		var lastLine int
   227  
   228  		d.Decode(symStart, symEnd, relocs, func(pc, size uint64, file string, line int, text string) {
   229  			i := pc - d.textStart
   230  
   231  			if printCode {
   232  				if file != lastFile || line != lastLine {
   233  					if srcLine, err := fc.Line(file, line); err == nil {
   234  						fmt.Fprintf(tw, "%s%s%s\n", []byte{tabwriter.Escape}, srcLine, []byte{tabwriter.Escape})
   235  					}
   236  
   237  					lastFile, lastLine = file, line
   238  				}
   239  
   240  				fmt.Fprintf(tw, "  %#x\t", pc)
   241  			} else {
   242  				fmt.Fprintf(tw, "  %s:%d\t%#x\t", base(file), line, pc)
   243  			}
   244  
   245  			if size%4 != 0 || d.goarch == "386" || d.goarch == "amd64" {
   246  				// Print instruction as bytes.
   247  				fmt.Fprintf(tw, "%x", code[i:i+size])
   248  			} else {
   249  				// Print instruction as 32-bit words.
   250  				for j := uint64(0); j < size; j += 4 {
   251  					if j > 0 {
   252  						fmt.Fprintf(tw, " ")
   253  					}
   254  					fmt.Fprintf(tw, "%08x", d.byteOrder.Uint32(code[i+j:]))
   255  				}
   256  			}
   257  			fmt.Fprintf(tw, "\t%s\n", text)
   258  		})
   259  		tw.Flush()
   260  	}
   261  	bw.Flush()
   262  }
   263  
   264  // Decode disassembles the text segment range [start, end), calling f for each instruction.
   265  func (d *Disasm) Decode(start, end uint64, relocs []Reloc, f func(pc, size uint64, file string, line int, text string)) {
   266  	if start < d.textStart {
   267  		start = d.textStart
   268  	}
   269  	if end > d.textEnd {
   270  		end = d.textEnd
   271  	}
   272  	code := d.text[:end-d.textStart]
   273  	lookup := d.lookup
   274  	for pc := start; pc < end; {
   275  		i := pc - d.textStart
   276  		text, size := d.disasm(code[i:], pc, lookup, d.byteOrder)
   277  		file, line, _ := d.pcln.PCToLine(pc)
   278  		text += "\t"
   279  		first := true
   280  		for len(relocs) > 0 && relocs[0].Addr < i+uint64(size) {
   281  			if first {
   282  				first = false
   283  			} else {
   284  				text += " "
   285  			}
   286  			text += relocs[0].Stringer.String(pc - start)
   287  			relocs = relocs[1:]
   288  		}
   289  		f(pc, uint64(size), file, line, text)
   290  		pc += uint64(size)
   291  	}
   292  }
   293  
   294  type lookupFunc func(addr uint64) (sym string, base uint64)
   295  type disasmFunc func(code []byte, pc uint64, lookup lookupFunc, ord binary.ByteOrder) (text string, size int)
   296  
   297  func disasm_386(code []byte, pc uint64, lookup lookupFunc, _ binary.ByteOrder) (string, int) {
   298  	return disasm_x86(code, pc, lookup, 32)
   299  }
   300  
   301  func disasm_amd64(code []byte, pc uint64, lookup lookupFunc, _ binary.ByteOrder) (string, int) {
   302  	return disasm_x86(code, pc, lookup, 64)
   303  }
   304  
   305  func disasm_x86(code []byte, pc uint64, lookup lookupFunc, arch int) (string, int) {
   306  	inst, err := x86asm.Decode(code, 64)
   307  	var text string
   308  	size := inst.Len
   309  	if err != nil || size == 0 || inst.Op == 0 {
   310  		size = 1
   311  		text = "?"
   312  	} else {
   313  		text = x86asm.GoSyntax(inst, pc, lookup)
   314  	}
   315  	return text, size
   316  }
   317  
   318  type textReader struct {
   319  	code []byte
   320  	pc   uint64
   321  }
   322  
   323  func (r textReader) ReadAt(data []byte, off int64) (n int, err error) {
   324  	if off < 0 || uint64(off) < r.pc {
   325  		return 0, io.EOF
   326  	}
   327  	d := uint64(off) - r.pc
   328  	if d >= uint64(len(r.code)) {
   329  		return 0, io.EOF
   330  	}
   331  	n = copy(data, r.code[d:])
   332  	if n < len(data) {
   333  		err = io.ErrUnexpectedEOF
   334  	}
   335  	return
   336  }
   337  
   338  func disasm_arm(code []byte, pc uint64, lookup lookupFunc, _ binary.ByteOrder) (string, int) {
   339  	inst, err := armasm.Decode(code, armasm.ModeARM)
   340  	var text string
   341  	size := inst.Len
   342  	if err != nil || size == 0 || inst.Op == 0 {
   343  		size = 4
   344  		text = "?"
   345  	} else {
   346  		text = armasm.GoSyntax(inst, pc, lookup, textReader{code, pc})
   347  	}
   348  	return text, size
   349  }
   350  
   351  func disasm_ppc64(code []byte, pc uint64, lookup lookupFunc, byteOrder binary.ByteOrder) (string, int) {
   352  	inst, err := ppc64asm.Decode(code, byteOrder)
   353  	var text string
   354  	size := inst.Len
   355  	if err != nil || size == 0 || inst.Op == 0 {
   356  		size = 4
   357  		text = "?"
   358  	} else {
   359  		text = ppc64asm.GoSyntax(inst, pc, lookup)
   360  	}
   361  	return text, size
   362  }
   363  
   364  var disasms = map[string]disasmFunc{
   365  	"386":     disasm_386,
   366  	"amd64":   disasm_amd64,
   367  	"arm":     disasm_arm,
   368  	"ppc64":   disasm_ppc64,
   369  	"ppc64le": disasm_ppc64,
   370  }
   371  
   372  var byteOrders = map[string]binary.ByteOrder{
   373  	"386":     binary.LittleEndian,
   374  	"amd64":   binary.LittleEndian,
   375  	"arm":     binary.LittleEndian,
   376  	"ppc64":   binary.BigEndian,
   377  	"ppc64le": binary.LittleEndian,
   378  	"s390x":   binary.BigEndian,
   379  }
   380  
   381  type Liner interface {
   382  	// Given a pc, returns the corresponding file, line, and function data.
   383  	// If unknown, returns "",0,nil.
   384  	PCToLine(uint64) (string, int, *gosym.Func)
   385  }