| // Copyright 2022 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. |
| |
| //go:build go1.18 |
| // +build go1.18 |
| |
| package buildinfo |
| |
| // This file adds to buildinfo the functionality for extracting the PCLN table. |
| |
| import ( |
| "debug/elf" |
| "debug/macho" |
| "debug/pe" |
| "encoding/binary" |
| "errors" |
| "fmt" |
| "io" |
| ) |
| |
| // ErrNoSymbols represents non-existence of symbol |
| // table in binaries supported by buildinfo. |
| var ErrNoSymbols = errors.New("no symbol section") |
| |
| // SymbolInfo is derived from cmd/internal/objfile/elf.go:symbols, symbolData. |
| func (x *elfExe) SymbolInfo(name string) (uint64, uint64, io.ReaderAt, error) { |
| sym, err := x.lookupSymbol(name) |
| if err != nil || sym == nil { |
| if errors.Is(err, elf.ErrNoSymbols) { |
| return 0, 0, nil, ErrNoSymbols |
| } |
| return 0, 0, nil, fmt.Errorf("no symbol %q", name) |
| } |
| prog := x.progContaining(sym.Value) |
| if prog == nil { |
| return 0, 0, nil, fmt.Errorf("no Prog containing value %d for %q", sym.Value, name) |
| } |
| return sym.Value, prog.Vaddr, prog.ReaderAt, nil |
| } |
| |
| func (x *elfExe) lookupSymbol(name string) (*elf.Symbol, error) { |
| x.symbolsOnce.Do(func() { |
| syms, err := x.f.Symbols() |
| if err != nil { |
| x.symbolsErr = err |
| return |
| } |
| x.symbols = make(map[string]*elf.Symbol, len(syms)) |
| for _, s := range syms { |
| s := s // make a copy to prevent aliasing |
| x.symbols[s.Name] = &s |
| } |
| }) |
| if x.symbolsErr != nil { |
| return nil, x.symbolsErr |
| } |
| return x.symbols[name], nil |
| } |
| |
| func (x *elfExe) progContaining(addr uint64) *elf.Prog { |
| for _, p := range x.f.Progs { |
| if addr >= p.Vaddr && addr < p.Vaddr+p.Filesz { |
| return p |
| } |
| } |
| return nil |
| } |
| |
| const go12magic = 0xfffffffb |
| const go116magic = 0xfffffffa |
| |
| // PCLNTab is derived from cmd/internal/objfile/elf.go:pcln. |
| func (x *elfExe) PCLNTab() ([]byte, uint64) { |
| var offset uint64 |
| text := x.f.Section(".text") |
| if text != nil { |
| offset = text.Offset |
| } |
| pclntab := x.f.Section(".gopclntab") |
| if pclntab == nil { |
| // Addition: this code is added to support some form of stripping. |
| pclntab = x.f.Section(".data.rel.ro.gopclntab") |
| if pclntab == nil { |
| pclntab = x.f.Section(".data.rel.ro") |
| if pclntab == nil { |
| return nil, 0 |
| } |
| // Possibly the PCLN table has been stuck in the .data.rel.ro section, but without |
| // its own section header. We can search for for the start by looking for the four |
| // byte magic and the go magic. |
| b, err := pclntab.Data() |
| if err != nil { |
| return nil, 0 |
| } |
| // TODO(rolandshoemaker): I'm not sure if the 16 byte increment during the search is |
| // actually correct. During testing it worked, but that may be because I got lucky |
| // with the binary I was using, and we need to do four byte jumps to exhaustively |
| // search the section? |
| for i := 0; i < len(b); i += 16 { |
| if len(b)-i > 16 && b[i+4] == 0 && b[i+5] == 0 && |
| (b[i+6] == 1 || b[i+6] == 2 || b[i+6] == 4) && |
| (b[i+7] == 4 || b[i+7] == 8) { |
| // Also check for the go magic |
| leMagic := binary.LittleEndian.Uint32(b[i:]) |
| beMagic := binary.BigEndian.Uint32(b[i:]) |
| switch { |
| case leMagic == go12magic: |
| fallthrough |
| case beMagic == go12magic: |
| fallthrough |
| case leMagic == go116magic: |
| fallthrough |
| case beMagic == go116magic: |
| return b[i:], offset |
| } |
| } |
| } |
| } |
| } |
| b, err := pclntab.Data() |
| if err != nil { |
| return nil, 0 |
| } |
| return b, offset |
| } |
| |
| // SymbolInfo is derived from cmd/internal/objfile/pe.go:findPESymbol, loadPETable. |
| func (x *peExe) SymbolInfo(name string) (uint64, uint64, io.ReaderAt, error) { |
| sym, err := x.lookupSymbol(name) |
| if err != nil { |
| return 0, 0, nil, err |
| } |
| if sym == nil { |
| return 0, 0, nil, fmt.Errorf("no symbol %q", name) |
| } |
| sect := x.f.Sections[sym.SectionNumber-1] |
| // In PE, the symbol's value is the offset from the section start. |
| return uint64(sym.Value), 0, sect.ReaderAt, nil |
| } |
| |
| func (x *peExe) lookupSymbol(name string) (*pe.Symbol, error) { |
| x.symbolsOnce.Do(func() { |
| x.symbols = make(map[string]*pe.Symbol, len(x.f.Symbols)) |
| if len(x.f.Symbols) == 0 { |
| x.symbolsErr = ErrNoSymbols |
| return |
| } |
| for _, s := range x.f.Symbols { |
| x.symbols[s.Name] = s |
| } |
| }) |
| if x.symbolsErr != nil { |
| return nil, x.symbolsErr |
| } |
| return x.symbols[name], nil |
| } |
| |
| // PCLNTab is derived from cmd/internal/objfile/pe.go:pcln. |
| // Assumes that the underlying symbol table exists, otherwise |
| // it might panic. |
| func (x *peExe) PCLNTab() ([]byte, uint64) { |
| var textOffset uint64 |
| for _, section := range x.f.Sections { |
| if section.Name == ".text" { |
| textOffset = uint64(section.Offset) |
| break |
| } |
| } |
| |
| var start, end int64 |
| var section int |
| if s, _ := x.lookupSymbol("runtime.pclntab"); s != nil { |
| start = int64(s.Value) |
| section = int(s.SectionNumber - 1) |
| } |
| if s, _ := x.lookupSymbol("runtime.epclntab"); s != nil { |
| end = int64(s.Value) |
| } |
| if start == 0 || end == 0 { |
| return nil, 0 |
| } |
| offset := int64(x.f.Sections[section].Offset) + start |
| size := end - start |
| |
| pclntab := make([]byte, size) |
| if _, err := x.r.ReadAt(pclntab, offset); err != nil { |
| return nil, 0 |
| } |
| return pclntab, textOffset |
| } |
| |
| // SymbolInfo is derived from cmd/internal/objfile/macho.go:symbols. |
| func (x *machoExe) SymbolInfo(name string) (uint64, uint64, io.ReaderAt, error) { |
| sym, err := x.lookupSymbol(name) |
| if err != nil { |
| return 0, 0, nil, err |
| } |
| if sym == nil { |
| return 0, 0, nil, fmt.Errorf("no symbol %q", name) |
| } |
| seg := x.segmentContaining(sym.Value) |
| if seg == nil { |
| return 0, 0, nil, fmt.Errorf("no Segment containing value %d for %q", sym.Value, name) |
| } |
| return sym.Value, seg.Addr, seg.ReaderAt, nil |
| } |
| |
| func (x *machoExe) lookupSymbol(name string) (*macho.Symbol, error) { |
| const mustExistSymbol = "runtime.main" |
| x.symbolsOnce.Do(func() { |
| x.symbols = make(map[string]*macho.Symbol, len(x.f.Symtab.Syms)) |
| for _, s := range x.f.Symtab.Syms { |
| s := s // make a copy to prevent aliasing |
| x.symbols[s.Name] = &s |
| } |
| // In the presence of stripping, the symbol table for darwin |
| // binaries will not be empty, but the program symbols will |
| // be missing. |
| if _, ok := x.symbols[mustExistSymbol]; !ok { |
| x.symbolsErr = ErrNoSymbols |
| } |
| }) |
| |
| if x.symbolsErr != nil { |
| return nil, x.symbolsErr |
| } |
| return x.symbols[name], nil |
| } |
| |
| func (x *machoExe) segmentContaining(addr uint64) *macho.Segment { |
| for _, load := range x.f.Loads { |
| seg, ok := load.(*macho.Segment) |
| if ok && seg.Addr <= addr && addr <= seg.Addr+seg.Filesz-1 && seg.Name != "__PAGEZERO" { |
| return seg |
| } |
| } |
| return nil |
| } |
| |
| // SymbolInfo is derived from cmd/internal/objfile/macho.go:pcln. |
| func (x *machoExe) PCLNTab() ([]byte, uint64) { |
| var textOffset uint64 |
| text := x.f.Section("__text") |
| if text != nil { |
| textOffset = uint64(text.Offset) |
| } |
| pclntab := x.f.Section("__gopclntab") |
| if pclntab == nil { |
| return nil, 0 |
| } |
| b, err := pclntab.Data() |
| if err != nil { |
| return nil, 0 |
| } |
| return b, textOffset |
| } |