blob: b94ba12efdc1330df9eda30e2baff59162dc9894 [file] [log] [blame]
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Addr2line is a minimal simulation of the GNU addr2line tool,
// just enough to support pprof.
//
// Usage:
// go tool addr2line binary
//
// Addr2line reads hexadecimal addresses, one per line and with optional 0x prefix,
// from standard input. For each input address, addr2line prints two output lines,
// first the name of the function containing the address and second the file:line
// of the source code corresponding to that address.
//
// This tool is intended for use only by pprof; its interface may change or
// it may be deleted entirely in future releases.
package main
import (
"bufio"
"debug/elf"
"debug/gosym"
"debug/macho"
"debug/pe"
"debug/plan9obj"
"flag"
"fmt"
"log"
"os"
"strconv"
"strings"
)
func printUsage(w *os.File) {
fmt.Fprintf(w, "usage: addr2line binary\n")
fmt.Fprintf(w, "reads addresses from standard input and writes two lines for each:\n")
fmt.Fprintf(w, "\tfunction name\n")
fmt.Fprintf(w, "\tfile:line\n")
}
func usage() {
printUsage(os.Stderr)
os.Exit(2)
}
func main() {
log.SetFlags(0)
log.SetPrefix("addr2line: ")
// pprof expects this behavior when checking for addr2line
if len(os.Args) > 1 && os.Args[1] == "--help" {
printUsage(os.Stdout)
os.Exit(0)
}
flag.Usage = usage
flag.Parse()
if flag.NArg() != 1 {
usage()
}
f, err := os.Open(flag.Arg(0))
if err != nil {
log.Fatal(err)
}
textStart, symtab, pclntab, err := loadTables(f)
if err != nil {
log.Fatalf("reading %s: %v", flag.Arg(0), err)
}
pcln := gosym.NewLineTable(pclntab, textStart)
tab, err := gosym.NewTable(symtab, pcln)
if err != nil {
log.Fatalf("reading %s: %v", flag.Arg(0), err)
}
stdin := bufio.NewScanner(os.Stdin)
stdout := bufio.NewWriter(os.Stdout)
for stdin.Scan() {
p := stdin.Text()
if strings.Contains(p, ":") {
// Reverse translate file:line to pc.
// This was an extension in the old C version of 'go tool addr2line'
// and is probably not used by anyone, but recognize the syntax.
// We don't have an implementation.
fmt.Fprintf(stdout, "!reverse translation not implemented\n")
continue
}
pc, _ := strconv.ParseUint(strings.TrimPrefix(p, "0x"), 16, 64)
file, line, fn := tab.PCToLine(pc)
name := "?"
if fn != nil {
name = fn.Name
} else {
file = "?"
line = 0
}
fmt.Fprintf(stdout, "%s\n%s:%d\n", name, file, line)
}
stdout.Flush()
}
func loadTables(f *os.File) (textStart uint64, symtab, pclntab []byte, err error) {
if obj, err := elf.NewFile(f); err == nil {
if sect := obj.Section(".text"); sect != nil {
textStart = sect.Addr
}
if sect := obj.Section(".gosymtab"); sect != nil {
if symtab, err = sect.Data(); err != nil {
return 0, nil, nil, err
}
}
if sect := obj.Section(".gopclntab"); sect != nil {
if pclntab, err = sect.Data(); err != nil {
return 0, nil, nil, err
}
}
return textStart, symtab, pclntab, nil
}
if obj, err := macho.NewFile(f); err == nil {
if sect := obj.Section("__text"); sect != nil {
textStart = sect.Addr
}
if sect := obj.Section("__gosymtab"); sect != nil {
if symtab, err = sect.Data(); err != nil {
return 0, nil, nil, err
}
}
if sect := obj.Section("__gopclntab"); sect != nil {
if pclntab, err = sect.Data(); err != nil {
return 0, nil, nil, err
}
}
return textStart, symtab, pclntab, nil
}
if obj, err := pe.NewFile(f); err == nil {
var imageBase uint64
switch oh := obj.OptionalHeader.(type) {
case *pe.OptionalHeader32:
imageBase = uint64(oh.ImageBase)
case *pe.OptionalHeader64:
imageBase = oh.ImageBase
default:
return 0, nil, nil, fmt.Errorf("pe file format not recognized")
}
if sect := obj.Section(".text"); sect != nil {
textStart = imageBase + uint64(sect.VirtualAddress)
}
if pclntab, err = loadPETable(obj, "pclntab", "epclntab"); err != nil {
return 0, nil, nil, err
}
if symtab, err = loadPETable(obj, "symtab", "esymtab"); err != nil {
return 0, nil, nil, err
}
return textStart, symtab, pclntab, nil
}
if obj, err := plan9obj.NewFile(f); err == nil {
sym, err := findPlan9Symbol(obj, "text")
if err != nil {
return 0, nil, nil, err
}
textStart = sym.Value
if pclntab, err = loadPlan9Table(obj, "pclntab", "epclntab"); err != nil {
return 0, nil, nil, err
}
if symtab, err = loadPlan9Table(obj, "symtab", "esymtab"); err != nil {
return 0, nil, nil, err
}
return textStart, symtab, pclntab, nil
}
return 0, nil, nil, fmt.Errorf("unrecognized binary format")
}
func findPESymbol(f *pe.File, name string) (*pe.Symbol, error) {
for _, s := range f.Symbols {
if s.Name != name {
continue
}
if s.SectionNumber <= 0 {
return nil, fmt.Errorf("symbol %s: invalid section number %d", name, s.SectionNumber)
}
if len(f.Sections) < int(s.SectionNumber) {
return nil, fmt.Errorf("symbol %s: section number %d is larger than max %d", name, s.SectionNumber, len(f.Sections))
}
return s, nil
}
return nil, fmt.Errorf("no %s symbol found", name)
}
func loadPETable(f *pe.File, sname, ename string) ([]byte, error) {
ssym, err := findPESymbol(f, sname)
if err != nil {
return nil, err
}
esym, err := findPESymbol(f, ename)
if err != nil {
return nil, err
}
if ssym.SectionNumber != esym.SectionNumber {
return nil, fmt.Errorf("%s and %s symbols must be in the same section", sname, ename)
}
sect := f.Sections[ssym.SectionNumber-1]
data, err := sect.Data()
if err != nil {
return nil, err
}
return data[ssym.Value:esym.Value], nil
}
func findPlan9Symbol(f *plan9obj.File, name string) (*plan9obj.Sym, error) {
syms, err := f.Symbols()
if err != nil {
return nil, err
}
for _, s := range syms {
if s.Name != name {
continue
}
return &s, nil
}
return nil, fmt.Errorf("no %s symbol found", name)
}
func loadPlan9Table(f *plan9obj.File, sname, ename string) ([]byte, error) {
ssym, err := findPlan9Symbol(f, sname)
if err != nil {
return nil, err
}
esym, err := findPlan9Symbol(f, ename)
if err != nil {
return nil, err
}
text, err := findPlan9Symbol(f, "text")
if err != nil {
return nil, err
}
sect := f.Section("text")
if sect == nil {
return nil, err
}
data, err := sect.Data()
if err != nil {
return nil, err
}
return data[ssym.Value-text.Value : esym.Value-text.Value], nil
}