Skip to content

Commit 4ea6555

Browse files
committed
Do exact address range match for bpf symbols
1 parent 29e27ec commit 4ea6555

5 files changed

Lines changed: 210 additions & 329 deletions

File tree

kallsyms/bpf.go

Lines changed: 114 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
package kallsyms // import "go.opentelemetry.io/ebpf-profiler/kallsyms"
55

66
import (
7+
"cmp"
78
"context"
89
"errors"
910
"slices"
11+
"strings"
1012
"sync/atomic"
1113
"time"
1214

@@ -17,37 +19,77 @@ import (
1719
"golang.org/x/sys/unix"
1820
)
1921

22+
// bpfProgPrefix is the prefix the kernel uses for all JIT'd BPF program
23+
// symbols in /proc/kallsyms and PERF_RECORD_KSYMBOL events.
24+
const bpfProgPrefix = "bpf_prog_"
25+
2026
type bpfSymbol struct {
2127
address libpf.Address
2228
size uint32
2329
name string
2430
}
2531

32+
// bpfSymbolTable is a sorted (by address) snapshot of all known BPF program
33+
// symbols. It is stored atomically so readers never block writers.
34+
type bpfSymbolTable struct {
35+
symbols []bpfSymbol
36+
}
37+
38+
// lookup returns the symbol containing addr, or ("", false) if none does.
39+
// A symbol covers [address, address+size).
40+
func (t *bpfSymbolTable) lookup(addr libpf.Address) (string, uint, bool) {
41+
// Binary search for the last symbol whose address <= addr.
42+
// BinarySearchFunc returns (index of exact match, true) or
43+
// (insertion point, false). In both cases the candidate symbol
44+
// is at the returned index when found, or at index-1 when not found.
45+
idx, found := slices.BinarySearchFunc(t.symbols, addr, func(sym bpfSymbol, a libpf.Address) int {
46+
return cmp.Compare(sym.address, a)
47+
})
48+
49+
if !found {
50+
// idx is the insertion point; the last symbol with address <= addr
51+
// is one position to the left.
52+
if idx == 0 {
53+
return "", 0, false
54+
}
55+
idx--
56+
}
57+
58+
sym := &t.symbols[idx]
59+
if addr >= sym.address+libpf.Address(sym.size) {
60+
return "", 0, false
61+
}
62+
63+
return sym.name, uint(addr - sym.address), true
64+
}
65+
2666
// bpfSymbolizer is responsible for getting updates from `PERF_RECORD_KSYMBOL`.
2767
// The symbolizer is not ready to use until startMonitor is called to load the symbols.
2868
type bpfSymbolizer struct {
2969
records chan *perf.KSymbolRecord
3070
events []*perf.Event
3171
cancel context.CancelFunc
32-
module atomic.Pointer[Module]
33-
// sizes maps BPF symbol start addresses to their JIT'd byte lengths. It is
34-
// used by finish to set the module end address to the precise end of the
35-
// last symbol rather than a page-rounded approximation.
36-
sizes map[libpf.Address]uint32
72+
table atomic.Pointer[bpfSymbolTable]
3773
}
3874

39-
// Module returns the [Module] with bpf symbols in it.
40-
// It returns nil until startMonitor is called or if there are no bpf symbols.
41-
func (s *bpfSymbolizer) Module() *Module {
42-
return s.module.Load()
75+
// LookupSymbol resolves addr to a BPF program symbol name and offset.
76+
// Returns ("", 0, false) if no BPF program covers addr.
77+
func (s *bpfSymbolizer) LookupSymbol(addr libpf.Address) (string, uint, bool) {
78+
t := s.table.Load()
79+
if t == nil {
80+
return "", 0, false
81+
}
82+
83+
return t.lookup(addr)
4384
}
4485

45-
// loadBPFPrograms enumerates all loaded BPF programs via bpf syscall
46-
// and builds a Module from their JIT symbol addresses and names.
86+
// loadBPFPrograms enumerates all loaded BPF programs via the bpf syscall and
87+
// builds a sorted bpfSymbolTable from their JIT symbol addresses and sizes.
88+
// Only symbols with the "bpf_prog_" prefix are included; trampolines and
89+
// dispatchers are intentionally excluded because they are not visible at
90+
// initial scan time and would cause misattribution.
4791
func (s *bpfSymbolizer) loadBPFPrograms() error {
48-
var symbols []bpfSymbol
49-
minAddr := uint64(0)
50-
sizes := make(map[libpf.Address]uint32)
92+
symbols := []bpfSymbol{}
5193

5294
id := ebpf.ProgramID(0)
5395
for {
@@ -77,62 +119,31 @@ func (s *bpfSymbolizer) loadBPFPrograms() error {
77119
lens, _ := info.JitedFuncLens()
78120

79121
// The kernel names BPF JIT symbols as "bpf_prog_<tag>_<name>".
80-
name := "bpf_prog_" + info.Tag + "_" + info.Name
122+
name := bpfProgPrefix + info.Tag + "_" + info.Name
81123

82124
for i, addr := range addrs {
83-
a := uint64(addr)
84-
if a < minAddr || minAddr == 0 {
85-
minAddr = a
86-
}
87125
sym := bpfSymbol{
88-
address: libpf.Address(a),
126+
address: libpf.Address(addr),
89127
name: name,
90128
}
129+
91130
if i < len(lens) {
92131
sym.size = lens[i]
93-
sizes[sym.address] = lens[i]
94132
}
133+
95134
symbols = append(symbols, sym)
96135
}
97136
}
98137

99-
if len(symbols) == 0 {
100-
s.module.Store(nil)
101-
return nil
102-
}
103-
104-
mod := &Module{
105-
start: libpf.Address(minAddr),
106-
}
107-
mod.addName("bpf")
108-
109-
for _, sym := range symbols {
110-
mod.symbols = append(mod.symbols, symbol{
111-
offset: uint32(sym.address - mod.start),
112-
index: mod.addName(sym.name),
113-
})
114-
}
115-
116-
s.sizes = sizes
117-
s.finish(mod)
138+
slices.SortFunc(symbols, func(a, b bpfSymbol) int {
139+
return cmp.Compare(a.address, b.address)
140+
})
118141

119-
s.module.Store(mod)
142+
s.table.Store(&bpfSymbolTable{symbols: symbols})
120143

121144
return nil
122145
}
123146

124-
// finish finalizes a BPF module. It calls Module.finish() to sort symbols and
125-
// synthesize end, then improves the end estimate for the last (highest-address)
126-
// symbol using its known JIT'd size from the sizes map. If the size is unknown
127-
// the page-rounded fallback from Module.finish() is kept.
128-
func (s *bpfSymbolizer) finish(m *Module) {
129-
m.finish()
130-
lastAddr := m.start + libpf.Address(m.symbols[0].offset)
131-
if size, ok := s.sizes[lastAddr]; ok {
132-
m.end = lastAddr + libpf.Address(size)
133-
}
134-
}
135-
136147
// startMonitor starts the update monitoring and loads bpf symbols.
137148
func (s *bpfSymbolizer) startMonitor(ctx context.Context, onlineCPUs []int) error {
138149
ctx, s.cancel = context.WithCancel(ctx)
@@ -148,6 +159,7 @@ func (s *bpfSymbolizer) startMonitor(ctx context.Context, onlineCPUs []int) erro
148159
}
149160

150161
go s.reloadWorker(ctx)
162+
151163
return nil
152164
}
153165

@@ -219,21 +231,21 @@ func (s *bpfSymbolizer) subscribe(ctx context.Context, onlineCPUs []int) error {
219231
// reloadWorker is the goroutine handling the reloads of the bpf symbols.
220232
func (s *bpfSymbolizer) reloadWorker(ctx context.Context) {
221233
noTimeout := make(<-chan time.Time)
222-
nextKallsymsReload := noTimeout
234+
nextReload := noTimeout
223235
for {
224236
select {
225-
case <-nextKallsymsReload:
237+
case <-nextReload:
226238
if err := s.loadBPFPrograms(); err == nil {
227239
log.Debugf("Kernel symbols reloaded")
228-
nextKallsymsReload = noTimeout
240+
nextReload = noTimeout
229241
} else {
230242
log.Warnf("Failed to reload kernel symbols: %v", err)
231-
nextKallsymsReload = time.After(time.Second)
243+
nextReload = time.After(time.Second)
232244
}
233245
case record := <-s.records:
234246
if err := s.handleBPFUpdate(record); err != nil {
235247
log.Warnf("Error handling bpf ksymbol update: %v", err)
236-
nextKallsymsReload = time.After(time.Second)
248+
nextReload = time.After(time.Second)
237249
}
238250
case <-ctx.Done():
239251
return
@@ -247,99 +259,68 @@ func (s *bpfSymbolizer) handleBPFUpdate(record *perf.KSymbolRecord) error {
247259
return errors.New("lost events detected")
248260
}
249261

250-
mod := s.module.Load()
251-
if mod == nil {
252-
// Unlikely to be triggered as some bpf programs are loaded by the tracer.
253-
return errors.New("first bpf symbol being added")
262+
// Only track bpf_prog_* symbols. Trampolines, dispatchers, and other
263+
// BPF-tagged symbols are excluded because they are not present at initial
264+
// scan time and would cause misattribution.
265+
if !strings.HasPrefix(record.Name, bpfProgPrefix) {
266+
return nil
254267
}
255268

256-
addr := libpf.Address(record.Addr)
257-
258269
if record.Flags&unix.PERF_RECORD_KSYMBOL_FLAGS_UNREGISTER != 0 {
259-
return s.removeBPFSymbol(mod, addr)
260-
}
261-
262-
return s.addBPFSymbol(mod, addr, record.Name, record.Len)
263-
}
264-
265-
// addBPFSymbol handles adding a new BPF symbol to the module.
266-
func (s *bpfSymbolizer) addBPFSymbol(mod *Module, addr libpf.Address, name string, size uint32) error {
267-
var replacement *Module
268-
269-
if addr < mod.start {
270-
// New symbol is below the current module start. Shift all existing
271-
// offsets up by the difference and set the new start address.
272-
replacement = mod.replacement()
273-
274-
delta := uint32(mod.start - addr)
275-
for i := range replacement.symbols {
276-
replacement.symbols[i].offset += delta
277-
}
278-
replacement.start = addr
279-
280-
replacement.symbols = append(replacement.symbols, symbol{
281-
offset: 0,
282-
index: replacement.addName(name),
283-
})
284-
} else {
285-
// If we can find the exact symbol, it's a benign race.
286-
// A race is possible for events that were buffered while a full parsing happened.
287-
existing, off, err := mod.LookupSymbolByAddress(addr)
288-
if err != nil || off > 0 || existing != name {
289-
replacement = mod.replacement()
290-
291-
replacement.symbols = append(replacement.symbols, symbol{
292-
offset: uint32(addr - replacement.start),
293-
index: replacement.addName(name),
294-
})
295-
}
270+
s.removeBPFSymbol(libpf.Address(record.Addr))
271+
return nil
296272
}
297273

298-
if replacement != nil {
299-
s.sizes[addr] = size
300-
s.finish(replacement)
301-
s.module.Store(replacement)
302-
}
274+
s.addBPFSymbol(libpf.Address(record.Addr), record.Name, record.Len)
303275

304276
return nil
305277
}
306278

307-
// removeBPFSymbol handles removing a BPF symbol from the module.
308-
func (s *bpfSymbolizer) removeBPFSymbol(mod *Module, addr libpf.Address) error {
309-
// If we can find the exact symbol, remove it. If we can't, it's a benign race.
310-
// A race is possible for events that were buffered while a full parsing happened.
311-
removeIdx := slices.IndexFunc(mod.symbols, func(sym symbol) bool {
312-
return sym.offset == uint32(addr-mod.start)
279+
// addBPFSymbol inserts a new BPF program symbol into the table.
280+
func (s *bpfSymbolizer) addBPFSymbol(addr libpf.Address, name string, size uint32) {
281+
old := s.table.Load()
282+
var oldSymbols []bpfSymbol
283+
if old != nil {
284+
oldSymbols = old.symbols
285+
}
286+
287+
// Check for a benign race: symbol already present with the same name.
288+
idx, found := slices.BinarySearchFunc(oldSymbols, addr, func(sym bpfSymbol, a libpf.Address) int {
289+
return cmp.Compare(sym.address, a)
313290
})
314-
if removeIdx == -1 {
315-
return nil
291+
if found && oldSymbols[idx].name == name {
292+
return
316293
}
317294

318-
replacement := mod.replacement()
319-
replacement.symbols = slices.Delete(replacement.symbols, removeIdx, removeIdx+1)
295+
// Insert the new symbol into the right position to maintain sorting.
296+
newSym := bpfSymbol{address: addr, size: size, name: name}
297+
newSymbols := make([]bpfSymbol, len(oldSymbols)+1)
298+
copy(newSymbols, oldSymbols[:idx])
299+
newSymbols[idx] = newSym
300+
copy(newSymbols[idx+1:], oldSymbols[idx:])
320301

321-
if len(replacement.symbols) == 0 {
322-
// All symbols gone, clear the module.
323-
s.module.Store(nil)
324-
return nil
302+
s.table.Store(&bpfSymbolTable{symbols: newSymbols})
303+
}
304+
305+
// removeBPFSymbol removes a BPF program symbol from the table by address.
306+
func (s *bpfSymbolizer) removeBPFSymbol(addr libpf.Address) {
307+
old := s.table.Load()
308+
if old == nil {
309+
return
325310
}
326311

327-
// If the lowest-address symbol was removed, adjust the module
328-
// start to the new lowest and shift all offsets down.
329-
newFirst := replacement.symbols[len(replacement.symbols)-1]
330-
if newFirst.offset > 0 {
331-
delta := newFirst.offset
332-
replacement.start += libpf.Address(delta)
333-
for i := range replacement.symbols {
334-
replacement.symbols[i].offset -= delta
335-
}
312+
idx, found := slices.BinarySearchFunc(old.symbols, addr, func(sym bpfSymbol, a libpf.Address) int {
313+
return cmp.Compare(sym.address, a)
314+
})
315+
if !found {
316+
return
336317
}
337318

338-
delete(s.sizes, addr)
339-
s.finish(replacement)
340-
s.module.Store(replacement)
319+
newSymbols := make([]bpfSymbol, len(old.symbols)-1)
320+
copy(newSymbols, old.symbols[:idx])
321+
copy(newSymbols[idx:], old.symbols[idx+1:])
341322

342-
return nil
323+
s.table.Store(&bpfSymbolTable{symbols: newSymbols})
343324
}
344325

345326
// Close frees resources associated with bpfSymbolizer.

0 commit comments

Comments
 (0)