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) (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)
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) (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 stextOffset *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 for _, s := range symbols {
459 if s.Name == "_stext" {
460
461 stextOffset = &s.Value
462 break
463 }
464 }
465 }
466
467
468
469
470
471
472 if _, err := elfexec.GetBase(&ef.FileHeader, elfexec.FindTextProgHeader(ef), stextOffset, start, limit, offset); err != nil {
473 return nil, fmt.Errorf("could not identify base for %s: %v", name, err)
474 }
475
476 if b.fast || (!b.addr2lineFound && !b.llvmSymbolizerFound) {
477 return &fileNM{file: file{
478 b: b,
479 name: name,
480 buildID: buildID,
481 m: &elfMapping{start: start, limit: limit, offset: offset, stextOffset: stextOffset},
482 }}, nil
483 }
484 return &fileAddr2Line{file: file{
485 b: b,
486 name: name,
487 buildID: buildID,
488 m: &elfMapping{start: start, limit: limit, offset: offset, stextOffset: stextOffset},
489 }}, nil
490 }
491
492 func (b *binrep) openPE(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
493 pf, err := pe.Open(name)
494 if err != nil {
495 return nil, fmt.Errorf("error parsing %s: %v", name, err)
496 }
497 defer pf.Close()
498
499 var imageBase uint64
500 switch h := pf.OptionalHeader.(type) {
501 case *pe.OptionalHeader32:
502 imageBase = uint64(h.ImageBase)
503 case *pe.OptionalHeader64:
504 imageBase = uint64(h.ImageBase)
505 default:
506 return nil, fmt.Errorf("unknown OptionalHeader %T", pf.OptionalHeader)
507 }
508
509 var base uint64
510 if start > 0 {
511 base = start - imageBase
512 }
513 if b.fast || (!b.addr2lineFound && !b.llvmSymbolizerFound) {
514 return &fileNM{file: file{b: b, name: name, base: base}}, nil
515 }
516 return &fileAddr2Line{file: file{b: b, name: name, base: base}}, nil
517 }
518
519
520
521 type elfMapping struct {
522
523 start, limit, offset uint64
524
525 stextOffset *uint64
526 }
527
528
529
530
531 func (m *elfMapping) findProgramHeader(ef *elf.File, addr uint64) (*elf.ProgHeader, error) {
532
533
534
535
536
537
538 if m.stextOffset != nil || m.start >= m.limit || m.limit >= (uint64(1)<<63) {
539
540 return elfexec.FindTextProgHeader(ef), nil
541 }
542
543
544 var phdrs []elf.ProgHeader
545 for i := range ef.Progs {
546 if ef.Progs[i].Type == elf.PT_LOAD {
547 phdrs = append(phdrs, ef.Progs[i].ProgHeader)
548 }
549 }
550
551
552 if len(phdrs) == 0 {
553 return nil, nil
554 }
555
556 headers := elfexec.ProgramHeadersForMapping(phdrs, m.offset, m.limit-m.start)
557 if len(headers) == 0 {
558 return nil, errors.New("no program header matches mapping info")
559 }
560 if len(headers) == 1 {
561 return headers[0], nil
562 }
563
564
565
566 return elfexec.HeaderForFileOffset(headers, addr-m.start+m.offset)
567 }
568
569
570 type file struct {
571 b *binrep
572 name string
573 buildID string
574
575 baseOnce sync.Once
576 base uint64
577 baseErr error
578 isData bool
579
580 m *elfMapping
581 }
582
583
584
585
586 func (f *file) computeBase(addr uint64) error {
587 if f == nil || f.m == nil {
588 return nil
589 }
590 if addr < f.m.start || addr >= f.m.limit {
591 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)
592 }
593 ef, err := elfOpen(f.name)
594 if err != nil {
595 return fmt.Errorf("error parsing %s: %v", f.name, err)
596 }
597 defer ef.Close()
598
599 ph, err := f.m.findProgramHeader(ef, addr)
600 if err != nil {
601 return fmt.Errorf("failed to find program header for file %q, ELF mapping %#v, address %x: %v", f.name, *f.m, addr, err)
602 }
603
604 base, err := elfexec.GetBase(&ef.FileHeader, ph, f.m.stextOffset, f.m.start, f.m.limit, f.m.offset)
605 if err != nil {
606 return err
607 }
608 f.base = base
609 f.isData = ph != nil && ph.Flags&elf.PF_X == 0
610 return nil
611 }
612
613 func (f *file) Name() string {
614 return f.name
615 }
616
617 func (f *file) ObjAddr(addr uint64) (uint64, error) {
618 f.baseOnce.Do(func() { f.baseErr = f.computeBase(addr) })
619 if f.baseErr != nil {
620 return 0, f.baseErr
621 }
622 return addr - f.base, nil
623 }
624
625 func (f *file) BuildID() string {
626 return f.buildID
627 }
628
629 func (f *file) SourceLine(addr uint64) ([]plugin.Frame, error) {
630 f.baseOnce.Do(func() { f.baseErr = f.computeBase(addr) })
631 if f.baseErr != nil {
632 return nil, f.baseErr
633 }
634 return nil, nil
635 }
636
637 func (f *file) Close() error {
638 return nil
639 }
640
641 func (f *file) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym, error) {
642
643 cmd := exec.Command(f.b.nm, "-n", f.name)
644 out, err := cmd.Output()
645 if err != nil {
646 return nil, fmt.Errorf("%v: %v", cmd.Args, err)
647 }
648
649 return findSymbols(out, f.name, r, addr)
650 }
651
652
653
654
655 type fileNM struct {
656 file
657 addr2linernm *addr2LinerNM
658 }
659
660 func (f *fileNM) SourceLine(addr uint64) ([]plugin.Frame, error) {
661 f.baseOnce.Do(func() { f.baseErr = f.computeBase(addr) })
662 if f.baseErr != nil {
663 return nil, f.baseErr
664 }
665 if f.addr2linernm == nil {
666 addr2liner, err := newAddr2LinerNM(f.b.nm, f.name, f.base)
667 if err != nil {
668 return nil, err
669 }
670 f.addr2linernm = addr2liner
671 }
672 return f.addr2linernm.addrInfo(addr)
673 }
674
675
676
677
678
679 type fileAddr2Line struct {
680 once sync.Once
681 file
682 addr2liner *addr2Liner
683 llvmSymbolizer *llvmSymbolizer
684 isData bool
685 }
686
687 func (f *fileAddr2Line) SourceLine(addr uint64) ([]plugin.Frame, error) {
688 f.baseOnce.Do(func() { f.baseErr = f.computeBase(addr) })
689 if f.baseErr != nil {
690 return nil, f.baseErr
691 }
692 f.once.Do(f.init)
693 if f.llvmSymbolizer != nil {
694 return f.llvmSymbolizer.addrInfo(addr)
695 }
696 if f.addr2liner != nil {
697 return f.addr2liner.addrInfo(addr)
698 }
699 return nil, fmt.Errorf("could not find local addr2liner")
700 }
701
702 func (f *fileAddr2Line) init() {
703 if llvmSymbolizer, err := newLLVMSymbolizer(f.b.llvmSymbolizer, f.name, f.base, f.isData); err == nil {
704 f.llvmSymbolizer = llvmSymbolizer
705 return
706 }
707
708 if addr2liner, err := newAddr2Liner(f.b.addr2line, f.name, f.base); err == nil {
709 f.addr2liner = addr2liner
710
711
712
713
714 if nm, err := newAddr2LinerNM(f.b.nm, f.name, f.base); err == nil {
715 f.addr2liner.nm = nm
716 }
717 }
718 }
719
720 func (f *fileAddr2Line) Close() error {
721 if f.llvmSymbolizer != nil {
722 f.llvmSymbolizer.rw.close()
723 f.llvmSymbolizer = nil
724 }
725 if f.addr2liner != nil {
726 f.addr2liner.rw.close()
727 f.addr2liner = nil
728 }
729 return nil
730 }
731
View as plain text