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, relocationSymbol string) (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, relocationSymbol)
   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, relocationSymbol string) (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  		kernelOffset *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  
   459  		// The kernel relocation symbol (the mapping start address) can be either
   460  		// _text or _stext. When profiles are generated by `perf`, which one was used is
   461  		// distinguished by the mapping name for the kernel image:
   462  		// '[kernel.kallsyms]_text' or '[kernel.kallsyms]_stext', respectively. If we haven't
   463  		// been able to parse it from the mapping, we default to _stext.
   464  		if relocationSymbol == "" {
   465  			relocationSymbol = "_stext"
   466  		}
   467  		for _, s := range symbols {
   468  			if s.Name == relocationSymbol {
   469  				kernelOffset = &s.Value
   470  				break
   471  			}
   472  		}
   473  	}
   474  
   475  	// Check that we can compute a base for the binary. This may not be the
   476  	// correct base value, so we don't save it. We delay computing the actual base
   477  	// value until we have a sample address for this mapping, so that we can
   478  	// correctly identify the associated program segment that is needed to compute
   479  	// the base.
   480  	if _, err := elfexec.GetBase(&ef.FileHeader, elfexec.FindTextProgHeader(ef), kernelOffset, start, limit, offset); err != nil {
   481  		return nil, fmt.Errorf("could not identify base for %s: %v", name, err)
   482  	}
   483  
   484  	if b.fast || (!b.addr2lineFound && !b.llvmSymbolizerFound) {
   485  		return &fileNM{file: file{
   486  			b:       b,
   487  			name:    name,
   488  			buildID: buildID,
   489  			m:       &elfMapping{start: start, limit: limit, offset: offset, kernelOffset: kernelOffset},
   490  		}}, nil
   491  	}
   492  	return &fileAddr2Line{file: file{
   493  		b:       b,
   494  		name:    name,
   495  		buildID: buildID,
   496  		m:       &elfMapping{start: start, limit: limit, offset: offset, kernelOffset: kernelOffset},
   497  	}}, nil
   498  }
   499  
   500  func (b *binrep) openPE(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
   501  	pf, err := pe.Open(name)
   502  	if err != nil {
   503  		return nil, fmt.Errorf("error parsing %s: %v", name, err)
   504  	}
   505  	defer pf.Close()
   506  
   507  	var imageBase uint64
   508  	switch h := pf.OptionalHeader.(type) {
   509  	case *pe.OptionalHeader32:
   510  		imageBase = uint64(h.ImageBase)
   511  	case *pe.OptionalHeader64:
   512  		imageBase = uint64(h.ImageBase)
   513  	default:
   514  		return nil, fmt.Errorf("unknown OptionalHeader %T", pf.OptionalHeader)
   515  	}
   516  
   517  	var base uint64
   518  	if start > 0 {
   519  		base = start - imageBase
   520  	}
   521  	if b.fast || (!b.addr2lineFound && !b.llvmSymbolizerFound) {
   522  		return &fileNM{file: file{b: b, name: name, base: base}}, nil
   523  	}
   524  	return &fileAddr2Line{file: file{b: b, name: name, base: base}}, nil
   525  }
   526  
   527  // elfMapping stores the parameters of a runtime mapping that are needed to
   528  // identify the ELF segment associated with a mapping.
   529  type elfMapping struct {
   530  	// Runtime mapping parameters.
   531  	start, limit, offset uint64
   532  	// Offset of kernel relocation symbol. Only defined for kernel images, nil otherwise.
   533  	kernelOffset *uint64
   534  }
   535  
   536  // findProgramHeader returns the program segment that matches the current
   537  // mapping and the given address, or an error if it cannot find a unique program
   538  // header.
   539  func (m *elfMapping) findProgramHeader(ef *elf.File, addr uint64) (*elf.ProgHeader, error) {
   540  	// For user space executables, we try to find the actual program segment that
   541  	// is associated with the given mapping. Skip this search if limit <= start.
   542  	// We cannot use just a check on the start address of the mapping to tell if
   543  	// it's a kernel / .ko module mapping, because with quipper address remapping
   544  	// enabled, the address would be in the lower half of the address space.
   545  
   546  	if m.kernelOffset != nil || m.start >= m.limit || m.limit >= (uint64(1)<<63) {
   547  		// For the kernel, find the program segment that includes the .text section.
   548  		return elfexec.FindTextProgHeader(ef), nil
   549  	}
   550  
   551  	// Fetch all the loadable segments.
   552  	var phdrs []elf.ProgHeader
   553  	for i := range ef.Progs {
   554  		if ef.Progs[i].Type == elf.PT_LOAD {
   555  			phdrs = append(phdrs, ef.Progs[i].ProgHeader)
   556  		}
   557  	}
   558  	// Some ELF files don't contain any loadable program segments, e.g. .ko
   559  	// kernel modules. It's not an error to have no header in such cases.
   560  	if len(phdrs) == 0 {
   561  		return nil, nil
   562  	}
   563  	// Get all program headers associated with the mapping.
   564  	headers := elfexec.ProgramHeadersForMapping(phdrs, m.offset, m.limit-m.start)
   565  	if len(headers) == 0 {
   566  		return nil, errors.New("no program header matches mapping info")
   567  	}
   568  	if len(headers) == 1 {
   569  		return headers[0], nil
   570  	}
   571  
   572  	// Use the file offset corresponding to the address to symbolize, to narrow
   573  	// down the header.
   574  	return elfexec.HeaderForFileOffset(headers, addr-m.start+m.offset)
   575  }
   576  
   577  // file implements the binutils.ObjFile interface.
   578  type file struct {
   579  	b       *binrep
   580  	name    string
   581  	buildID string
   582  
   583  	baseOnce sync.Once // Ensures the base, baseErr and isData are computed once.
   584  	base     uint64
   585  	baseErr  error // Any eventual error while computing the base.
   586  	isData   bool
   587  	// Mapping information. Relevant only for ELF files, nil otherwise.
   588  	m *elfMapping
   589  }
   590  
   591  // computeBase computes the relocation base for the given binary file only if
   592  // the elfMapping field is set. It populates the base and isData fields and
   593  // returns an error.
   594  func (f *file) computeBase(addr uint64) error {
   595  	if f == nil || f.m == nil {
   596  		return nil
   597  	}
   598  	if addr < f.m.start || addr >= f.m.limit {
   599  		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)
   600  	}
   601  	ef, err := elfOpen(f.name)
   602  	if err != nil {
   603  		return fmt.Errorf("error parsing %s: %v", f.name, err)
   604  	}
   605  	defer ef.Close()
   606  
   607  	ph, err := f.m.findProgramHeader(ef, addr)
   608  	if err != nil {
   609  		return fmt.Errorf("failed to find program header for file %q, ELF mapping %#v, address %x: %v", f.name, *f.m, addr, err)
   610  	}
   611  
   612  	base, err := elfexec.GetBase(&ef.FileHeader, ph, f.m.kernelOffset, f.m.start, f.m.limit, f.m.offset)
   613  	if err != nil {
   614  		return err
   615  	}
   616  	f.base = base
   617  	f.isData = ph != nil && ph.Flags&elf.PF_X == 0
   618  	return nil
   619  }
   620  
   621  func (f *file) Name() string {
   622  	return f.name
   623  }
   624  
   625  func (f *file) ObjAddr(addr uint64) (uint64, error) {
   626  	f.baseOnce.Do(func() { f.baseErr = f.computeBase(addr) })
   627  	if f.baseErr != nil {
   628  		return 0, f.baseErr
   629  	}
   630  	return addr - f.base, nil
   631  }
   632  
   633  func (f *file) BuildID() string {
   634  	return f.buildID
   635  }
   636  
   637  func (f *file) SourceLine(addr uint64) ([]plugin.Frame, error) {
   638  	f.baseOnce.Do(func() { f.baseErr = f.computeBase(addr) })
   639  	if f.baseErr != nil {
   640  		return nil, f.baseErr
   641  	}
   642  	return nil, nil
   643  }
   644  
   645  func (f *file) Close() error {
   646  	return nil
   647  }
   648  
   649  func (f *file) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym, error) {
   650  	// Get from nm a list of symbols sorted by address.
   651  	cmd := exec.Command(f.b.nm, "-n", f.name)
   652  	out, err := cmd.Output()
   653  	if err != nil {
   654  		return nil, fmt.Errorf("%v: %v", cmd.Args, err)
   655  	}
   656  
   657  	return findSymbols(out, f.name, r, addr)
   658  }
   659  
   660  // fileNM implements the binutils.ObjFile interface, using 'nm' to map
   661  // addresses to symbols (without file/line number information). It is
   662  // faster than fileAddr2Line.
   663  type fileNM struct {
   664  	file
   665  	addr2linernm *addr2LinerNM
   666  }
   667  
   668  func (f *fileNM) SourceLine(addr uint64) ([]plugin.Frame, error) {
   669  	f.baseOnce.Do(func() { f.baseErr = f.computeBase(addr) })
   670  	if f.baseErr != nil {
   671  		return nil, f.baseErr
   672  	}
   673  	if f.addr2linernm == nil {
   674  		addr2liner, err := newAddr2LinerNM(f.b.nm, f.name, f.base)
   675  		if err != nil {
   676  			return nil, err
   677  		}
   678  		f.addr2linernm = addr2liner
   679  	}
   680  	return f.addr2linernm.addrInfo(addr)
   681  }
   682  
   683  // fileAddr2Line implements the binutils.ObjFile interface, using
   684  // llvm-symbolizer, if that's available, or addr2line to map addresses to
   685  // symbols (with file/line number information). It can be slow for large
   686  // binaries with debug information.
   687  type fileAddr2Line struct {
   688  	once sync.Once
   689  	file
   690  	addr2liner     *addr2Liner
   691  	llvmSymbolizer *llvmSymbolizer
   692  	isData         bool
   693  }
   694  
   695  func (f *fileAddr2Line) SourceLine(addr uint64) ([]plugin.Frame, error) {
   696  	f.baseOnce.Do(func() { f.baseErr = f.computeBase(addr) })
   697  	if f.baseErr != nil {
   698  		return nil, f.baseErr
   699  	}
   700  	f.once.Do(f.init)
   701  	if f.llvmSymbolizer != nil {
   702  		return f.llvmSymbolizer.addrInfo(addr)
   703  	}
   704  	if f.addr2liner != nil {
   705  		return f.addr2liner.addrInfo(addr)
   706  	}
   707  	return nil, fmt.Errorf("could not find local addr2liner")
   708  }
   709  
   710  func (f *fileAddr2Line) init() {
   711  	if llvmSymbolizer, err := newLLVMSymbolizer(f.b.llvmSymbolizer, f.name, f.base, f.isData); err == nil {
   712  		f.llvmSymbolizer = llvmSymbolizer
   713  		return
   714  	}
   715  
   716  	if addr2liner, err := newAddr2Liner(f.b.addr2line, f.name, f.base); err == nil {
   717  		f.addr2liner = addr2liner
   718  
   719  		// When addr2line encounters some gcc compiled binaries, it
   720  		// drops interesting parts of names in anonymous namespaces.
   721  		// Fallback to NM for better function names.
   722  		if nm, err := newAddr2LinerNM(f.b.nm, f.name, f.base); err == nil {
   723  			f.addr2liner.nm = nm
   724  		}
   725  	}
   726  }
   727  
   728  func (f *fileAddr2Line) Close() error {
   729  	if f.llvmSymbolizer != nil {
   730  		f.llvmSymbolizer.rw.close()
   731  		f.llvmSymbolizer = nil
   732  	}
   733  	if f.addr2liner != nil {
   734  		f.addr2liner.rw.close()
   735  		f.addr2liner = nil
   736  	}
   737  	return nil
   738  }
   739  

View as plain text