| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | |
| | package binutils |
| |
|
| | import ( |
| | "debug/elf" |
| | "debug/macho" |
| | "debug/pe" |
| | "encoding/binary" |
| | "errors" |
| | "fmt" |
| | "io" |
| | "os" |
| | "os/exec" |
| | "path/filepath" |
| | "regexp" |
| | "runtime" |
| | "strconv" |
| | "strings" |
| | "sync" |
| |
|
| | "github.com/google/pprof/internal/elfexec" |
| | "github.com/google/pprof/internal/plugin" |
| | ) |
| |
|
| | |
| | type Binutils struct { |
| | mu sync.Mutex |
| | rep *binrep |
| | } |
| |
|
| | var ( |
| | objdumpLLVMVerRE = regexp.MustCompile(`LLVM version (?:(\d*)\.(\d*)\.(\d*)|.*(trunk).*)`) |
| |
|
| | |
| | elfOpen = elf.Open |
| | ) |
| |
|
| | |
| | |
| | type binrep struct { |
| | |
| | llvmSymbolizer string |
| | llvmSymbolizerFound bool |
| | addr2line string |
| | addr2lineFound bool |
| | nm string |
| | nmFound bool |
| | objdump string |
| | objdumpFound bool |
| | isLLVMObjdump bool |
| |
|
| | |
| | |
| | fast bool |
| | } |
| |
|
| | |
| | func (bu *Binutils) get() *binrep { |
| | bu.mu.Lock() |
| | r := bu.rep |
| | if r == nil { |
| | r = &binrep{} |
| | initTools(r, "") |
| | bu.rep = r |
| | } |
| | bu.mu.Unlock() |
| | return r |
| | } |
| |
|
| | |
| | func (bu *Binutils) update(fn func(r *binrep)) { |
| | r := &binrep{} |
| | bu.mu.Lock() |
| | defer bu.mu.Unlock() |
| | if bu.rep == nil { |
| | initTools(r, "") |
| | } else { |
| | *r = *bu.rep |
| | } |
| | fn(r) |
| | bu.rep = r |
| | } |
| |
|
| | |
| | func (bu *Binutils) String() string { |
| | r := bu.get() |
| | var llvmSymbolizer, addr2line, nm, objdump string |
| | if r.llvmSymbolizerFound { |
| | llvmSymbolizer = r.llvmSymbolizer |
| | } |
| | if r.addr2lineFound { |
| | addr2line = r.addr2line |
| | } |
| | if r.nmFound { |
| | nm = r.nm |
| | } |
| | if r.objdumpFound { |
| | objdump = r.objdump |
| | } |
| | return fmt.Sprintf("llvm-symbolizer=%q addr2line=%q nm=%q objdump=%q fast=%t", |
| | llvmSymbolizer, addr2line, nm, objdump, r.fast) |
| | } |
| |
|
| | |
| | |
| | |
| | func (bu *Binutils) SetFastSymbolization(fast bool) { |
| | bu.update(func(r *binrep) { r.fast = fast }) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | func (bu *Binutils) SetTools(config string) { |
| | bu.update(func(r *binrep) { initTools(r, config) }) |
| | } |
| |
|
| | func initTools(b *binrep, config string) { |
| | |
| | paths := make(map[string][]string) |
| | for _, t := range strings.Split(config, ",") { |
| | name, path := "", t |
| | if ct := strings.SplitN(t, ":", 2); len(ct) == 2 { |
| | name, path = ct[0], ct[1] |
| | } |
| | paths[name] = append(paths[name], path) |
| | } |
| |
|
| | defaultPath := paths[""] |
| | b.llvmSymbolizer, b.llvmSymbolizerFound = chooseExe([]string{"llvm-symbolizer"}, []string{}, append(paths["llvm-symbolizer"], defaultPath...)) |
| | b.addr2line, b.addr2lineFound = chooseExe([]string{"addr2line"}, []string{"gaddr2line"}, append(paths["addr2line"], defaultPath...)) |
| | |
| | |
| | |
| | b.nm, b.nmFound = chooseExe([]string{"llvm-nm", "nm"}, []string{"gnm"}, append(paths["nm"], defaultPath...)) |
| | b.objdump, b.objdumpFound, b.isLLVMObjdump = findObjdump(append(paths["objdump"], defaultPath...)) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | func findObjdump(paths []string) (string, bool, bool) { |
| | objdumpNames := []string{"llvm-objdump", "objdump"} |
| | if runtime.GOOS == "darwin" { |
| | objdumpNames = append(objdumpNames, "gobjdump") |
| | } |
| |
|
| | for _, objdumpName := range objdumpNames { |
| | if objdump, objdumpFound := findExe(objdumpName, paths); objdumpFound { |
| | cmdOut, err := exec.Command(objdump, "--version").Output() |
| | if err != nil { |
| | continue |
| | } |
| | if isLLVMObjdump(string(cmdOut)) { |
| | return objdump, true, true |
| | } |
| | if isBuObjdump(string(cmdOut)) { |
| | return objdump, true, false |
| | } |
| | } |
| | } |
| | return "", false, false |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | func chooseExe(names, osxNames []string, paths []string) (string, bool) { |
| | if runtime.GOOS == "darwin" { |
| | names = append(names, osxNames...) |
| | } |
| | for _, name := range names { |
| | if binary, found := findExe(name, paths); found { |
| | return binary, true |
| | } |
| | } |
| | return "", false |
| | } |
| |
|
| | |
| | |
| | |
| | func isLLVMObjdump(output string) bool { |
| | fields := objdumpLLVMVerRE.FindStringSubmatch(output) |
| | if len(fields) != 5 { |
| | return false |
| | } |
| | if fields[4] == "trunk" { |
| | return true |
| | } |
| | verMajor, err := strconv.Atoi(fields[1]) |
| | if err != nil { |
| | return false |
| | } |
| | verPatch, err := strconv.Atoi(fields[3]) |
| | if err != nil { |
| | return false |
| | } |
| | if runtime.GOOS == "linux" && verMajor >= 8 { |
| | |
| | |
| | |
| | return true |
| | } |
| | if runtime.GOOS == "darwin" { |
| | |
| | return verMajor > 10 || (verMajor == 10 && verPatch >= 1) |
| | } |
| | return false |
| | } |
| |
|
| | |
| | |
| | |
| | func isBuObjdump(output string) bool { |
| | return strings.Contains(output, "GNU objdump") |
| | } |
| |
|
| | |
| | |
| | func findExe(cmd string, paths []string) (string, bool) { |
| | for _, p := range paths { |
| | cp := filepath.Join(p, cmd) |
| | if c, err := exec.LookPath(cp); err == nil { |
| | return c, true |
| | } |
| | } |
| | return cmd, false |
| | } |
| |
|
| | |
| | |
| | func (bu *Binutils) Disasm(file string, start, end uint64, intelSyntax bool) ([]plugin.Inst, error) { |
| | b := bu.get() |
| | if !b.objdumpFound { |
| | return nil, errors.New("cannot disasm: no objdump tool available") |
| | } |
| | args := []string{"--disassemble", "--demangle", "--no-show-raw-insn", |
| | "--line-numbers", fmt.Sprintf("--start-address=%#x", start), |
| | fmt.Sprintf("--stop-address=%#x", end)} |
| |
|
| | if intelSyntax { |
| | if b.isLLVMObjdump { |
| | args = append(args, "--x86-asm-syntax=intel") |
| | } else { |
| | args = append(args, "-M", "intel") |
| | } |
| | } |
| |
|
| | args = append(args, file) |
| | cmd := exec.Command(b.objdump, args...) |
| | out, err := cmd.Output() |
| | if err != nil { |
| | return nil, fmt.Errorf("%v: %v", cmd.Args, err) |
| | } |
| |
|
| | return disassemble(out) |
| | } |
| |
|
| | |
| | func (bu *Binutils) Open(name string, start, limit, offset uint64, relocationSymbol string) (plugin.ObjFile, error) { |
| | b := bu.get() |
| |
|
| | |
| | |
| | |
| |
|
| | if _, err := os.Stat(name); err != nil { |
| | |
| | if strings.Contains(b.addr2line, "testdata/") { |
| | return &fileAddr2Line{file: file{b: b, name: name}}, nil |
| | } |
| | return nil, err |
| | } |
| |
|
| | |
| |
|
| | f, err := os.Open(name) |
| | if err != nil { |
| | return nil, fmt.Errorf("error opening %s: %v", name, err) |
| | } |
| | defer f.Close() |
| |
|
| | var header [4]byte |
| | if _, err = io.ReadFull(f, header[:]); err != nil { |
| | return nil, fmt.Errorf("error reading magic number from %s: %v", name, err) |
| | } |
| |
|
| | elfMagic := string(header[:]) |
| |
|
| | |
| | if elfMagic == elf.ELFMAG { |
| | f, err := b.openELF(name, start, limit, offset, relocationSymbol) |
| | if err != nil { |
| | return nil, fmt.Errorf("error reading ELF file %s: %v", name, err) |
| | } |
| | return f, nil |
| | } |
| |
|
| | |
| | machoMagicLittle := binary.LittleEndian.Uint32(header[:]) |
| | machoMagicBig := binary.BigEndian.Uint32(header[:]) |
| |
|
| | if machoMagicLittle == macho.Magic32 || machoMagicLittle == macho.Magic64 || |
| | machoMagicBig == macho.Magic32 || machoMagicBig == macho.Magic64 { |
| | f, err := b.openMachO(name, start, limit, offset) |
| | if err != nil { |
| | return nil, fmt.Errorf("error reading Mach-O file %s: %v", name, err) |
| | } |
| | return f, nil |
| | } |
| | if machoMagicLittle == macho.MagicFat || machoMagicBig == macho.MagicFat { |
| | f, err := b.openFatMachO(name, start, limit, offset) |
| | if err != nil { |
| | return nil, fmt.Errorf("error reading fat Mach-O file %s: %v", name, err) |
| | } |
| | return f, nil |
| | } |
| |
|
| | peMagic := string(header[:2]) |
| | if peMagic == "MZ" { |
| | f, err := b.openPE(name, start, limit, offset) |
| | if err != nil { |
| | return nil, fmt.Errorf("error reading PE file %s: %v", name, err) |
| | } |
| | return f, nil |
| | } |
| |
|
| | return nil, fmt.Errorf("unrecognized binary format: %s", name) |
| | } |
| |
|
| | func (b *binrep) openMachOCommon(name string, of *macho.File, start, limit, offset uint64) (plugin.ObjFile, error) { |
| |
|
| | |
| | |
| | |
| |
|
| | textSegment := of.Segment("__TEXT") |
| | if textSegment == nil { |
| | return nil, fmt.Errorf("could not identify base for %s: no __TEXT segment", name) |
| | } |
| | if textSegment.Addr > start { |
| | return nil, fmt.Errorf("could not identify base for %s: __TEXT segment address (0x%x) > mapping start address (0x%x)", |
| | name, textSegment.Addr, start) |
| | } |
| |
|
| | base := start - textSegment.Addr |
| |
|
| | if b.fast || (!b.addr2lineFound && !b.llvmSymbolizerFound) { |
| | return &fileNM{file: file{b: b, name: name, base: base}}, nil |
| | } |
| | return &fileAddr2Line{file: file{b: b, name: name, base: base}}, nil |
| | } |
| |
|
| | func (b *binrep) openFatMachO(name string, start, limit, offset uint64) (plugin.ObjFile, error) { |
| | of, err := macho.OpenFat(name) |
| | if err != nil { |
| | return nil, fmt.Errorf("error parsing %s: %v", name, err) |
| | } |
| | defer of.Close() |
| |
|
| | if len(of.Arches) == 0 { |
| | return nil, fmt.Errorf("empty fat Mach-O file: %s", name) |
| | } |
| |
|
| | var arch macho.Cpu |
| | |
| | |
| | |
| | switch runtime.GOARCH { |
| | case "386": |
| | arch = macho.Cpu386 |
| | case "amd64", "amd64p32": |
| | arch = macho.CpuAmd64 |
| | case "arm", "armbe", "arm64", "arm64be": |
| | arch = macho.CpuArm |
| | case "ppc": |
| | arch = macho.CpuPpc |
| | case "ppc64", "ppc64le": |
| | arch = macho.CpuPpc64 |
| | default: |
| | return nil, fmt.Errorf("unsupported host architecture for %s: %s", name, runtime.GOARCH) |
| | } |
| | for i := range of.Arches { |
| | if of.Arches[i].Cpu == arch { |
| | return b.openMachOCommon(name, of.Arches[i].File, start, limit, offset) |
| | } |
| | } |
| | return nil, fmt.Errorf("architecture not found in %s: %s", name, runtime.GOARCH) |
| | } |
| |
|
| | func (b *binrep) openMachO(name string, start, limit, offset uint64) (plugin.ObjFile, error) { |
| | of, err := macho.Open(name) |
| | if err != nil { |
| | return nil, fmt.Errorf("error parsing %s: %v", name, err) |
| | } |
| | defer of.Close() |
| |
|
| | return b.openMachOCommon(name, of, start, limit, offset) |
| | } |
| |
|
| | func (b *binrep) openELF(name string, start, limit, offset uint64, relocationSymbol string) (plugin.ObjFile, error) { |
| | ef, err := elfOpen(name) |
| | if err != nil { |
| | return nil, fmt.Errorf("error parsing %s: %v", name, err) |
| | } |
| | defer ef.Close() |
| |
|
| | buildID := "" |
| | if id, err := elfexec.GetBuildID(ef); err == nil { |
| | buildID = fmt.Sprintf("%x", id) |
| | } |
| |
|
| | var ( |
| | kernelOffset *uint64 |
| | pageAligned = func(addr uint64) bool { return addr%4096 == 0 } |
| | ) |
| | if strings.Contains(name, "vmlinux") || !pageAligned(start) || !pageAligned(limit) || !pageAligned(offset) { |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | symbols, err := ef.Symbols() |
| | if err != nil && err != elf.ErrNoSymbols { |
| | return nil, err |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | if relocationSymbol == "" { |
| | relocationSymbol = "_stext" |
| | } |
| | for _, s := range symbols { |
| | if s.Name == relocationSymbol { |
| | kernelOffset = &s.Value |
| | break |
| | } |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | if _, err := elfexec.GetBase(&ef.FileHeader, elfexec.FindTextProgHeader(ef), kernelOffset, start, limit, offset); err != nil { |
| | return nil, fmt.Errorf("could not identify base for %s: %v", name, err) |
| | } |
| |
|
| | if b.fast || (!b.addr2lineFound && !b.llvmSymbolizerFound) { |
| | return &fileNM{file: file{ |
| | b: b, |
| | name: name, |
| | buildID: buildID, |
| | m: &elfMapping{start: start, limit: limit, offset: offset, kernelOffset: kernelOffset}, |
| | }}, nil |
| | } |
| | return &fileAddr2Line{file: file{ |
| | b: b, |
| | name: name, |
| | buildID: buildID, |
| | m: &elfMapping{start: start, limit: limit, offset: offset, kernelOffset: kernelOffset}, |
| | }}, nil |
| | } |
| |
|
| | func (b *binrep) openPE(name string, start, limit, offset uint64) (plugin.ObjFile, error) { |
| | pf, err := pe.Open(name) |
| | if err != nil { |
| | return nil, fmt.Errorf("error parsing %s: %v", name, err) |
| | } |
| | defer pf.Close() |
| |
|
| | var imageBase uint64 |
| | switch h := pf.OptionalHeader.(type) { |
| | case *pe.OptionalHeader32: |
| | imageBase = uint64(h.ImageBase) |
| | case *pe.OptionalHeader64: |
| | imageBase = uint64(h.ImageBase) |
| | default: |
| | return nil, fmt.Errorf("unknown OptionalHeader %T", pf.OptionalHeader) |
| | } |
| |
|
| | var base uint64 |
| | if start > 0 { |
| | base = start - imageBase |
| | } |
| | if b.fast || (!b.addr2lineFound && !b.llvmSymbolizerFound) { |
| | return &fileNM{file: file{b: b, name: name, base: base}}, nil |
| | } |
| | return &fileAddr2Line{file: file{b: b, name: name, base: base}}, nil |
| | } |
| |
|
| | |
| | |
| | type elfMapping struct { |
| | |
| | start, limit, offset uint64 |
| | |
| | kernelOffset *uint64 |
| | } |
| |
|
| | |
| | |
| | |
| | func (m *elfMapping) findProgramHeader(ef *elf.File, addr uint64) (*elf.ProgHeader, error) { |
| | |
| | |
| | |
| | |
| | |
| |
|
| | if m.kernelOffset != nil || m.start >= m.limit || m.limit >= (uint64(1)<<63) { |
| | |
| | return elfexec.FindTextProgHeader(ef), nil |
| | } |
| |
|
| | |
| | var phdrs []elf.ProgHeader |
| | for i := range ef.Progs { |
| | if ef.Progs[i].Type == elf.PT_LOAD { |
| | phdrs = append(phdrs, ef.Progs[i].ProgHeader) |
| | } |
| | } |
| | |
| | |
| | if len(phdrs) == 0 { |
| | return nil, nil |
| | } |
| | |
| | headers := elfexec.ProgramHeadersForMapping(phdrs, m.offset, m.limit-m.start) |
| | if len(headers) == 0 { |
| | return nil, errors.New("no program header matches mapping info") |
| | } |
| | if len(headers) == 1 { |
| | return headers[0], nil |
| | } |
| |
|
| | |
| | |
| | return elfexec.HeaderForFileOffset(headers, addr-m.start+m.offset) |
| | } |
| |
|
| | |
| | type file struct { |
| | b *binrep |
| | name string |
| | buildID string |
| |
|
| | baseOnce sync.Once |
| | base uint64 |
| | baseErr error |
| | isData bool |
| | |
| | m *elfMapping |
| | } |
| |
|
| | |
| | |
| | |
| | func (f *file) computeBase(addr uint64) error { |
| | if f == nil || f.m == nil { |
| | return nil |
| | } |
| | if addr < f.m.start || addr >= f.m.limit { |
| | 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) |
| | } |
| | ef, err := elfOpen(f.name) |
| | if err != nil { |
| | return fmt.Errorf("error parsing %s: %v", f.name, err) |
| | } |
| | defer ef.Close() |
| |
|
| | ph, err := f.m.findProgramHeader(ef, addr) |
| | if err != nil { |
| | return fmt.Errorf("failed to find program header for file %q, ELF mapping %#v, address %x: %v", f.name, *f.m, addr, err) |
| | } |
| |
|
| | base, err := elfexec.GetBase(&ef.FileHeader, ph, f.m.kernelOffset, f.m.start, f.m.limit, f.m.offset) |
| | if err != nil { |
| | return err |
| | } |
| | f.base = base |
| | f.isData = ph != nil && ph.Flags&elf.PF_X == 0 |
| | return nil |
| | } |
| |
|
| | func (f *file) Name() string { |
| | return f.name |
| | } |
| |
|
| | func (f *file) ObjAddr(addr uint64) (uint64, error) { |
| | f.baseOnce.Do(func() { f.baseErr = f.computeBase(addr) }) |
| | if f.baseErr != nil { |
| | return 0, f.baseErr |
| | } |
| | return addr - f.base, nil |
| | } |
| |
|
| | func (f *file) BuildID() string { |
| | return f.buildID |
| | } |
| |
|
| | func (f *file) SourceLine(addr uint64) ([]plugin.Frame, error) { |
| | f.baseOnce.Do(func() { f.baseErr = f.computeBase(addr) }) |
| | if f.baseErr != nil { |
| | return nil, f.baseErr |
| | } |
| | return nil, nil |
| | } |
| |
|
| | func (f *file) Close() error { |
| | return nil |
| | } |
| |
|
| | func (f *file) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym, error) { |
| | |
| | cmd := exec.Command(f.b.nm, "-n", f.name) |
| | out, err := cmd.Output() |
| | if err != nil { |
| | return nil, fmt.Errorf("%v: %v", cmd.Args, err) |
| | } |
| |
|
| | return findSymbols(out, f.name, r, addr) |
| | } |
| |
|
| | |
| | |
| | |
| | type fileNM struct { |
| | file |
| | addr2linernm *addr2LinerNM |
| | } |
| |
|
| | func (f *fileNM) SourceLine(addr uint64) ([]plugin.Frame, error) { |
| | f.baseOnce.Do(func() { f.baseErr = f.computeBase(addr) }) |
| | if f.baseErr != nil { |
| | return nil, f.baseErr |
| | } |
| | if f.addr2linernm == nil { |
| | addr2liner, err := newAddr2LinerNM(f.b.nm, f.name, f.base) |
| | if err != nil { |
| | return nil, err |
| | } |
| | f.addr2linernm = addr2liner |
| | } |
| | return f.addr2linernm.addrInfo(addr) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | type fileAddr2Line struct { |
| | once sync.Once |
| | file |
| | addr2liner *addr2Liner |
| | llvmSymbolizer *llvmSymbolizer |
| | isData bool |
| | } |
| |
|
| | func (f *fileAddr2Line) SourceLine(addr uint64) ([]plugin.Frame, error) { |
| | f.baseOnce.Do(func() { f.baseErr = f.computeBase(addr) }) |
| | if f.baseErr != nil { |
| | return nil, f.baseErr |
| | } |
| | f.once.Do(f.init) |
| | if f.llvmSymbolizer != nil { |
| | return f.llvmSymbolizer.addrInfo(addr) |
| | } |
| | if f.addr2liner != nil { |
| | return f.addr2liner.addrInfo(addr) |
| | } |
| | return nil, fmt.Errorf("could not find local addr2liner") |
| | } |
| |
|
| | func (f *fileAddr2Line) init() { |
| | if llvmSymbolizer, err := newLLVMSymbolizer(f.b.llvmSymbolizer, f.name, f.base, f.isData); err == nil { |
| | f.llvmSymbolizer = llvmSymbolizer |
| | return |
| | } |
| |
|
| | if addr2liner, err := newAddr2Liner(f.b.addr2line, f.name, f.base); err == nil { |
| | f.addr2liner = addr2liner |
| |
|
| | |
| | |
| | |
| | if nm, err := newAddr2LinerNM(f.b.nm, f.name, f.base); err == nil { |
| | f.addr2liner.nm = nm |
| | } |
| | } |
| | } |
| |
|
| | func (f *fileAddr2Line) Close() error { |
| | if f.llvmSymbolizer != nil { |
| | f.llvmSymbolizer.rw.close() |
| | f.llvmSymbolizer = nil |
| | } |
| | if f.addr2liner != nil { |
| | f.addr2liner.rw.close() |
| | f.addr2liner = nil |
| | } |
| | return nil |
| | } |
| |
|