1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289
|
package kallsyms
import (
"errors"
"fmt"
"io"
"os"
"slices"
"strconv"
"strings"
"github.com/cilium/ebpf/internal"
)
var errAmbiguousKsym = errors.New("multiple kernel symbols with the same name")
var symAddrs cache[string, uint64]
var symModules cache[string, string]
// Module returns the kernel module providing the given symbol in the kernel, if
// any. Returns an empty string and no error if the symbol is not present in the
// kernel. Only function symbols are considered. Returns an error if multiple
// symbols with the same name were found.
//
// Consider [AssignModules] if you need to resolve multiple symbols, as it will
// only perform one iteration over /proc/kallsyms.
func Module(name string) (string, error) {
if name == "" {
return "", nil
}
if mod, ok := symModules.Load(name); ok {
return mod, nil
}
request := map[string]string{name: ""}
if err := AssignModules(request); err != nil {
return "", err
}
return request[name], nil
}
// AssignModules looks up the kernel module providing each given symbol, if any,
// and assigns them to their corresponding values in the symbols map. Only
// function symbols are considered. Results of all lookups are cached,
// successful or otherwise.
//
// Any symbols missing in the kernel are ignored. Returns an error if multiple
// symbols with a given name were found.
func AssignModules(symbols map[string]string) error {
if !internal.OnLinux {
return fmt.Errorf("read /proc/kallsyms: %w", internal.ErrNotSupportedOnOS)
}
if len(symbols) == 0 {
return nil
}
// Attempt to fetch symbols from cache.
request := make(map[string]string)
for name := range symbols {
if mod, ok := symModules.Load(name); ok {
symbols[name] = mod
continue
}
// Mark the symbol to be read from /proc/kallsyms.
request[name] = ""
}
if len(request) == 0 {
// All symbols satisfied from cache.
return nil
}
f, err := os.Open("/proc/kallsyms")
if err != nil {
return err
}
defer f.Close()
if err := assignModules(f, request); err != nil {
return fmt.Errorf("assigning symbol modules: %w", err)
}
// Update the cache with the new symbols. Cache all requested symbols, even if
// they're missing or don't belong to a module.
for name, mod := range request {
symModules.Store(name, mod)
symbols[name] = mod
}
return nil
}
// assignModules assigns kernel symbol modules read from f to values requested
// by symbols. Always scans the whole input to make sure the user didn't request
// an ambiguous symbol.
func assignModules(f io.Reader, symbols map[string]string) error {
if len(symbols) == 0 {
return nil
}
found := make(map[string]struct{})
r := newReader(f)
for r.Line() {
// Only look for function symbols in the kernel's text section (tT).
s, err, skip := parseSymbol(r, []rune{'t', 'T'})
if err != nil {
return fmt.Errorf("parsing kallsyms line: %w", err)
}
if skip {
continue
}
if _, requested := symbols[s.name]; !requested {
continue
}
if _, ok := found[s.name]; ok {
// We've already seen this symbol. Return an error to avoid silently
// attaching to a symbol in the wrong module. libbpf also rejects
// referring to ambiguous symbols.
//
// We can't simply check if we already have a value for the given symbol,
// since many won't have an associated kernel module.
return fmt.Errorf("symbol %s: duplicate found at address 0x%x (module %q): %w",
s.name, s.addr, s.mod, errAmbiguousKsym)
}
symbols[s.name] = s.mod
found[s.name] = struct{}{}
}
if err := r.Err(); err != nil {
return fmt.Errorf("reading kallsyms: %w", err)
}
return nil
}
// Address returns the address of the given symbol in the kernel. Returns 0 and
// no error if the symbol is not present. Returns an error if multiple addresses
// were found for a symbol.
//
// Consider [AssignAddresses] if you need to resolve multiple symbols, as it
// will only perform one iteration over /proc/kallsyms.
func Address(symbol string) (uint64, error) {
if symbol == "" {
return 0, nil
}
if addr, ok := symAddrs.Load(symbol); ok {
return addr, nil
}
request := map[string]uint64{symbol: 0}
if err := AssignAddresses(request); err != nil {
return 0, err
}
return request[symbol], nil
}
// AssignAddresses looks up the addresses of the requested symbols in the kernel
// and assigns them to their corresponding values in the symbols map. Results
// of all lookups are cached, successful or otherwise.
//
// Any symbols missing in the kernel are ignored. Returns an error if multiple
// addresses were found for a symbol.
func AssignAddresses(symbols map[string]uint64) error {
if !internal.OnLinux {
return fmt.Errorf("read /proc/kallsyms: %w", internal.ErrNotSupportedOnOS)
}
if len(symbols) == 0 {
return nil
}
// Attempt to fetch symbols from cache.
request := make(map[string]uint64)
for name := range symbols {
if addr, ok := symAddrs.Load(name); ok {
symbols[name] = addr
continue
}
// Mark the symbol to be read from /proc/kallsyms.
request[name] = 0
}
if len(request) == 0 {
// All symbols satisfied from cache.
return nil
}
f, err := os.Open("/proc/kallsyms")
if err != nil {
return err
}
defer f.Close()
if err := assignAddresses(f, request); err != nil {
return fmt.Errorf("loading symbol addresses: %w", err)
}
// Update the cache with the new symbols. Cache all requested symbols even if
// they weren't found, to avoid repeated lookups.
for name, addr := range request {
symAddrs.Store(name, addr)
symbols[name] = addr
}
return nil
}
// assignAddresses assigns kernel symbol addresses read from f to values
// requested by symbols. Always scans the whole input to make sure the user
// didn't request an ambiguous symbol.
func assignAddresses(f io.Reader, symbols map[string]uint64) error {
if len(symbols) == 0 {
return nil
}
r := newReader(f)
for r.Line() {
s, err, skip := parseSymbol(r, nil)
if err != nil {
return fmt.Errorf("parsing kallsyms line: %w", err)
}
if skip {
continue
}
existing, requested := symbols[s.name]
if existing != 0 {
// Multiple addresses for a symbol have been found. Return a friendly
// error to avoid silently attaching to the wrong symbol. libbpf also
// rejects referring to ambiguous symbols.
return fmt.Errorf("symbol %s(0x%x): duplicate found at address 0x%x: %w", s.name, existing, s.addr, errAmbiguousKsym)
}
if requested {
symbols[s.name] = s.addr
}
}
if err := r.Err(); err != nil {
return fmt.Errorf("reading kallsyms: %w", err)
}
return nil
}
type ksym struct {
addr uint64
name string
mod string
}
// parseSymbol parses a line from /proc/kallsyms into an address, type, name and
// module. Skip will be true if the symbol doesn't match any of the given symbol
// types. See `man 1 nm` for all available types.
//
// Example line: `ffffffffc1682010 T nf_nat_init [nf_nat]`
func parseSymbol(r *reader, types []rune) (s ksym, err error, skip bool) {
for i := 0; r.Word(); i++ {
switch i {
// Address of the symbol.
case 0:
s.addr, err = strconv.ParseUint(r.Text(), 16, 64)
if err != nil {
return s, fmt.Errorf("parsing address: %w", err), false
}
// Type of the symbol. Assume the character is ASCII-encoded by converting
// it directly to a rune, since it's a fixed field controlled by the kernel.
case 1:
if len(types) > 0 && !slices.Contains(types, rune(r.Bytes()[0])) {
return s, nil, true
}
// Name of the symbol.
case 2:
s.name = r.Text()
// Kernel module the symbol is provided by.
case 3:
s.mod = strings.Trim(r.Text(), "[]")
// Ignore any future fields.
default:
break
}
}
return
}
|