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 }