golang.org/toolchain@v0.0.1-go1.9rc2.windows-amd64/src/cmd/vendor/github.com/google/pprof/internal/binutils/binutils.go (about)

     1  // Copyright 2014 Google Inc. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package binutils provides access to the GNU binutils.
    16  package binutils
    17  
    18  import (
    19  	"debug/elf"
    20  	"debug/macho"
    21  	"fmt"
    22  	"os"
    23  	"os/exec"
    24  	"path/filepath"
    25  	"regexp"
    26  	"strings"
    27  
    28  	"github.com/google/pprof/internal/elfexec"
    29  	"github.com/google/pprof/internal/plugin"
    30  )
    31  
    32  // A Binutils implements plugin.ObjTool by invoking the GNU binutils.
    33  // SetConfig must be called before any of the other methods.
    34  type Binutils struct {
    35  	// Commands to invoke.
    36  	llvmSymbolizer      string
    37  	llvmSymbolizerFound bool
    38  	addr2line           string
    39  	addr2lineFound      bool
    40  	nm                  string
    41  	nmFound             bool
    42  	objdump             string
    43  	objdumpFound        bool
    44  
    45  	// if fast, perform symbolization using nm (symbol names only),
    46  	// instead of file-line detail from the slower addr2line.
    47  	fast bool
    48  }
    49  
    50  // SetFastSymbolization sets a toggle that makes binutils use fast
    51  // symbolization (using nm), which is much faster than addr2line but
    52  // provides only symbol name information (no file/line).
    53  func (b *Binutils) SetFastSymbolization(fast bool) {
    54  	b.fast = fast
    55  }
    56  
    57  // SetTools processes the contents of the tools option. It
    58  // expects a set of entries separated by commas; each entry is a pair
    59  // of the form t:path, where cmd will be used to look only for the
    60  // tool named t. If t is not specified, the path is searched for all
    61  // tools.
    62  func (b *Binutils) SetTools(config string) {
    63  	// paths collect paths per tool; Key "" contains the default.
    64  	paths := make(map[string][]string)
    65  	for _, t := range strings.Split(config, ",") {
    66  		name, path := "", t
    67  		if ct := strings.SplitN(t, ":", 2); len(ct) == 2 {
    68  			name, path = ct[0], ct[1]
    69  		}
    70  		paths[name] = append(paths[name], path)
    71  	}
    72  
    73  	defaultPath := paths[""]
    74  	b.llvmSymbolizer, b.llvmSymbolizerFound = findExe("llvm-symbolizer", append(paths["llvm-symbolizer"], defaultPath...))
    75  	b.addr2line, b.addr2lineFound = findExe("addr2line", append(paths["addr2line"], defaultPath...))
    76  	b.nm, b.nmFound = findExe("nm", append(paths["nm"], defaultPath...))
    77  	b.objdump, b.objdumpFound = findExe("objdump", append(paths["objdump"], defaultPath...))
    78  }
    79  
    80  // findExe looks for an executable command on a set of paths.
    81  // If it cannot find it, returns cmd.
    82  func findExe(cmd string, paths []string) (string, bool) {
    83  	for _, p := range paths {
    84  		cp := filepath.Join(p, cmd)
    85  		if c, err := exec.LookPath(cp); err == nil {
    86  			return c, true
    87  		}
    88  	}
    89  	return cmd, false
    90  }
    91  
    92  // Disasm returns the assembly instructions for the specified address range
    93  // of a binary.
    94  func (b *Binutils) Disasm(file string, start, end uint64) ([]plugin.Inst, error) {
    95  	if b.addr2line == "" {
    96  		// Update the command invocations if not initialized.
    97  		b.SetTools("")
    98  	}
    99  	cmd := exec.Command(b.objdump, "-d", "-C", "--no-show-raw-insn", "-l",
   100  		fmt.Sprintf("--start-address=%#x", start),
   101  		fmt.Sprintf("--stop-address=%#x", end),
   102  		file)
   103  	out, err := cmd.Output()
   104  	if err != nil {
   105  		return nil, fmt.Errorf("%v: %v", cmd.Args, err)
   106  	}
   107  
   108  	return disassemble(out)
   109  }
   110  
   111  // Open satisfies the plugin.ObjTool interface.
   112  func (b *Binutils) Open(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
   113  	if b.addr2line == "" {
   114  		// Update the command invocations if not initialized.
   115  		b.SetTools("")
   116  	}
   117  
   118  	// Make sure file is a supported executable.
   119  	// The pprof driver uses Open to sniff the difference
   120  	// between an executable and a profile.
   121  	// For now, only ELF is supported.
   122  	// Could read the first few bytes of the file and
   123  	// use a table of prefixes if we need to support other
   124  	// systems at some point.
   125  
   126  	if _, err := os.Stat(name); err != nil {
   127  		// For testing, do not require file name to exist.
   128  		if strings.Contains(b.addr2line, "testdata/") {
   129  			return &fileAddr2Line{file: file{b: b, name: name}}, nil
   130  		}
   131  		return nil, err
   132  	}
   133  
   134  	if f, err := b.openELF(name, start, limit, offset); err == nil {
   135  		return f, nil
   136  	}
   137  	if f, err := b.openMachO(name, start, limit, offset); err == nil {
   138  		return f, nil
   139  	}
   140  	return nil, fmt.Errorf("unrecognized binary: %s", name)
   141  }
   142  
   143  func (b *Binutils) openMachO(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
   144  	of, err := macho.Open(name)
   145  	if err != nil {
   146  		return nil, fmt.Errorf("Parsing %s: %v", name, err)
   147  	}
   148  	defer of.Close()
   149  
   150  	if b.fast || (!b.addr2lineFound && !b.llvmSymbolizerFound) {
   151  		return &fileNM{file: file{b: b, name: name}}, nil
   152  	}
   153  	return &fileAddr2Line{file: file{b: b, name: name}}, nil
   154  }
   155  
   156  func (b *Binutils) openELF(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
   157  	ef, err := elf.Open(name)
   158  	if err != nil {
   159  		return nil, fmt.Errorf("Parsing %s: %v", name, err)
   160  	}
   161  	defer ef.Close()
   162  
   163  	var stextOffset *uint64
   164  	var pageAligned = func(addr uint64) bool { return addr%4096 == 0 }
   165  	if strings.Contains(name, "vmlinux") || !pageAligned(start) || !pageAligned(limit) || !pageAligned(offset) {
   166  		// Reading all Symbols is expensive, and we only rarely need it so
   167  		// we don't want to do it every time. But if _stext happens to be
   168  		// page-aligned but isn't the same as Vaddr, we would symbolize
   169  		// wrong. So if the name the addresses aren't page aligned, or if
   170  		// the name is "vmlinux" we read _stext. We can be wrong if: (1)
   171  		// someone passes a kernel path that doesn't contain "vmlinux" AND
   172  		// (2) _stext is page-aligned AND (3) _stext is not at Vaddr
   173  		symbols, err := ef.Symbols()
   174  		if err != nil {
   175  			return nil, err
   176  		}
   177  		for _, s := range symbols {
   178  			if s.Name == "_stext" {
   179  				// The kernel may use _stext as the mapping start address.
   180  				stextOffset = &s.Value
   181  				break
   182  			}
   183  		}
   184  	}
   185  
   186  	base, err := elfexec.GetBase(&ef.FileHeader, nil, stextOffset, start, limit, offset)
   187  	if err != nil {
   188  		return nil, fmt.Errorf("Could not identify base for %s: %v", name, err)
   189  	}
   190  
   191  	buildID := ""
   192  	if f, err := os.Open(name); err == nil {
   193  		if id, err := elfexec.GetBuildID(f); err == nil {
   194  			buildID = fmt.Sprintf("%x", id)
   195  		}
   196  	}
   197  	if b.fast || (!b.addr2lineFound && !b.llvmSymbolizerFound) {
   198  		return &fileNM{file: file{b, name, base, buildID}}, nil
   199  	}
   200  	return &fileAddr2Line{file: file{b, name, base, buildID}}, nil
   201  }
   202  
   203  // file implements the binutils.ObjFile interface.
   204  type file struct {
   205  	b       *Binutils
   206  	name    string
   207  	base    uint64
   208  	buildID string
   209  }
   210  
   211  func (f *file) Name() string {
   212  	return f.name
   213  }
   214  
   215  func (f *file) Base() uint64 {
   216  	return f.base
   217  }
   218  
   219  func (f *file) BuildID() string {
   220  	return f.buildID
   221  }
   222  
   223  func (f *file) SourceLine(addr uint64) ([]plugin.Frame, error) {
   224  	return []plugin.Frame{}, nil
   225  }
   226  
   227  func (f *file) Close() error {
   228  	return nil
   229  }
   230  
   231  func (f *file) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym, error) {
   232  	// Get from nm a list of symbols sorted by address.
   233  	cmd := exec.Command(f.b.nm, "-n", f.name)
   234  	out, err := cmd.Output()
   235  	if err != nil {
   236  		return nil, fmt.Errorf("%v: %v", cmd.Args, err)
   237  	}
   238  
   239  	return findSymbols(out, f.name, r, addr)
   240  }
   241  
   242  // fileNM implements the binutils.ObjFile interface, using 'nm' to map
   243  // addresses to symbols (without file/line number information). It is
   244  // faster than fileAddr2Line.
   245  type fileNM struct {
   246  	file
   247  	addr2linernm *addr2LinerNM
   248  }
   249  
   250  func (f *fileNM) SourceLine(addr uint64) ([]plugin.Frame, error) {
   251  	if f.addr2linernm == nil {
   252  		addr2liner, err := newAddr2LinerNM(f.b.nm, f.name, f.base)
   253  		if err != nil {
   254  			return nil, err
   255  		}
   256  		f.addr2linernm = addr2liner
   257  	}
   258  	return f.addr2linernm.addrInfo(addr)
   259  }
   260  
   261  // fileAddr2Line implements the binutils.ObjFile interface, using
   262  // 'addr2line' to map addresses to symbols (with file/line number
   263  // information). It can be slow for large binaries with debug
   264  // information.
   265  type fileAddr2Line struct {
   266  	file
   267  	addr2liner     *addr2Liner
   268  	llvmSymbolizer *llvmSymbolizer
   269  }
   270  
   271  func (f *fileAddr2Line) SourceLine(addr uint64) ([]plugin.Frame, error) {
   272  	if f.llvmSymbolizer != nil {
   273  		return f.llvmSymbolizer.addrInfo(addr)
   274  	}
   275  	if f.addr2liner != nil {
   276  		return f.addr2liner.addrInfo(addr)
   277  	}
   278  
   279  	if llvmSymbolizer, err := newLLVMSymbolizer(f.b.llvmSymbolizer, f.name, f.base); err == nil {
   280  		f.llvmSymbolizer = llvmSymbolizer
   281  		return f.llvmSymbolizer.addrInfo(addr)
   282  	}
   283  
   284  	if addr2liner, err := newAddr2Liner(f.b.addr2line, f.name, f.base); err == nil {
   285  		f.addr2liner = addr2liner
   286  
   287  		// When addr2line encounters some gcc compiled binaries, it
   288  		// drops interesting parts of names in anonymous namespaces.
   289  		// Fallback to NM for better function names.
   290  		if nm, err := newAddr2LinerNM(f.b.nm, f.name, f.base); err == nil {
   291  			f.addr2liner.nm = nm
   292  		}
   293  		return f.addr2liner.addrInfo(addr)
   294  	}
   295  
   296  	return nil, fmt.Errorf("could not find local addr2liner")
   297  }
   298  
   299  func (f *fileAddr2Line) Close() error {
   300  	if f.addr2liner != nil {
   301  		f.addr2liner.rw.close()
   302  		f.addr2liner = nil
   303  	}
   304  	return nil
   305  }