@@ -26,6 +26,7 @@ import (
2626 "github.com/cilium/ebpf/features"
2727 "github.com/cilium/ebpf/link"
2828 "github.com/elastic/go-perf"
29+ "golang.org/x/sync/errgroup"
2930
3031 "go.opentelemetry.io/ebpf-profiler/internal/linux"
3132 "go.opentelemetry.io/ebpf-profiler/internal/log"
@@ -453,19 +454,29 @@ func initializeMapsAndPrograms(kmod *kallsyms.Module, cfg *Config, origins *orig
453454 }
454455 }
455456
456- if err = loadPerfUnwinders (coll , ebpfProgs , ebpfMaps ["perf_progs" ], tailCallProgs ,
457- cfg .BPFVerifierLogLevel ); err != nil {
458- return nil , nil , nil , fmt .Errorf ("failed to load perf eBPF programs: %v" , err )
457+ // The perf and the probe unwinders refer to disjoint sets of program
458+ // specifications ("perf_" and "kprobe_" prefixed), and the probe unwinders only
459+ // depend on already loaded maps, not on the loaded perf programs. Collect the load
460+ // jobs of both so that all of them are verified by the kernel concurrently.
461+ perfJobs , err := perfUnwinderJobs (coll , ebpfMaps ["perf_progs" ], tailCallProgs )
462+ if err != nil {
463+ return nil , nil , nil , fmt .Errorf ("failed to prepare perf eBPF programs: %v" , err )
459464 }
460465
461466 // Load the tail call destinations so custom probes can use it.
462- // loadProbeUnwinders repoints the probe unwinder's per_cpu_records references
467+ // probeUnwinderJobs repoints the probe unwinder's per_cpu_records references
463468 // to per_cpu_records_kp so a perf sampler can't clobber an in-flight uprobe unwind;
464469 // the perf unwinder keeps per_cpu_records.
465- if err = loadProbeUnwinders (coll , ebpfProgs , ebpfMaps ["kprobe_progs" ], tailCallProgs ,
466- cfg .BPFVerifierLogLevel , ebpfMaps ["perf_progs" ].FD (),
467- ebpfMaps ["per_cpu_records" ].FD (), ebpfMaps ["per_cpu_records_kp" ]); err != nil {
468- return nil , nil , nil , fmt .Errorf ("failed to load kprobe eBPF programs: %v" , err )
470+ probeJobs , err := probeUnwinderJobs (coll , ebpfMaps ["kprobe_progs" ], tailCallProgs ,
471+ ebpfMaps ["perf_progs" ].FD (), ebpfMaps ["per_cpu_records" ].FD (),
472+ ebpfMaps ["per_cpu_records_kp" ])
473+ if err != nil {
474+ return nil , nil , nil , fmt .Errorf ("failed to prepare kprobe eBPF programs: %v" , err )
475+ }
476+
477+ if err = loadPrograms (append (perfJobs , probeJobs ... ), cfg .BPFVerifierLogLevel ,
478+ ebpfProgs ); err != nil {
479+ return nil , nil , nil , fmt .Errorf ("failed to load unwinder eBPF programs: %v" , err )
469480 }
470481
471482 if err = removeTemporaryMaps (ebpfMaps ); err != nil {
@@ -718,10 +729,18 @@ func loadPerfUnwinders(coll *cebpf.CollectionSpec, ebpfProgs map[string]*cebpf.P
718729 tailcallMap * cebpf.Map , tailCallProgs []ProgLoaderHelper ,
719730 bpfVerifierLogLevel uint32 ,
720731) error {
721- programOptions := cebpf.ProgramOptions {
722- LogLevel : cebpf .LogLevel (bpfVerifierLogLevel ),
732+ jobs , err := perfUnwinderJobs (coll , tailcallMap , tailCallProgs )
733+ if err != nil {
734+ return err
723735 }
736+ return loadPrograms (jobs , bpfVerifierLogLevel , ebpfProgs )
737+ }
724738
739+ // perfUnwinderJobs collects the load jobs for all perf eBPF programs and their tail
740+ // call targets. It only prepares the jobs, it does not load anything into the kernel.
741+ func perfUnwinderJobs (coll * cebpf.CollectionSpec , tailcallMap * cebpf.Map ,
742+ tailCallProgs []ProgLoaderHelper ,
743+ ) ([]loadJob , error ) {
725744 progs := make ([]ProgLoaderHelper , len (tailCallProgs )+ 3 )
726745 copy (progs , tailCallProgs )
727746
@@ -743,6 +762,7 @@ func loadPerfUnwinders(coll *cebpf.CollectionSpec, ebpfProgs map[string]*cebpf.P
743762 Enable : true ,
744763 })
745764
765+ jobs := make ([]loadJob , 0 , len (progs ))
746766 for _ , unwindProg := range progs {
747767 if ! unwindProg .Enable {
748768 continue
@@ -755,16 +775,18 @@ func loadPerfUnwinders(coll *cebpf.CollectionSpec, ebpfProgs map[string]*cebpf.P
755775
756776 progSpec , ok := coll .Programs [unwindProgName ]
757777 if ! ok {
758- return fmt .Errorf ("program %s does not exist" , unwindProgName )
778+ return nil , fmt .Errorf ("program %s does not exist" , unwindProgName )
759779 }
760780
761- if err := loadProgram (ebpfProgs , tailcallMap , unwindProg .ProgID , progSpec ,
762- programOptions , unwindProg .NoTailCallTarget ); err != nil {
763- return err
764- }
781+ jobs = append (jobs , loadJob {
782+ progID : unwindProg .ProgID ,
783+ progSpec : progSpec ,
784+ tailcallMap : tailcallMap ,
785+ noTailCallTarget : unwindProg .NoTailCallTarget ,
786+ })
765787 }
766788
767- return nil
789+ return jobs , nil
768790}
769791
770792// progArrayReferences returns a list of instructions which load a specified tail
@@ -795,10 +817,21 @@ func loadProbeUnwinders(coll *cebpf.CollectionSpec, ebpfProgs map[string]*cebpf.
795817 bpfVerifierLogLevel uint32 , perfTailCallMapFD int ,
796818 perCPURecordsFD int , perCPURecordsKprobeMap * cebpf.Map ,
797819) error {
798- programOptions := cebpf.ProgramOptions {
799- LogLevel : cebpf .LogLevel (bpfVerifierLogLevel ),
820+ jobs , err := probeUnwinderJobs (coll , tailcallMap , progs , perfTailCallMapFD ,
821+ perCPURecordsFD , perCPURecordsKprobeMap )
822+ if err != nil {
823+ return err
800824 }
825+ return loadPrograms (jobs , bpfVerifierLogLevel , ebpfProgs )
826+ }
801827
828+ // probeUnwinderJobs rewrites the probe program specifications and collects their load
829+ // jobs. It only prepares the jobs, it does not load anything into the kernel.
830+ func probeUnwinderJobs (coll * cebpf.CollectionSpec , tailcallMap * cebpf.Map ,
831+ progs []ProgLoaderHelper , perfTailCallMapFD int ,
832+ perCPURecordsFD int , perCPURecordsKprobeMap * cebpf.Map ,
833+ ) ([]loadJob , error ) {
834+ jobs := make ([]loadJob , 0 , len (progs ))
802835 for _ , unwindProg := range progs {
803836 if ! unwindProg .Enable {
804837 continue
@@ -811,79 +844,173 @@ func loadProbeUnwinders(coll *cebpf.CollectionSpec, ebpfProgs map[string]*cebpf.
811844
812845 progSpec , ok := coll .Programs [unwindProgName ]
813846 if ! ok {
814- return fmt .Errorf ("program %s does not exist" , unwindProgName )
847+ return nil , fmt .Errorf ("program %s does not exist" , unwindProgName )
815848 }
816849
817850 // Replace the prog array for the tail calls.
818851 insns := progArrayReferences (perfTailCallMapFD , progSpec .Instructions )
819852 for _ , ins := range insns {
820853 if err := progSpec .Instructions [ins ].AssociateMap (tailcallMap ); err != nil {
821- return fmt .Errorf ("failed to rewrite map ptr: %v" , err )
854+ return nil , fmt .Errorf ("failed to rewrite map ptr: %v" , err )
822855 }
823856 }
824857
825858 // Repoint per_cpu_records to the probe unwinder's own record map.
826859 recInsns := progArrayReferences (perCPURecordsFD , progSpec .Instructions )
827860 for _ , ins := range recInsns {
828861 if err := progSpec .Instructions [ins ].AssociateMap (perCPURecordsKprobeMap ); err != nil {
829- return fmt .Errorf ("failed to rewrite per_cpu_records ptr: %v" , err )
862+ return nil , fmt .Errorf ("failed to rewrite per_cpu_records ptr: %v" , err )
830863 }
831864 }
832865
833- if err := loadProgram (ebpfProgs , tailcallMap , unwindProg .ProgID , progSpec ,
834- programOptions , unwindProg .NoTailCallTarget ); err != nil {
835- return err
836- }
866+ jobs = append (jobs , loadJob {
867+ progID : unwindProg .ProgID ,
868+ progSpec : progSpec ,
869+ tailcallMap : tailcallMap ,
870+ noTailCallTarget : unwindProg .NoTailCallTarget ,
871+ })
837872 }
838873
839- return nil
874+ return jobs , nil
840875}
841876
842- // loadProgram loads an eBPF program from progSpec and populates the related maps.
843- func loadProgram (ebpfProgs map [string ]* cebpf.Program , tailcallMap * cebpf.Map ,
844- progID uint32 , progSpec * cebpf.ProgramSpec , programOptions cebpf.ProgramOptions ,
845- noTailCallTarget bool ,
877+ // loadJob describes a single eBPF program that needs to be loaded into the kernel.
878+ type loadJob struct {
879+ // progID is the tail call map index of the program, unused if noTailCallTarget.
880+ progID uint32
881+ // progSpec is the specification of the program to load. Each job refers to a
882+ // distinct spec, so jobs can be verified concurrently.
883+ progSpec * cebpf.ProgramSpec
884+ // tailcallMap is the prog array the loaded program is registered in.
885+ tailcallMap * cebpf.Map
886+ // noTailCallTarget indicates the program is not the destination of a tail call.
887+ noTailCallTarget bool
888+ // prog is the loaded program, populated by loadPrograms.
889+ prog * cebpf.Program
890+ // err is the error returned by the kernel, populated by loadPrograms.
891+ err error
892+ }
893+
894+ // loadPrograms loads the given eBPF programs into the kernel and populates the related
895+ // maps.
896+ //
897+ // Loading a program is dominated by the time the kernel spends in the verifier, and each
898+ // program is verified independently of the others, so the programs are loaded
899+ // concurrently. Everything that mutates shared state (registering the programs and
900+ // updating the tail call maps) is done sequentially once all programs are loaded.
901+ func loadPrograms (jobs []loadJob , bpfVerifierLogLevel uint32 ,
902+ ebpfProgs map [string ]* cebpf.Program ,
846903) error {
904+ if len (jobs ) == 0 {
905+ return nil
906+ }
907+
908+ programOptions := cebpf.ProgramOptions {
909+ LogLevel : cebpf .LogLevel (bpfVerifierLogLevel ),
910+ }
911+
912+ // Raise the memlock rlimit once for all programs. It is a process wide resource
913+ // limit, so it must not be modified while other programs are being loaded.
847914 restoreRlimit , err := rlimit .MaximizeMemlock ()
848915 if err != nil {
849916 return fmt .Errorf ("failed to adjust rlimit: %v" , err )
850917 }
851918 defer restoreRlimit ()
852919
853- // Load the eBPF program into the kernel. If no error is returned,
854- // the eBPF program can be used/called/triggered from now on.
855- unwinder , err := cebpf .NewProgramWithOptions (progSpec , programOptions )
856- if err != nil {
857- // These errors tend to have hundreds of lines (or more),
858- // so we print each line individually.
859- if ve , ok := err .(* cebpf.VerifierError ); ok {
860- for _ , line := range ve .Log {
861- log .Errorf ("%s" , line )
862- }
863- } else {
864- scanner := bufio .NewScanner (strings .NewReader (err .Error ()))
865- for scanner .Scan () {
866- log .Errorf ("%s" , scanner .Text ())
920+ eg := & errgroup.Group {}
921+ eg .SetLimit (min (runtime .GOMAXPROCS (0 ), len (jobs )))
922+ for i := range jobs {
923+ job := & jobs [i ]
924+ eg .Go (func () error {
925+ // Load the eBPF program into the kernel. If no error is returned,
926+ // the eBPF program can be used/called/triggered from now on.
927+ prog , err := cebpf .NewProgramWithOptions (job .progSpec , programOptions )
928+ if err != nil {
929+ // The error is only recorded here and reported once all loads
930+ // have finished: verifier errors are hundreds of lines long and
931+ // are logged line by line, so logging them from several
932+ // goroutines would interleave them into an unreadable mess.
933+ job .err = err
934+ return err
867935 }
868- }
869- return fmt .Errorf ("failed to load %s" , progSpec .Name )
936+ job .prog = prog
937+ return nil
938+ })
870939 }
871- ebpfProgs [ progSpec . Name ] = unwinder
940+ loadErr := eg . Wait ()
872941
873- if noTailCallTarget {
874- return nil
942+ // Hand over every program that did load, so that a partially failed load is
943+ // still cleaned up by the caller.
944+ for i := range jobs {
945+ if jobs [i ].prog != nil {
946+ ebpfProgs [jobs [i ].progSpec .Name ] = jobs [i ].prog
947+ }
875948 }
876- fd := uint32 (unwinder .FD ())
877- if err := tailcallMap .Update (unsafe .Pointer (& progID ), unsafe .Pointer (& fd ),
878- cebpf .UpdateAny ); err != nil {
879- // Every eBPF program that is loaded within loadUnwinders can be the
880- // destination of a tail call of another eBPF program. If we can not update
881- // the eBPF map that manages these destinations our unwinding will fail.
882- return fmt .Errorf ("failed to update tailcall map: %v" , err )
949+ if loadErr != nil {
950+ return reportLoadErrors (jobs )
951+ }
952+
953+ for i := range jobs {
954+ job := & jobs [i ]
955+ if job .noTailCallTarget {
956+ continue
957+ }
958+ fd := uint32 (job .prog .FD ())
959+ if err := job .tailcallMap .Update (unsafe .Pointer (& job .progID ), unsafe .Pointer (& fd ),
960+ cebpf .UpdateAny ); err != nil {
961+ // Every eBPF program that is loaded within loadUnwinders can be the
962+ // destination of a tail call of another eBPF program. If we can not update
963+ // the eBPF map that manages these destinations our unwinding will fail.
964+ return fmt .Errorf ("failed to update tailcall map: %v" , err )
965+ }
883966 }
884967 return nil
885968}
886969
970+ // reportLoadErrors logs the failures recorded by loadPrograms and returns the error
971+ // of the first job that failed, in job order, so that the reported error does not
972+ // depend on the order in which the loads happened to complete.
973+ //
974+ // A single unsupported instruction usually makes every program fail, and the kernel
975+ // log of one failure is already hundreds of lines long, so only the first one is
976+ // logged in full and the others are summarized.
977+ func reportLoadErrors (jobs []loadJob ) error {
978+ var firstErr error
979+ var alsoFailed []string
980+ for i := range jobs {
981+ job := & jobs [i ]
982+ if job .err == nil {
983+ continue
984+ }
985+ if firstErr == nil {
986+ logLoadError (job .err )
987+ firstErr = fmt .Errorf ("failed to load %s" , job .progSpec .Name )
988+ continue
989+ }
990+ alsoFailed = append (alsoFailed , job .progSpec .Name )
991+ }
992+ if len (alsoFailed ) > 0 {
993+ log .Errorf ("%d other eBPF programs failed to load: %s" ,
994+ len (alsoFailed ), strings .Join (alsoFailed , ", " ))
995+ }
996+ return firstErr
997+ }
998+
999+ // logLoadError logs a program load error. These errors tend to have hundreds of
1000+ // lines (or more), so we print each line individually.
1001+ func logLoadError (err error ) {
1002+ if ve , ok := errors.AsType [* cebpf.VerifierError ](err ); ok {
1003+ for _ , line := range ve .Log {
1004+ log .Errorf ("%s" , line )
1005+ }
1006+ return
1007+ }
1008+ scanner := bufio .NewScanner (strings .NewReader (err .Error ()))
1009+ for scanner .Scan () {
1010+ log .Errorf ("%s" , scanner .Text ())
1011+ }
1012+ }
1013+
8871014// enableEvent removes the entry of given eventType from the inhibitEvents map
8881015// so that the eBPF code will send the event again.
8891016func (t * Tracer ) enableEvent (eventType int ) {
0 commit comments