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.
16package binutils
17
18import (
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.
40type Binutils struct {
41	mu  sync.Mutex
42	rep *binrep
43}
44
45var (
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.
54type 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.
72func (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.
85func (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.
99func (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).
121func (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.
130func (bu *Binutils) SetTools(config string) {
131	bu.update(func(r *binrep) { initTools(r, config) })
132}
133
134func 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.
163func 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.
194func 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.
209func 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.
241func 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.
247func 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.
259func (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.
287func (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
358func (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
381func (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
418func (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
428func (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
498func (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.
527type 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.
537func (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.
576type 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.
592func (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
619func (f *file) Name() string {
620	return f.name
621}
622
623func (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
631func (f *file) BuildID() string {
632	return f.buildID
633}
634
635func (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
643func (f *file) Close() error {
644	return nil
645}
646
647func (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.
661type fileNM struct {
662	file
663	addr2linernm *addr2LinerNM
664}
665
666func (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.
685type fileAddr2Line struct {
686	once sync.Once
687	file
688	addr2liner     *addr2Liner
689	llvmSymbolizer *llvmSymbolizer
690	isData         bool
691}
692
693func (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
708func (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
726func (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