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 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
446
447
448
449
450
451
452 symbols, err := ef.Symbols()
453 if err != nil && err != elf.ErrNoSymbols {
454 return nil, err
455 }
456
457
458
459
460
461
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
474
475
476
477
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
526
527 type elfMapping struct {
528
529 start, limit, offset uint64
530
531 kernelOffset *uint64
532 }
533
534
535
536
537 func (m *elfMapping) findProgramHeader(ef *elf.File, addr uint64) (*elf.ProgHeader, error) {
538
539
540
541
542
543
544 if m.kernelOffset != nil || m.start >= m.limit || m.limit >= (uint64(1)<<63) {
545
546 return elfexec.FindTextProgHeader(ef), nil
547 }
548
549
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
557
558 if len(phdrs) == 0 {
559 return nil, nil
560 }
561
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
571
572 return elfexec.HeaderForFileOffset(headers, addr-m.start+m.offset)
573 }
574
575
576 type file struct {
577 b *binrep
578 name string
579 buildID string
580
581 baseOnce sync.Once
582 base uint64
583 baseErr error
584 isData bool
585
586 m *elfMapping
587 }
588
589
590
591
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
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
659
660
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
682
683
684
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
718
719
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