Source file src/cmd/vendor/github.com/google/pprof/internal/binutils/binutils.go

     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  	"debug/pe"
    22  	"encoding/binary"
    23  	"errors"
    24  	"fmt"
    25  	"io"
    26  	"os"
    27  	"os/exec"
    28  	"path/filepath"
    29  	"regexp"
    30  	"runtime"
    31  	"strconv"
    32  	"strings"
    33  	"sync"
    34  
    35  	"github.com/google/pprof/internal/elfexec"
    36  	"github.com/google/pprof/internal/plugin"
    37  )
    38  
    39  // A Binutils implements plugin.ObjTool by invoking the GNU binutils.
    40  type Binutils struct {
    41  	mu  sync.Mutex
    42  	rep *binrep
    43  }
    44  
    45  var (
    46  	objdumpLLVMVerRE = regexp.MustCompile(`LLVM version (?:(\d*)\.(\d*)\.(\d*)|.*(trunk).*)`)
    47  
    48  	// Defined for testing
    49  	elfOpen = elf.Open
    50  )
    51  
    52  // binrep is an immutable representation for Binutils.  It is atomically
    53  // replaced on every mutation to provide thread-safe access.
    54  type binrep struct {
    55  	// Commands to invoke.
    56  	llvmSymbolizer      string
    57  	llvmSymbolizerFound bool
    58  	addr2line           string
    59  	addr2lineFound      bool
    60  	nm                  string
    61  	nmFound             bool
    62  	objdump             string
    63  	objdumpFound        bool
    64  	isLLVMObjdump       bool
    65  
    66  	// if fast, perform symbolization using nm (symbol names only),
    67  	// instead of file-line detail from the slower addr2line.
    68  	fast bool
    69  }
    70  
    71  // get returns the current representation for bu, initializing it if necessary.
    72  func (bu *Binutils) get() *binrep {
    73  	bu.mu.Lock()
    74  	r := bu.rep
    75  	if r == nil {
    76  		r = &binrep{}
    77  		initTools(r, "")
    78  		bu.rep = r
    79  	}
    80  	bu.mu.Unlock()
    81  	return r
    82  }
    83  
    84  // update modifies the rep for bu via the supplied function.
    85  func (bu *Binutils) update(fn func(r *binrep)) {
    86  	r := &binrep{}
    87  	bu.mu.Lock()
    88  	defer bu.mu.Unlock()
    89  	if bu.rep == nil {
    90  		initTools(r, "")
    91  	} else {
    92  		*r = *bu.rep
    93  	}
    94  	fn(r)
    95  	bu.rep = r
    96  }
    97  
    98  // String returns string representation of the binutils state for debug logging.
    99  func (bu *Binutils) String() string {
   100  	r := bu.get()
   101  	var llvmSymbolizer, addr2line, nm, objdump string
   102  	if r.llvmSymbolizerFound {
   103  		llvmSymbolizer = r.llvmSymbolizer
   104  	}
   105  	if r.addr2lineFound {
   106  		addr2line = r.addr2line
   107  	}
   108  	if r.nmFound {
   109  		nm = r.nm
   110  	}
   111  	if r.objdumpFound {
   112  		objdump = r.objdump
   113  	}
   114  	return fmt.Sprintf("llvm-symbolizer=%q addr2line=%q nm=%q objdump=%q fast=%t",
   115  		llvmSymbolizer, addr2line, nm, objdump, r.fast)
   116  }
   117  
   118  // SetFastSymbolization sets a toggle that makes binutils use fast
   119  // symbolization (using nm), which is much faster than addr2line but
   120  // provides only symbol name information (no file/line).
   121  func (bu *Binutils) SetFastSymbolization(fast bool) {
   122  	bu.update(func(r *binrep) { r.fast = fast })
   123  }
   124  
   125  // SetTools processes the contents of the tools option. It
   126  // expects a set of entries separated by commas; each entry is a pair
   127  // of the form t:path, where cmd will be used to look only for the
   128  // tool named t. If t is not specified, the path is searched for all
   129  // tools.
   130  func (bu *Binutils) SetTools(config string) {
   131  	bu.update(func(r *binrep) { initTools(r, config) })
   132  }
   133  
   134  func initTools(b *binrep, config string) {
   135  	// paths collect paths per tool; Key "" contains the default.
   136  	paths := make(map[string][]string)
   137  	for _, t := range strings.Split(config, ",") {
   138  		name, path := "", t
   139  		if ct := strings.SplitN(t, ":", 2); len(ct) == 2 {
   140  			name, path = ct[0], ct[1]
   141  		}
   142  		paths[name] = append(paths[name], path)
   143  	}
   144  
   145  	defaultPath := paths[""]
   146  	b.llvmSymbolizer, b.llvmSymbolizerFound = chooseExe([]string{"llvm-symbolizer"}, []string{}, append(paths["llvm-symbolizer"], defaultPath...))
   147  	b.addr2line, b.addr2lineFound = chooseExe([]string{"addr2line"}, []string{"gaddr2line"}, append(paths["addr2line"], defaultPath...))
   148  	// The "-n" option is supported by LLVM since 2011. The output of llvm-nm
   149  	// and GNU nm with "-n" option is interchangeable for our purposes, so we do
   150  	// not need to differrentiate them.
   151  	b.nm, b.nmFound = chooseExe([]string{"llvm-nm", "nm"}, []string{"gnm"}, append(paths["nm"], defaultPath...))
   152  	b.objdump, b.objdumpFound, b.isLLVMObjdump = findObjdump(append(paths["objdump"], defaultPath...))
   153  }
   154  
   155  // findObjdump finds and returns path to preferred objdump binary.
   156  // Order of preference is: llvm-objdump, objdump.
   157  // On MacOS only, also looks for gobjdump with least preference.
   158  // Accepts a list of paths and returns:
   159  // a string with path to the preferred objdump binary if found,
   160  // or an empty string if not found;
   161  // a boolean if any acceptable objdump was found;
   162  // a boolean indicating if it is an LLVM objdump.
   163  func findObjdump(paths []string) (string, bool, bool) {
   164  	objdumpNames := []string{"llvm-objdump", "objdump"}
   165  	if runtime.GOOS == "darwin" {
   166  		objdumpNames = append(objdumpNames, "gobjdump")
   167  	}
   168  
   169  	for _, objdumpName := range objdumpNames {
   170  		if objdump, objdumpFound := findExe(objdumpName, paths); objdumpFound {
   171  			cmdOut, err := exec.Command(objdump, "--version").Output()
   172  			if err != nil {
   173  				continue
   174  			}
   175  			if isLLVMObjdump(string(cmdOut)) {
   176  				return objdump, true, true
   177  			}
   178  			if isBuObjdump(string(cmdOut)) {
   179  				return objdump, true, false
   180  			}
   181  		}
   182  	}
   183  	return "", false, false
   184  }
   185  
   186  // chooseExe finds and returns path to preferred binary. names is a list of
   187  // names to search on both Linux and OSX. osxNames is a list of names specific
   188  // to OSX. names always has a higher priority than osxNames. The order of
   189  // the name within each list decides its priority (e.g. the first name has a
   190  // higher priority than the second name in the list).
   191  //
   192  // It returns a string with path to the binary and a boolean indicating if any
   193  // acceptable binary was found.
   194  func chooseExe(names, osxNames []string, paths []string) (string, bool) {
   195  	if runtime.GOOS == "darwin" {
   196  		names = append(names, osxNames...)
   197  	}
   198  	for _, name := range names {
   199  		if binary, found := findExe(name, paths); found {
   200  			return binary, true
   201  		}
   202  	}
   203  	return "", false
   204  }
   205  
   206  // isLLVMObjdump accepts a string with path to an objdump binary,
   207  // and returns a boolean indicating if the given binary is an LLVM
   208  // objdump binary of an acceptable version.
   209  func isLLVMObjdump(output string) bool {
   210  	fields := objdumpLLVMVerRE.FindStringSubmatch(output)
   211  	if len(fields) != 5 {
   212  		return false
   213  	}
   214  	if fields[4] == "trunk" {
   215  		return true
   216  	}
   217  	verMajor, err := strconv.Atoi(fields[1])
   218  	if err != nil {
   219  		return false
   220  	}
   221  	verPatch, err := strconv.Atoi(fields[3])
   222  	if err != nil {
   223  		return false
   224  	}
   225  	if runtime.GOOS == "linux" && verMajor >= 8 {
   226  		// Ensure LLVM objdump is at least version 8.0 on Linux.
   227  		// Some flags, like --demangle, and double dashes for options are
   228  		// not supported by previous versions.
   229  		return true
   230  	}
   231  	if runtime.GOOS == "darwin" {
   232  		// Ensure LLVM objdump is at least version 10.0.1 on MacOS.
   233  		return verMajor > 10 || (verMajor == 10 && verPatch >= 1)
   234  	}
   235  	return false
   236  }
   237  
   238  // isBuObjdump accepts a string with path to an objdump binary,
   239  // and returns a boolean indicating if the given binary is a GNU
   240  // binutils objdump binary. No version check is performed.
   241  func isBuObjdump(output string) bool {
   242  	return strings.Contains(output, "GNU objdump")
   243  }
   244  
   245  // findExe looks for an executable command on a set of paths.
   246  // If it cannot find it, returns cmd.
   247  func findExe(cmd string, paths []string) (string, bool) {
   248  	for _, p := range paths {
   249  		cp := filepath.Join(p, cmd)
   250  		if c, err := exec.LookPath(cp); err == nil {
   251  			return c, true
   252  		}
   253  	}
   254  	return cmd, false
   255  }
   256  
   257  // Disasm returns the assembly instructions for the specified address range
   258  // of a binary.
   259  func (bu *Binutils) Disasm(file string, start, end uint64, intelSyntax bool) ([]plugin.Inst, error) {
   260  	b := bu.get()
   261  	if !b.objdumpFound {
   262  		return nil, errors.New("cannot disasm: no objdump tool available")
   263  	}
   264  	args := []string{"--disassemble", "--demangle", "--no-show-raw-insn",
   265  		"--line-numbers", fmt.Sprintf("--start-address=%#x", start),
   266  		fmt.Sprintf("--stop-address=%#x", end)}
   267  
   268  	if intelSyntax {
   269  		if b.isLLVMObjdump {
   270  			args = append(args, "--x86-asm-syntax=intel")
   271  		} else {
   272  			args = append(args, "-M", "intel")
   273  		}
   274  	}
   275  
   276  	args = append(args, file)
   277  	cmd := exec.Command(b.objdump, args...)
   278  	out, err := cmd.Output()
   279  	if err != nil {
   280  		return nil, fmt.Errorf("%v: %v", cmd.Args, err)
   281  	}
   282  
   283  	return disassemble(out)
   284  }
   285  
   286  // Open satisfies the plugin.ObjTool interface.
   287  func (bu *Binutils) Open(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
   288  	b := bu.get()
   289  
   290  	// Make sure file is a supported executable.
   291  	// This uses magic numbers, mainly to provide better error messages but
   292  	// it should also help speed.
   293  
   294  	if _, err := os.Stat(name); err != nil {
   295  		// For testing, do not require file name to exist.
   296  		if strings.Contains(b.addr2line, "testdata/") {
   297  			return &fileAddr2Line{file: file{b: b, name: name}}, nil
   298  		}
   299  		return nil, err
   300  	}
   301  
   302  	// Read the first 4 bytes of the file.
   303  
   304  	f, err := os.Open(name)
   305  	if err != nil {
   306  		return nil, fmt.Errorf("error opening %s: %v", name, err)
   307  	}
   308  	defer f.Close()
   309  
   310  	var header [4]byte
   311  	if _, err = io.ReadFull(f, header[:]); err != nil {
   312  		return nil, fmt.Errorf("error reading magic number from %s: %v", name, err)
   313  	}
   314  
   315  	elfMagic := string(header[:])
   316  
   317  	// Match against supported file types.
   318  	if elfMagic == elf.ELFMAG {
   319  		f, err := b.openELF(name, start, limit, offset)
   320  		if err != nil {
   321  			return nil, fmt.Errorf("error reading ELF file %s: %v", name, err)
   322  		}
   323  		return f, nil
   324  	}
   325  
   326  	// Mach-O magic numbers can be big or little endian.
   327  	machoMagicLittle := binary.LittleEndian.Uint32(header[:])
   328  	machoMagicBig := binary.BigEndian.Uint32(header[:])
   329  
   330  	if machoMagicLittle == macho.Magic32 || machoMagicLittle == macho.Magic64 ||
   331  		machoMagicBig == macho.Magic32 || machoMagicBig == macho.Magic64 {
   332  		f, err := b.openMachO(name, start, limit, offset)
   333  		if err != nil {
   334  			return nil, fmt.Errorf("error reading Mach-O file %s: %v", name, err)
   335  		}
   336  		return f, nil
   337  	}
   338  	if machoMagicLittle == macho.MagicFat || machoMagicBig == macho.MagicFat {
   339  		f, err := b.openFatMachO(name, start, limit, offset)
   340  		if err != nil {
   341  			return nil, fmt.Errorf("error reading fat Mach-O file %s: %v", name, err)
   342  		}
   343  		return f, nil
   344  	}
   345  
   346  	peMagic := string(header[:2])
   347  	if peMagic == "MZ" {
   348  		f, err := b.openPE(name, start, limit, offset)
   349  		if err != nil {
   350  			return nil, fmt.Errorf("error reading PE file %s: %v", name, err)
   351  		}
   352  		return f, nil
   353  	}
   354  
   355  	return nil, fmt.Errorf("unrecognized binary format: %s", name)
   356  }
   357  
   358  func (b *binrep) openMachOCommon(name string, of *macho.File, start, limit, offset uint64) (plugin.ObjFile, error) {
   359  
   360  	// Subtract the load address of the __TEXT section. Usually 0 for shared
   361  	// libraries or 0x100000000 for executables. You can check this value by
   362  	// running `objdump -private-headers <file>`.
   363  
   364  	textSegment := of.Segment("__TEXT")
   365  	if textSegment == nil {
   366  		return nil, fmt.Errorf("could not identify base for %s: no __TEXT segment", name)
   367  	}
   368  	if textSegment.Addr > start {
   369  		return nil, fmt.Errorf("could not identify base for %s: __TEXT segment address (0x%x) > mapping start address (0x%x)",
   370  			name, textSegment.Addr, start)
   371  	}
   372  
   373  	base := start - textSegment.Addr
   374  
   375  	if b.fast || (!b.addr2lineFound && !b.llvmSymbolizerFound) {
   376  		return &fileNM{file: file{b: b, name: name, base: base}}, nil
   377  	}
   378  	return &fileAddr2Line{file: file{b: b, name: name, base: base}}, nil
   379  }
   380  
   381  func (b *binrep) openFatMachO(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
   382  	of, err := macho.OpenFat(name)
   383  	if err != nil {
   384  		return nil, fmt.Errorf("error parsing %s: %v", name, err)
   385  	}
   386  	defer of.Close()
   387  
   388  	if len(of.Arches) == 0 {
   389  		return nil, fmt.Errorf("empty fat Mach-O file: %s", name)
   390  	}
   391  
   392  	var arch macho.Cpu
   393  	// Use the host architecture.
   394  	// TODO: This is not ideal because the host architecture may not be the one
   395  	// that was profiled. E.g. an amd64 host can profile a 386 program.
   396  	switch runtime.GOARCH {
   397  	case "386":
   398  		arch = macho.Cpu386
   399  	case "amd64", "amd64p32":
   400  		arch = macho.CpuAmd64
   401  	case "arm", "armbe", "arm64", "arm64be":
   402  		arch = macho.CpuArm
   403  	case "ppc":
   404  		arch = macho.CpuPpc
   405  	case "ppc64", "ppc64le":
   406  		arch = macho.CpuPpc64
   407  	default:
   408  		return nil, fmt.Errorf("unsupported host architecture for %s: %s", name, runtime.GOARCH)
   409  	}
   410  	for i := range of.Arches {
   411  		if of.Arches[i].Cpu == arch {
   412  			return b.openMachOCommon(name, of.Arches[i].File, start, limit, offset)
   413  		}
   414  	}
   415  	return nil, fmt.Errorf("architecture not found in %s: %s", name, runtime.GOARCH)
   416  }
   417  
   418  func (b *binrep) openMachO(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
   419  	of, err := macho.Open(name)
   420  	if err != nil {
   421  		return nil, fmt.Errorf("error parsing %s: %v", name, err)
   422  	}
   423  	defer of.Close()
   424  
   425  	return b.openMachOCommon(name, of, start, limit, offset)
   426  }
   427  
   428  func (b *binrep) openELF(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
   429  	ef, err := elfOpen(name)
   430  	if err != nil {
   431  		return nil, fmt.Errorf("error parsing %s: %v", name, err)
   432  	}
   433  	defer ef.Close()
   434  
   435  	buildID := ""
   436  	if f, err := os.Open(name); err == nil {
   437  		if id, err := elfexec.GetBuildID(f); err == nil {
   438  			buildID = fmt.Sprintf("%x", id)
   439  		}
   440  	}
   441  
   442  	var (
   443  		stextOffset *uint64
   444  		pageAligned = func(addr uint64) bool { return addr%4096 == 0 }
   445  	)
   446  	if strings.Contains(name, "vmlinux") || !pageAligned(start) || !pageAligned(limit) || !pageAligned(offset) {
   447  		// Reading all Symbols is expensive, and we only rarely need it so
   448  		// we don't want to do it every time. But if _stext happens to be
   449  		// page-aligned but isn't the same as Vaddr, we would symbolize
   450  		// wrong. So if the name the addresses aren't page aligned, or if
   451  		// the name is "vmlinux" we read _stext. We can be wrong if: (1)
   452  		// someone passes a kernel path that doesn't contain "vmlinux" AND
   453  		// (2) _stext is page-aligned AND (3) _stext is not at Vaddr
   454  		symbols, err := ef.Symbols()
   455  		if err != nil && err != elf.ErrNoSymbols {
   456  			return nil, err
   457  		}
   458  		for _, s := range symbols {
   459  			if s.Name == "_stext" {
   460  				// The kernel may use _stext as the mapping start address.
   461  				stextOffset = &s.Value
   462  				break
   463  			}
   464  		}
   465  	}
   466  
   467  	// Check that we can compute a base for the binary. This may not be the
   468  	// correct base value, so we don't save it. We delay computing the actual base
   469  	// value until we have a sample address for this mapping, so that we can
   470  	// correctly identify the associated program segment that is needed to compute
   471  	// the base.
   472  	if _, err := elfexec.GetBase(&ef.FileHeader, elfexec.FindTextProgHeader(ef), stextOffset, start, limit, offset); err != nil {
   473  		return nil, fmt.Errorf("could not identify base for %s: %v", name, err)
   474  	}
   475  
   476  	if b.fast || (!b.addr2lineFound && !b.llvmSymbolizerFound) {
   477  		return &fileNM{file: file{
   478  			b:       b,
   479  			name:    name,
   480  			buildID: buildID,
   481  			m:       &elfMapping{start: start, limit: limit, offset: offset, stextOffset: stextOffset},
   482  		}}, nil
   483  	}
   484  	return &fileAddr2Line{file: file{
   485  		b:       b,
   486  		name:    name,
   487  		buildID: buildID,
   488  		m:       &elfMapping{start: start, limit: limit, offset: offset, stextOffset: stextOffset},
   489  	}}, nil
   490  }
   491  
   492  func (b *binrep) openPE(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
   493  	pf, err := pe.Open(name)
   494  	if err != nil {
   495  		return nil, fmt.Errorf("error parsing %s: %v", name, err)
   496  	}
   497  	defer pf.Close()
   498  
   499  	var imageBase uint64
   500  	switch h := pf.OptionalHeader.(type) {
   501  	case *pe.OptionalHeader32:
   502  		imageBase = uint64(h.ImageBase)
   503  	case *pe.OptionalHeader64:
   504  		imageBase = uint64(h.ImageBase)
   505  	default:
   506  		return nil, fmt.Errorf("unknown OptionalHeader %T", pf.OptionalHeader)
   507  	}
   508  
   509  	var base uint64
   510  	if start > 0 {
   511  		base = start - imageBase
   512  	}
   513  	if b.fast || (!b.addr2lineFound && !b.llvmSymbolizerFound) {
   514  		return &fileNM{file: file{b: b, name: name, base: base}}, nil
   515  	}
   516  	return &fileAddr2Line{file: file{b: b, name: name, base: base}}, nil
   517  }
   518  
   519  // elfMapping stores the parameters of a runtime mapping that are needed to
   520  // identify the ELF segment associated with a mapping.
   521  type elfMapping struct {
   522  	// Runtime mapping parameters.
   523  	start, limit, offset uint64
   524  	// Offset of _stext symbol. Only defined for kernel images, nil otherwise.
   525  	stextOffset *uint64
   526  }
   527  
   528  // findProgramHeader returns the program segment that matches the current
   529  // mapping and the given address, or an error if it cannot find a unique program
   530  // header.
   531  func (m *elfMapping) findProgramHeader(ef *elf.File, addr uint64) (*elf.ProgHeader, error) {
   532  	// For user space executables, we try to find the actual program segment that
   533  	// is associated with the given mapping. Skip this search if limit <= start.
   534  	// We cannot use just a check on the start address of the mapping to tell if
   535  	// it's a kernel / .ko module mapping, because with quipper address remapping
   536  	// enabled, the address would be in the lower half of the address space.
   537  
   538  	if m.stextOffset != nil || m.start >= m.limit || m.limit >= (uint64(1)<<63) {
   539  		// For the kernel, find the program segment that includes the .text section.
   540  		return elfexec.FindTextProgHeader(ef), nil
   541  	}
   542  
   543  	// Fetch all the loadable segments.
   544  	var phdrs []elf.ProgHeader
   545  	for i := range ef.Progs {
   546  		if ef.Progs[i].Type == elf.PT_LOAD {
   547  			phdrs = append(phdrs, ef.Progs[i].ProgHeader)
   548  		}
   549  	}
   550  	// Some ELF files don't contain any loadable program segments, e.g. .ko
   551  	// kernel modules. It's not an error to have no header in such cases.
   552  	if len(phdrs) == 0 {
   553  		return nil, nil
   554  	}
   555  	// Get all program headers associated with the mapping.
   556  	headers := elfexec.ProgramHeadersForMapping(phdrs, m.offset, m.limit-m.start)
   557  	if len(headers) == 0 {
   558  		return nil, errors.New("no program header matches mapping info")
   559  	}
   560  	if len(headers) == 1 {
   561  		return headers[0], nil
   562  	}
   563  
   564  	// Use the file offset corresponding to the address to symbolize, to narrow
   565  	// down the header.
   566  	return elfexec.HeaderForFileOffset(headers, addr-m.start+m.offset)
   567  }
   568  
   569  // file implements the binutils.ObjFile interface.
   570  type file struct {
   571  	b       *binrep
   572  	name    string
   573  	buildID string
   574  
   575  	baseOnce sync.Once // Ensures the base, baseErr and isData are computed once.
   576  	base     uint64
   577  	baseErr  error // Any eventual error while computing the base.
   578  	isData   bool
   579  	// Mapping information. Relevant only for ELF files, nil otherwise.
   580  	m *elfMapping
   581  }
   582  
   583  // computeBase computes the relocation base for the given binary file only if
   584  // the elfMapping field is set. It populates the base and isData fields and
   585  // returns an error.
   586  func (f *file) computeBase(addr uint64) error {
   587  	if f == nil || f.m == nil {
   588  		return nil
   589  	}
   590  	if addr < f.m.start || addr >= f.m.limit {
   591  		return fmt.Errorf("specified address %x is outside the mapping range [%x, %x] for file %q", addr, f.m.start, f.m.limit, f.name)
   592  	}
   593  	ef, err := elfOpen(f.name)
   594  	if err != nil {
   595  		return fmt.Errorf("error parsing %s: %v", f.name, err)
   596  	}
   597  	defer ef.Close()
   598  
   599  	ph, err := f.m.findProgramHeader(ef, addr)
   600  	if err != nil {
   601  		return fmt.Errorf("failed to find program header for file %q, ELF mapping %#v, address %x: %v", f.name, *f.m, addr, err)
   602  	}
   603  
   604  	base, err := elfexec.GetBase(&ef.FileHeader, ph, f.m.stextOffset, f.m.start, f.m.limit, f.m.offset)
   605  	if err != nil {
   606  		return err
   607  	}
   608  	f.base = base
   609  	f.isData = ph != nil && ph.Flags&elf.PF_X == 0
   610  	return nil
   611  }
   612  
   613  func (f *file) Name() string {
   614  	return f.name
   615  }
   616  
   617  func (f *file) ObjAddr(addr uint64) (uint64, error) {
   618  	f.baseOnce.Do(func() { f.baseErr = f.computeBase(addr) })
   619  	if f.baseErr != nil {
   620  		return 0, f.baseErr
   621  	}
   622  	return addr - f.base, nil
   623  }
   624  
   625  func (f *file) BuildID() string {
   626  	return f.buildID
   627  }
   628  
   629  func (f *file) SourceLine(addr uint64) ([]plugin.Frame, error) {
   630  	f.baseOnce.Do(func() { f.baseErr = f.computeBase(addr) })
   631  	if f.baseErr != nil {
   632  		return nil, f.baseErr
   633  	}
   634  	return nil, nil
   635  }
   636  
   637  func (f *file) Close() error {
   638  	return nil
   639  }
   640  
   641  func (f *file) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym, error) {
   642  	// Get from nm a list of symbols sorted by address.
   643  	cmd := exec.Command(f.b.nm, "-n", f.name)
   644  	out, err := cmd.Output()
   645  	if err != nil {
   646  		return nil, fmt.Errorf("%v: %v", cmd.Args, err)
   647  	}
   648  
   649  	return findSymbols(out, f.name, r, addr)
   650  }
   651  
   652  // fileNM implements the binutils.ObjFile interface, using 'nm' to map
   653  // addresses to symbols (without file/line number information). It is
   654  // faster than fileAddr2Line.
   655  type fileNM struct {
   656  	file
   657  	addr2linernm *addr2LinerNM
   658  }
   659  
   660  func (f *fileNM) SourceLine(addr uint64) ([]plugin.Frame, error) {
   661  	f.baseOnce.Do(func() { f.baseErr = f.computeBase(addr) })
   662  	if f.baseErr != nil {
   663  		return nil, f.baseErr
   664  	}
   665  	if f.addr2linernm == nil {
   666  		addr2liner, err := newAddr2LinerNM(f.b.nm, f.name, f.base)
   667  		if err != nil {
   668  			return nil, err
   669  		}
   670  		f.addr2linernm = addr2liner
   671  	}
   672  	return f.addr2linernm.addrInfo(addr)
   673  }
   674  
   675  // fileAddr2Line implements the binutils.ObjFile interface, using
   676  // llvm-symbolizer, if that's available, or addr2line to map addresses to
   677  // symbols (with file/line number information). It can be slow for large
   678  // binaries with debug information.
   679  type fileAddr2Line struct {
   680  	once sync.Once
   681  	file
   682  	addr2liner     *addr2Liner
   683  	llvmSymbolizer *llvmSymbolizer
   684  	isData         bool
   685  }
   686  
   687  func (f *fileAddr2Line) SourceLine(addr uint64) ([]plugin.Frame, error) {
   688  	f.baseOnce.Do(func() { f.baseErr = f.computeBase(addr) })
   689  	if f.baseErr != nil {
   690  		return nil, f.baseErr
   691  	}
   692  	f.once.Do(f.init)
   693  	if f.llvmSymbolizer != nil {
   694  		return f.llvmSymbolizer.addrInfo(addr)
   695  	}
   696  	if f.addr2liner != nil {
   697  		return f.addr2liner.addrInfo(addr)
   698  	}
   699  	return nil, fmt.Errorf("could not find local addr2liner")
   700  }
   701  
   702  func (f *fileAddr2Line) init() {
   703  	if llvmSymbolizer, err := newLLVMSymbolizer(f.b.llvmSymbolizer, f.name, f.base, f.isData); err == nil {
   704  		f.llvmSymbolizer = llvmSymbolizer
   705  		return
   706  	}
   707  
   708  	if addr2liner, err := newAddr2Liner(f.b.addr2line, f.name, f.base); err == nil {
   709  		f.addr2liner = addr2liner
   710  
   711  		// When addr2line encounters some gcc compiled binaries, it
   712  		// drops interesting parts of names in anonymous namespaces.
   713  		// Fallback to NM for better function names.
   714  		if nm, err := newAddr2LinerNM(f.b.nm, f.name, f.base); err == nil {
   715  			f.addr2liner.nm = nm
   716  		}
   717  	}
   718  }
   719  
   720  func (f *fileAddr2Line) Close() error {
   721  	if f.llvmSymbolizer != nil {
   722  		f.llvmSymbolizer.rw.close()
   723  		f.llvmSymbolizer = nil
   724  	}
   725  	if f.addr2liner != nil {
   726  		f.addr2liner.rw.close()
   727  		f.addr2liner = nil
   728  	}
   729  	return nil
   730  }
   731  

View as plain text