44package kallsyms // import "go.opentelemetry.io/ebpf-profiler/kallsyms"
55
66import (
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+
2026type 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.
2868type 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.
4791func (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.
137148func (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.
220232func (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