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