src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

additions_buildinfo.go (6915B)


      1 // Copyright 2022 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 //go:build go1.18
      6 
      7 package buildinfo
      8 
      9 // This file adds to buildinfo the functionality for extracting the PCLN table.
     10 
     11 import (
     12 	"debug/elf"
     13 	"debug/macho"
     14 	"debug/pe"
     15 	"encoding/binary"
     16 	"errors"
     17 	"fmt"
     18 	"io"
     19 )
     20 
     21 // ErrNoSymbols represents non-existence of symbol
     22 // table in binaries supported by buildinfo.
     23 var ErrNoSymbols = errors.New("no symbol section")
     24 
     25 // SymbolInfo is derived from cmd/internal/objfile/elf.go:symbols, symbolData.
     26 func (x *elfExe) SymbolInfo(name string) (uint64, uint64, io.ReaderAt, error) {
     27 	sym, err := x.lookupSymbol(name)
     28 	if err != nil || sym == nil {
     29 		if errors.Is(err, elf.ErrNoSymbols) {
     30 			return 0, 0, nil, ErrNoSymbols
     31 		}
     32 		return 0, 0, nil, fmt.Errorf("no symbol %q", name)
     33 	}
     34 	prog := x.progContaining(sym.Value)
     35 	if prog == nil {
     36 		return 0, 0, nil, fmt.Errorf("no Prog containing value %d for %q", sym.Value, name)
     37 	}
     38 	return sym.Value, prog.Vaddr, prog.ReaderAt, nil
     39 }
     40 
     41 func (x *elfExe) lookupSymbol(name string) (*elf.Symbol, error) {
     42 	x.symbolsOnce.Do(func() {
     43 		syms, err := x.f.Symbols()
     44 		if err != nil {
     45 			x.symbolsErr = err
     46 			return
     47 		}
     48 		x.symbols = make(map[string]*elf.Symbol, len(syms))
     49 		for _, s := range syms {
     50 			s := s // make a copy to prevent aliasing
     51 			x.symbols[s.Name] = &s
     52 		}
     53 	})
     54 	if x.symbolsErr != nil {
     55 		return nil, x.symbolsErr
     56 	}
     57 	return x.symbols[name], nil
     58 }
     59 
     60 func (x *elfExe) progContaining(addr uint64) *elf.Prog {
     61 	for _, p := range x.f.Progs {
     62 		if addr >= p.Vaddr && addr < p.Vaddr+p.Filesz {
     63 			return p
     64 		}
     65 	}
     66 	return nil
     67 }
     68 
     69 const go12magic = 0xfffffffb
     70 const go116magic = 0xfffffffa
     71 
     72 // PCLNTab is derived from cmd/internal/objfile/elf.go:pcln.
     73 func (x *elfExe) PCLNTab() ([]byte, uint64) {
     74 	var offset uint64
     75 	text := x.f.Section(".text")
     76 	if text != nil {
     77 		offset = text.Offset
     78 	}
     79 	pclntab := x.f.Section(".gopclntab")
     80 	if pclntab == nil {
     81 		// Addition: this code is added to support some form of stripping.
     82 		pclntab = x.f.Section(".data.rel.ro.gopclntab")
     83 		if pclntab == nil {
     84 			pclntab = x.f.Section(".data.rel.ro")
     85 			if pclntab == nil {
     86 				return nil, 0
     87 			}
     88 			// Possibly the PCLN table has been stuck in the .data.rel.ro section, but without
     89 			// its own section header. We can search for for the start by looking for the four
     90 			// byte magic and the go magic.
     91 			b, err := pclntab.Data()
     92 			if err != nil {
     93 				return nil, 0
     94 			}
     95 			// TODO(rolandshoemaker): I'm not sure if the 16 byte increment during the search is
     96 			// actually correct. During testing it worked, but that may be because I got lucky
     97 			// with the binary I was using, and we need to do four byte jumps to exhaustively
     98 			// search the section?
     99 			for i := 0; i < len(b); i += 16 {
    100 				if len(b)-i > 16 && b[i+4] == 0 && b[i+5] == 0 &&
    101 					(b[i+6] == 1 || b[i+6] == 2 || b[i+6] == 4) &&
    102 					(b[i+7] == 4 || b[i+7] == 8) {
    103 					// Also check for the go magic
    104 					leMagic := binary.LittleEndian.Uint32(b[i:])
    105 					beMagic := binary.BigEndian.Uint32(b[i:])
    106 					switch {
    107 					case leMagic == go12magic:
    108 						fallthrough
    109 					case beMagic == go12magic:
    110 						fallthrough
    111 					case leMagic == go116magic:
    112 						fallthrough
    113 					case beMagic == go116magic:
    114 						return b[i:], offset
    115 					}
    116 				}
    117 			}
    118 		}
    119 	}
    120 	b, err := pclntab.Data()
    121 	if err != nil {
    122 		return nil, 0
    123 	}
    124 	return b, offset
    125 }
    126 
    127 // SymbolInfo is derived from cmd/internal/objfile/pe.go:findPESymbol, loadPETable.
    128 func (x *peExe) SymbolInfo(name string) (uint64, uint64, io.ReaderAt, error) {
    129 	sym, err := x.lookupSymbol(name)
    130 	if err != nil {
    131 		return 0, 0, nil, err
    132 	}
    133 	if sym == nil {
    134 		return 0, 0, nil, fmt.Errorf("no symbol %q", name)
    135 	}
    136 	sect := x.f.Sections[sym.SectionNumber-1]
    137 	// In PE, the symbol's value is the offset from the section start.
    138 	return uint64(sym.Value), 0, sect.ReaderAt, nil
    139 }
    140 
    141 func (x *peExe) lookupSymbol(name string) (*pe.Symbol, error) {
    142 	x.symbolsOnce.Do(func() {
    143 		x.symbols = make(map[string]*pe.Symbol, len(x.f.Symbols))
    144 		if len(x.f.Symbols) == 0 {
    145 			x.symbolsErr = ErrNoSymbols
    146 			return
    147 		}
    148 		for _, s := range x.f.Symbols {
    149 			x.symbols[s.Name] = s
    150 		}
    151 	})
    152 	if x.symbolsErr != nil {
    153 		return nil, x.symbolsErr
    154 	}
    155 	return x.symbols[name], nil
    156 }
    157 
    158 // PCLNTab is derived from cmd/internal/objfile/pe.go:pcln.
    159 // Assumes that the underlying symbol table exists, otherwise
    160 // it might panic.
    161 func (x *peExe) PCLNTab() ([]byte, uint64) {
    162 	var textOffset uint64
    163 	for _, section := range x.f.Sections {
    164 		if section.Name == ".text" {
    165 			textOffset = uint64(section.Offset)
    166 			break
    167 		}
    168 	}
    169 
    170 	var start, end int64
    171 	var section int
    172 	if s, _ := x.lookupSymbol("runtime.pclntab"); s != nil {
    173 		start = int64(s.Value)
    174 		section = int(s.SectionNumber - 1)
    175 	}
    176 	if s, _ := x.lookupSymbol("runtime.epclntab"); s != nil {
    177 		end = int64(s.Value)
    178 	}
    179 	if start == 0 || end == 0 {
    180 		return nil, 0
    181 	}
    182 	offset := int64(x.f.Sections[section].Offset) + start
    183 	size := end - start
    184 
    185 	pclntab := make([]byte, size)
    186 	if _, err := x.r.ReadAt(pclntab, offset); err != nil {
    187 		return nil, 0
    188 	}
    189 	return pclntab, textOffset
    190 }
    191 
    192 // SymbolInfo is derived from cmd/internal/objfile/macho.go:symbols.
    193 func (x *machoExe) SymbolInfo(name string) (uint64, uint64, io.ReaderAt, error) {
    194 	sym, err := x.lookupSymbol(name)
    195 	if err != nil {
    196 		return 0, 0, nil, err
    197 	}
    198 	if sym == nil {
    199 		return 0, 0, nil, fmt.Errorf("no symbol %q", name)
    200 	}
    201 	seg := x.segmentContaining(sym.Value)
    202 	if seg == nil {
    203 		return 0, 0, nil, fmt.Errorf("no Segment containing value %d for %q", sym.Value, name)
    204 	}
    205 	return sym.Value, seg.Addr, seg.ReaderAt, nil
    206 }
    207 
    208 func (x *machoExe) lookupSymbol(name string) (*macho.Symbol, error) {
    209 	const mustExistSymbol = "runtime.main"
    210 	x.symbolsOnce.Do(func() {
    211 		x.symbols = make(map[string]*macho.Symbol, len(x.f.Symtab.Syms))
    212 		for _, s := range x.f.Symtab.Syms {
    213 			s := s // make a copy to prevent aliasing
    214 			x.symbols[s.Name] = &s
    215 		}
    216 		// In the presence of stripping, the symbol table for darwin
    217 		// binaries will not be empty, but the program symbols will
    218 		// be missing.
    219 		if _, ok := x.symbols[mustExistSymbol]; !ok {
    220 			x.symbolsErr = ErrNoSymbols
    221 		}
    222 	})
    223 
    224 	if x.symbolsErr != nil {
    225 		return nil, x.symbolsErr
    226 	}
    227 	return x.symbols[name], nil
    228 }
    229 
    230 func (x *machoExe) segmentContaining(addr uint64) *macho.Segment {
    231 	for _, load := range x.f.Loads {
    232 		seg, ok := load.(*macho.Segment)
    233 		if ok && seg.Addr <= addr && addr <= seg.Addr+seg.Filesz-1 && seg.Name != "__PAGEZERO" {
    234 			return seg
    235 		}
    236 	}
    237 	return nil
    238 }
    239 
    240 // SymbolInfo is derived from cmd/internal/objfile/macho.go:pcln.
    241 func (x *machoExe) PCLNTab() ([]byte, uint64) {
    242 	var textOffset uint64
    243 	text := x.f.Section("__text")
    244 	if text != nil {
    245 		textOffset = uint64(text.Offset)
    246 	}
    247 	pclntab := x.f.Section("__gopclntab")
    248 	if pclntab == nil {
    249 		return nil, 0
    250 	}
    251 	b, err := pclntab.Data()
    252 	if err != nil {
    253 		return nil, 0
    254 	}
    255 	return b, textOffset
    256 }