@@ -80,6 +80,10 @@ type ebpfMapsImpl struct {
8080 usdtSpecsMap * cebpf.Map
8181 nextSpecID uint32
8282 specIDLock sync.Mutex
83+
84+ // allMaps holds all loaded BPF maps by name for generic lookups
85+ // (e.g. UpdateProgArray).
86+ allMaps map [string ]* cebpf.Map
8387}
8488
8589// Compile time check to make sure ebpfMapsImpl satisfies the interface .
@@ -98,6 +102,7 @@ func LoadMaps(ctx context.Context, maps map[string]*cebpf.Map,
98102 probeProgsMap : maps ["kprobe_progs" ],
99103 usdtSpecsMap : maps ["__bpf_usdt_specs" ],
100104 nextSpecID : 0 ,
105+ allMaps : maps ,
101106 }
102107 impl .errCounter = make (map [metrics.MetricID ]int64 )
103108
@@ -212,12 +217,13 @@ func (lc *linkCloser) Unload() error {
212217}
213218
214219// AttachUSDTProbes allows interpreters to attach to usdt probes.
220+ //
221+ // When both multiProgName and singleProgNames are provided, multi-probe
222+ // attachment is attempted first (if the kernel supports it). On failure the
223+ // function automatically falls back to single-shot per-probe attachment.
215224func (impl * ebpfMapsImpl ) AttachUSDTProbes (pid libpf.PID , path , multiProgName string ,
216225 probes []pfelf.USDTProbe , cookies []uint64 , singleProgNames []string ) (interpreter.LinkCloser , error ) {
217226 useMulti := util .HasMultiUprobeSupport ()
218- if ! useMulti && len (probes ) > 1 && multiProgName != "" && len (singleProgNames ) == 0 {
219- return nil , errors .New ("uprobe multi attach requires kernel support (kernel 6.6+)" )
220- }
221227
222228 containerPath := fmt .Sprintf ("/proc/%d/root/%s" , pid , path )
223229 exe , err := link .OpenExecutable (containerPath )
@@ -280,77 +286,88 @@ func (impl *ebpfMapsImpl) AttachUSDTProbes(pid libpf.PID, path, multiProgName st
280286 }
281287 }
282288
283- // If multiProgName is empty or multi-probe not supported, use individual programs (one per probe)
284- if multiProgName == "" || ! useMulti {
285- if singleProgNames == nil {
286- return nil , fmt .Errorf ("singleProgNames required when multiProgName is empty or multi-probe not supported" )
287- }
288- if len (singleProgNames ) != len (probes ) {
289- return nil , fmt .Errorf (
290- "number of single program names %d does not match number of probes %d" ,
291- len (singleProgNames ), len (probes ))
289+ // Try multi-probe first if the kernel supports it and a dispatcher was given.
290+ if multiProgName != "" && useMulti {
291+ lc , multiErr := impl .attachMultiProbe (exe , path , pid , multiProgName ,
292+ names , addresses , offsets , finalCookies , specIDs )
293+ if multiErr == nil {
294+ return lc , nil
292295 }
293- // Single-shot mode with multiple programs
294- // Load all programs first
295- progs := make ([]* cebpf.Program , len (probes ))
296- for i := range probes {
297- prog := impl.userProgs [singleProgNames [i ]]
298- if prog == nil {
299- if err := impl .loadUSDTProgram (singleProgNames [i ], false ); err != nil {
300- return nil , err
301- }
302- prog = impl.userProgs [singleProgNames [i ]]
303- }
304- progs [i ] = prog
296+ if singleProgNames == nil {
297+ return nil , multiErr
305298 }
299+ log .Warnf ("multi-probe attach failed (%v), falling back to single-shot" , multiErr )
300+ }
306301
307- // Attach individual probes with their own programs
308- var links []link.Link
309- for i , probe := range probes {
310- prog := progs [i ]
311- if prog == nil {
312- // Clean up already attached probes
313- for _ , l := range links {
314- l .Close ()
315- }
316- return nil , fmt .Errorf ("program %d is nil for probe %s" , i , probe .Name )
317- }
302+ // Single-shot mode: one program per probe (direct path or fallback).
303+ if singleProgNames == nil {
304+ return nil , fmt .Errorf ("singleProgNames required when multi-probe not available" )
305+ }
306+ if len (singleProgNames ) != len (probes ) {
307+ return nil , fmt .Errorf (
308+ "number of single program names %d does not match number of probes %d" ,
309+ len (singleProgNames ), len (probes ))
310+ }
318311
319- // Prepare uprobe options
320- uprobeOpts := & link.UprobeOptions {
321- Address : probe .Location ,
322- RefCtrOffset : probe .SemaphoreOffset ,
312+ progs := make ([]* cebpf.Program , len (probes ))
313+ for i := range probes {
314+ prog := impl.userProgs [singleProgNames [i ]]
315+ if prog == nil {
316+ if err := impl .loadUSDTProgram (singleProgNames [i ], false ); err != nil {
317+ return nil , err
323318 }
319+ prog = impl.userProgs [singleProgNames [i ]]
320+ }
321+ progs [i ] = prog
322+ }
324323
325- // Set cookie if provided
326- if finalCookies != nil && i < len (finalCookies ) {
327- uprobeOpts .Cookie = finalCookies [i ]
324+ var links []link.Link
325+ for i , probe := range probes {
326+ prog := progs [i ]
327+ if prog == nil {
328+ for _ , l := range links {
329+ l .Close ()
328330 }
331+ return nil , fmt .Errorf ("program %d is nil for probe %s" , i , probe .Name )
332+ }
329333
330- // Attach uprobe at the probe location
331- l , err := exe .Uprobe (probe .Name , prog , uprobeOpts )
332- if err != nil {
333- // Clean up already attached probes
334- for _ , lnk := range links {
335- lnk .Close ()
336- }
337- return nil , fmt .Errorf ("failed to attach USDT probe %s at location 0x%x: %w" ,
338- probe .Name , probe .Location , err )
339- }
340- links = append (links , l )
334+ uprobeOpts := & link.UprobeOptions {
335+ Address : probe .Location ,
336+ RefCtrOffset : probe .SemaphoreOffset ,
337+ }
338+ if finalCookies != nil && i < len (finalCookies ) {
339+ uprobeOpts .Cookie = finalCookies [i ]
341340 }
342341
343- log .Infof ("Attached %d individual probes to %s in PID %d" , len (links ), path , pid )
344- return & linkCloser {
345- unloadLink : links ,
346- unloadSpecIDs : specIDs ,
347- specMap : impl .usdtSpecsMap ,
348- }, nil
342+ l , err := exe .Uprobe (probe .Name , prog , uprobeOpts )
343+ if err != nil {
344+ for _ , lnk := range links {
345+ lnk .Close ()
346+ }
347+ return nil , fmt .Errorf ("failed to attach USDT probe %s at location 0x%x: %w" ,
348+ probe .Name , probe .Location , err )
349+ }
350+ links = append (links , l )
349351 }
350352
353+ log .Infof ("Attached %d individual probes to %s in PID %d" , len (links ), path , pid )
354+ return & linkCloser {
355+ unloadLink : links ,
356+ unloadSpecIDs : specIDs ,
357+ specMap : impl .usdtSpecsMap ,
358+ }, nil
359+ }
360+
361+ // attachMultiProbe loads the multi-probe dispatcher and attaches all probes
362+ // via UprobeMulti.
363+ func (impl * ebpfMapsImpl ) attachMultiProbe (
364+ exe * link.Executable , path string , pid libpf.PID , multiProgName string ,
365+ names []string , addresses , offsets , finalCookies []uint64 ,
366+ specIDs []uint32 ,
367+ ) (interpreter.LinkCloser , error ) {
351368 prog := impl .userProgs [multiProgName ]
352369 if prog == nil {
353- if err := impl .loadUSDTProgram (multiProgName , useMulti ); err != nil {
370+ if err := impl .loadUSDTProgram (multiProgName , true ); err != nil {
354371 return nil , err
355372 }
356373 prog = impl .userProgs [multiProgName ]
@@ -374,14 +391,43 @@ func (impl *ebpfMapsImpl) AttachUSDTProbes(pid libpf.PID, path, multiProgName st
374391 }, nil
375392}
376393
377- // loadProgram loads an eBPF program from progSpec and populates the related maps.
394+ // UpdateProgArray loads a USDT eBPF program by name and inserts it into
395+ // the named BPF_MAP_TYPE_PROG_ARRAY at the given key.
396+ func (impl * ebpfMapsImpl ) UpdateProgArray (mapName string , key uint32 , progName string ) error {
397+ m := impl .allMaps [mapName ]
398+ if m == nil {
399+ return fmt .Errorf ("map %s not found" , mapName )
400+ }
401+ if impl .userProgs == nil {
402+ impl .userProgs = make (map [string ]* cebpf.Program )
403+ }
404+ prog := impl .userProgs [progName ]
405+ if prog == nil {
406+ if err := impl .loadUSDTProgram (progName , false ); err != nil {
407+ return fmt .Errorf ("failed to load program %s: %w" , progName , err )
408+ }
409+ prog = impl .userProgs [progName ]
410+ }
411+ fd := uint32 (prog .FD ())
412+ if err := m .Update (unsafe .Pointer (& key ), unsafe .Pointer (& fd ),
413+ cebpf .UpdateAny ); err != nil {
414+ return fmt .Errorf ("failed to insert %s into %s[%d]: %w" ,
415+ progName , mapName , key , err )
416+ }
417+ log .Infof ("Inserted program %s into %s[%d]" , progName , mapName , key )
418+ return nil
419+ }
420+
421+ // loadUSDTProgram loads an eBPF program from progSpec and populates the related maps.
378422func (impl * ebpfMapsImpl ) loadUSDTProgram (progName string , useMulti bool ) error {
379423 progSpec := impl .coll .Programs [progName ]
380424 if progSpec == nil {
381425 return fmt .Errorf ("eBPF program %s not found in collection" , progName )
382426 }
383427 programOptions := cebpf.ProgramOptions {
384- // TODO: wire in debug level
428+ // LogSizeStart is the initial buffer for the verifier log on load failure.
429+ // Complex USDT programs (e.g. cuda_probe) produce large verifier output.
430+ LogSizeStart : 1 << 24 , // 16MB
385431 }
386432 restoreRlimit , err := rlimit .MaximizeMemlock ()
387433 if err != nil {
0 commit comments