1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
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
49 elfOpen = elf.Open
50 )
51
52
53
54 type binrep struct {
55
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
67
68 fast bool
69 }
70
71
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
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
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
119
120
121 func (bu *Binutils) SetFastSymbolization(fast bool) {
122 bu.update(func(r *binrep) { r.fast = fast })
123 }
124
125
126
127
128
129
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
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
149
150
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
156
157
158
159
160
161
162
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
187
188
189
190
191
192
193
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
207
208
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
227
228
229 return true
230 }
231 if runtime.GOOS == "darwin" {
232
233 return verMajor > 10 || (verMajor == 10 && verPatch >= 1)
234 }
235 return false
236 }
237
238
239
240
241 func isBuObjdump(output string) bool {
242 return strings.Contains(output, "GNU objdump")
243 }
244
245
246
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
258
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
287 func (bu *Binutils) Open(name string, start, limit, offset uint64, relocationSymbol string) (plugin.ObjFile, error) {
288 b := bu.get()
289
290
291
292
293
294 if _, err := os.Stat(name); err != nil {
295
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
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
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
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
361
362
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
394
395
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
448
449
450
451
452
453
454 symbols, err := ef.Symbols()
455 if err != nil && err != elf.ErrNoSymbols {
456 return nil, err
457 }
458
459
460
461
462
463
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
476
477
478
479
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
528
529 type elfMapping struct {
530
531 start, limit, offset uint64
532
533 kernelOffset *uint64
534 }
535
536
537
538
539 func (m *elfMapping) findProgramHeader(ef *elf.File, addr uint64) (*elf.ProgHeader, error) {
540
541
542
543
544
545
546 if m.kernelOffset != nil || m.start >= m.limit || m.limit >= (uint64(1)<<63) {
547
548 return elfexec.FindTextProgHeader(ef), nil
549 }
550
551
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
559
560 if len(phdrs) == 0 {
561 return nil, nil
562 }
563
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
573
574 return elfexec.HeaderForFileOffset(headers, addr-m.start+m.offset)
575 }
576
577
578 type file struct {
579 b *binrep
580 name string
581 buildID string
582
583 baseOnce sync.Once
584 base uint64
585 baseErr error
586 isData bool
587
588 m *elfMapping
589 }
590
591
592
593
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
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
661
662
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
684
685
686
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
720
721
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