@@ -25,9 +25,12 @@ import (
2525 "go.opentelemetry.io/ebpf-profiler/libpf"
2626 "go.opentelemetry.io/ebpf-profiler/libpf/pfelf"
2727 "go.opentelemetry.io/ebpf-profiler/libpf/pfunsafe"
28+ "go.opentelemetry.io/ebpf-profiler/lpm"
2829 "go.opentelemetry.io/ebpf-profiler/metrics"
2930 npsr "go.opentelemetry.io/ebpf-profiler/nopanicslicereader"
31+ "go.opentelemetry.io/ebpf-profiler/process"
3032 "go.opentelemetry.io/ebpf-profiler/remotememory"
33+ "go.opentelemetry.io/ebpf-profiler/reporter"
3134 "go.opentelemetry.io/ebpf-profiler/successfailurecounter"
3235 "go.opentelemetry.io/ebpf-profiler/support"
3336 "go.opentelemetry.io/ebpf-profiler/util"
8891
8992 unknownCfunc = libpf .Intern ("UNKNOWN CFUNC" )
9093 cfuncDummyFile = libpf .Intern ("<cfunc>" )
94+ rubyJitDummyFrame = libpf .Intern ("UNKNOWN JIT CODE" )
95+ rubyJitDummyFile = libpf .Intern ("<jitted code>" )
9196 // compiler check to make sure the needed interfaces are satisfied
9297 _ interpreter.Data = & rubyData {}
9398 _ interpreter.Instance = & rubyInstance {}
@@ -295,8 +300,11 @@ func (r *rubyData) Attach(ebpf interpreter.EbpfHandler, pid libpf.PID, bias libp
295300 return & rubyInstance {
296301 r : r ,
297302 rm : rm ,
303+ procInfo : & cdata ,
298304 globalSymbolsAddr : r .globalSymbolsAddr + bias ,
299305 addrToString : addrToString ,
306+ mappings : make (map [process.Mapping ]* uint32 ),
307+ prefixes : make (map [lpm.Prefix ]* uint32 ),
300308 memPool : sync.Pool {
301309 New : func () any {
302310 buf := make ([]byte , 512 )
@@ -335,6 +343,9 @@ type rubyInstance struct {
335343 r * rubyData
336344 rm remotememory.RemoteMemory
337345
346+ // Store the procinfo so we can update it if mappings are updated
347+ procInfo * support.RubyProcInfo
348+
338349 // globalSymbolsAddr is the offset of the global symbol table, for looking up ruby symbolic ids
339350 globalSymbolsAddr libpf.Address
340351
@@ -347,6 +358,13 @@ type rubyInstance struct {
347358 // maxSize is the largest number we did see in the last reporting interval for size
348359 // in getRubyLineNo.
349360 maxSize atomic.Uint32
361+
362+ // mappings is indexed by the Mapping to its generation
363+ mappings map [process.Mapping ]* uint32
364+ // prefixes is indexed by the prefix added to ebpf maps (to be cleaned up) to its generation
365+ prefixes map [lpm.Prefix ]* uint32
366+ // mappingGeneration is the current generation (so old entries can be pruned)
367+ mappingGeneration uint32
350368}
351369
352370func (r * rubyInstance ) Detach (ebpf interpreter.EbpfHandler , pid libpf.PID ) error {
@@ -984,6 +1002,15 @@ func (r *rubyInstance) Symbolize(frame *host.Frame, frames *libpf.Frames) error
9841002
9851003 case support .RubyFrameTypeIseq :
9861004 iseqBody = libpf .Address (frameAddr )
1005+ case support .RubyFrameTypeJit :
1006+ label := rubyJitDummyFrame
1007+ frames .Append (& libpf.Frame {
1008+ Type : libpf .RubyFrame ,
1009+ FunctionName : label ,
1010+ SourceFile : rubyJitDummyFile ,
1011+ SourceLine : 0 ,
1012+ })
1013+ return nil
9871014 default :
9881015 return fmt .Errorf ("Unable to get CME or ISEQ from frame address (%d : %04x)" , frameAddrType , frameFlags )
9891016 }
@@ -1121,6 +1148,92 @@ func profileFrameFullLabel(classPath, label, baseLabel, methodName libpf.String,
11211148 return libpf .Intern (profileLabel )
11221149}
11231150
1151+ func (r * rubyInstance ) SynchronizeMappings (ebpf interpreter.EbpfHandler ,
1152+ _ reporter.ExecutableReporter , pr process.Process , mappings []process.Mapping ) error {
1153+ var jitMapping * process.Mapping
1154+
1155+ pid := pr .PID ()
1156+ jitFound := false
1157+ r .mappingGeneration ++
1158+
1159+ log .Debugf ("Synchronizing ruby mappings" )
1160+
1161+ for idx := range mappings {
1162+ m := & mappings [idx ]
1163+ if ! m .IsExecutable () || ! m .IsAnonymous () {
1164+ continue
1165+ }
1166+ // If prctl is allowed, ruby should label the memory region
1167+ // always prefer that
1168+ if strings .Contains (m .Path .String (), "jit_reserve_addr_space" ) {
1169+ jitMapping = m
1170+ jitFound = true
1171+ }
1172+ // Use the first executable anon region we find if it isn't labeled
1173+ // If we find more, prefer ones earlier in memory or larger in size
1174+ if ! jitFound && (jitMapping == nil || m .Vaddr < jitMapping .Vaddr || m .Length > jitMapping .Length ) {
1175+ // Don't set jitFound here as it is a heuristic, we aren't sure
1176+ // could be on a system without linux config flag to allow prctl to label memoy
1177+ jitMapping = m
1178+ }
1179+
1180+ if _ , exists := r .mappings [* m ]; exists {
1181+ * r .mappings [* m ] = r .mappingGeneration
1182+ continue
1183+ }
1184+
1185+ // Generate a new uint32 pointer which is shared for mapping and the prefixes it owns
1186+ // so updating the mapping above will reflect to prefixes also.
1187+ mappingGeneration := r .mappingGeneration
1188+ r .mappings [* m ] = & mappingGeneration
1189+
1190+ // Just assume all anonymous and executable mappings are Ruby for now
1191+ log .Debugf ("Enabling Ruby interpreter for %#x/%#x" , m .Vaddr , m .Length )
1192+
1193+ prefixes , err := lpm .CalculatePrefixList (m .Vaddr , m .Vaddr + m .Length )
1194+ if err != nil {
1195+ return fmt .Errorf ("new anonymous mapping lpm failure %#x/%#x" , m .Vaddr , m .Length )
1196+ }
1197+
1198+ for _ , prefix := range prefixes {
1199+ _ , exists := r .prefixes [prefix ]
1200+ if ! exists {
1201+ err := ebpf .UpdatePidInterpreterMapping (pid , prefix , support .ProgUnwindRuby , 0 , 0 )
1202+ if err != nil {
1203+ return err
1204+ }
1205+ }
1206+ r .prefixes [prefix ] = & mappingGeneration
1207+ }
1208+ }
1209+ if jitMapping != nil && (r .procInfo .Jit_start != jitMapping .Vaddr || r .procInfo .Jit_end != jitMapping .Vaddr + jitMapping .Length ) {
1210+ r .procInfo .Jit_start = jitMapping .Vaddr
1211+ r .procInfo .Jit_end = jitMapping .Vaddr + jitMapping .Length
1212+ if err := ebpf .UpdateProcData (libpf .Ruby , pr .PID (), unsafe .Pointer (r .procInfo )); err != nil {
1213+ return err
1214+ }
1215+ log .Debugf ("Added jit mapping %08x ruby proc info, %08x" , r .procInfo .Jit_start , r .procInfo .Jit_end )
1216+ }
1217+ // Remove prefixes not seen
1218+ for prefix , generationPtr := range r .prefixes {
1219+ if * generationPtr == r .mappingGeneration {
1220+ continue
1221+ }
1222+ log .Debugf ("Delete Ruby prefix %#v" , prefix )
1223+ _ = ebpf .DeletePidInterpreterMapping (pid , prefix )
1224+ delete (r .prefixes , prefix )
1225+ }
1226+ for m , generationPtr := range r .mappings {
1227+ if * generationPtr == r .mappingGeneration {
1228+ continue
1229+ }
1230+ log .Debugf ("Disabling Ruby for %#x/%#x" , m .Vaddr , m .Length )
1231+ delete (r .mappings , m )
1232+ }
1233+
1234+ return nil
1235+ }
1236+
11241237func (r * rubyInstance ) GetAndResetMetrics () ([]metrics.Metric , error ) {
11251238 addrToStringStats := r .addrToString .ResetMetrics ()
11261239
0 commit comments