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

View as plain text