@@ -9,13 +9,16 @@ package beam // import "go.opentelemetry.io/ebpf-profiler/interpreter/beam"
99// that share the same bytecode, such as Elixir and Gleam.
1010
1111import (
12+ "encoding/binary"
1213 "fmt"
14+ "hash/crc32"
1315 "regexp"
1416 "strings"
1517 "unsafe"
1618
1719 "github.com/elastic/go-freelru"
1820 "go.opentelemetry.io/ebpf-profiler/internal/log"
21+ "go.opentelemetry.io/ebpf-profiler/libpf/hash"
1922
2023 "go.opentelemetry.io/ebpf-profiler/host"
2124 "go.opentelemetry.io/ebpf-profiler/interpreter"
@@ -103,16 +106,23 @@ type beamData struct {
103106 }
104107}
105108
109+ type beamMfa struct {
110+ module uint32
111+ function uint32
112+ arity uint32
113+ }
114+
106115type beamInstance struct {
107116 interpreter.InstanceStubs
108117
109- pid libpf.PID
110- data * beamData
111- rm remotememory.RemoteMemory
112- rangesPtr libpf.Address
113- atomTable libpf.Address
114- atomCache map [uint32 ]string
115- stringCache * freelru.LRU [libpf.Address , libpf.String ]
118+ pid libpf.PID
119+ data * beamData
120+ rm remotememory.RemoteMemory
121+ rangesPtr libpf.Address
122+ atomTable libpf.Address
123+ atomCache * freelru.LRU [uint32 , libpf.String ]
124+ mfaNameCache * freelru.LRU [beamMfa , libpf.String ]
125+ stringCache * freelru.LRU [libpf.Address , libpf.String ]
116126
117127 // prefixes is indexed by the prefix added to ebpf maps (to be cleaned up) to its generation
118128 prefixes map [lpm.Prefix ]uint32
@@ -230,6 +240,7 @@ func Loader(ebpf interpreter.EbpfHandler, info *interpreter.LoaderInfo) (interpr
230240 vms .beamCodeHeader .lineTable = 72
231241 vms .ertsCodeInfo .sizeOf = 40
232242 vms .ertsCodeInfo .mfa = 16
243+ vms .ertsCodeMfa .sizeOf = 24
233244 vms .ertsCodeMfa .module = 0
234245 vms .ertsCodeMfa .function = 8
235246 vms .ertsCodeMfa .arity = 16
@@ -283,21 +294,41 @@ func (d *beamData) Attach(ebpf interpreter.EbpfHandler, pid libpf.PID, bias libp
283294 return nil , err
284295 }
285296
297+ atomCache , err := freelru .New [uint32 , libpf.String ](
298+ interpreter .LruFunctionCacheSize , hash .Uint32 )
299+ if err != nil {
300+ return nil , err
301+ }
302+
303+ hashMFA := func (key beamMfa ) uint32 {
304+ data := make ([]byte , 12 )
305+ binary .LittleEndian .PutUint32 (data [0 :4 ], key .module )
306+ binary .LittleEndian .PutUint32 (data [4 :8 ], key .function )
307+ binary .LittleEndian .PutUint32 (data [8 :12 ], key .arity )
308+ return crc32 .ChecksumIEEE (data )
309+ }
310+ mfaNameCache , err := freelru .New [beamMfa , libpf.String ](
311+ interpreter .LruFunctionCacheSize , hashMFA )
312+ if err != nil {
313+ return nil , err
314+ }
315+
286316 stringCache , err := freelru .New [libpf.Address , libpf.String ](
287317 interpreter .LruFunctionCacheSize , libpf .Address .Hash32 )
288318 if err != nil {
289319 return nil , err
290320 }
291321
292322 return & beamInstance {
293- pid : pid ,
294- data : d ,
295- rm : rm ,
296- prefixes : make (map [lpm.Prefix ]uint32 ),
297- rangesPtr : bias + libpf .Address (d .r ),
298- atomTable : bias + libpf .Address (d .ertsAtomTable ),
299- atomCache : make (map [uint32 ]string ),
300- stringCache : stringCache ,
323+ pid : pid ,
324+ data : d ,
325+ rm : rm ,
326+ prefixes : make (map [lpm.Prefix ]uint32 ),
327+ rangesPtr : bias + libpf .Address (d .r ),
328+ atomTable : bias + libpf .Address (d .ertsAtomTable ),
329+ atomCache : atomCache ,
330+ mfaNameCache : mfaNameCache ,
331+ stringCache : stringCache ,
301332 }, nil
302333}
303334
@@ -357,60 +388,74 @@ func (i *beamInstance) Symbolize(frame *host.Frame, frames *libpf.Frames) error
357388 codeHeader := libpf .Address (frame .File )
358389 pc := libpf .Address (frame .Lineno )
359390
360- log .Debugf ("BEAM symbolizing pc: 0x%x" , pc )
361-
362- functionIndex , moduleID , functionID , arity , err := i .findMFA (pc , codeHeader )
391+ functionIndex , mfa , err := i .findMFA (pc , codeHeader )
363392 if err != nil {
364393 return err
365394 }
366395
367- moduleName , err := i .lookupAtom (moduleID )
368- if err != nil {
369- return err
370- }
371- functionName , err := i .lookupAtom (functionID )
372- if err != nil {
373- return err
374- }
375-
376- mfaName := ""
377- if strings .HasPrefix (moduleName , "Elixir." ) {
378- // This is an Elixir module, so format the function using Elixir syntax (without the "Elixir." prefix)
379- mfaName = fmt .Sprintf ("%s.%s/%d" , moduleName [7 :], functionName , arity )
396+ var mfaName libpf.String
397+ if value , ok := i .mfaNameCache .Get (mfa ); ok {
398+ mfaName = value
380399 } else {
381- // Assume it's Erlang and format it using Erlang syntax
382- mfaName = fmt .Sprintf ("%s:%s/%d" , moduleName , functionName , arity )
400+ moduleName , err := i .lookupAtom (mfa .module )
401+ if err != nil {
402+ return err
403+ }
404+ functionName , err := i .lookupAtom (mfa .function )
405+ if err != nil {
406+ return err
407+ }
408+
409+ if strings .HasPrefix (moduleName .String (), "Elixir." ) {
410+ // This is an Elixir module, so format the function using Elixir syntax (without the "Elixir." prefix)
411+ mfaName = libpf .Intern (fmt .Sprintf ("%s.%s/%d" , moduleName .String ()[7 :], functionName , mfa .arity ))
412+ } else {
413+ // Assume it's Erlang and format it using Erlang syntax
414+ mfaName = libpf .Intern (fmt .Sprintf ("%s:%s/%d" , moduleName , functionName , mfa .arity ))
415+ }
416+
417+ i .mfaNameCache .Add (mfa , mfaName )
383418 }
384419
385420 fileName , lineNumber , err := i .findFileLocation (codeHeader , functionIndex , pc )
386- if err != nil {
387- return err
421+ if err == nil {
422+ log .Debugf ("BEAM Found function %s at %s:%d" , mfaName , fileName , lineNumber )
423+ frames .Append (& libpf.Frame {
424+ Type : libpf .BEAMFrame ,
425+ FunctionName : mfaName ,
426+ SourceFile : fileName ,
427+ SourceLine : libpf .SourceLineno (lineNumber ),
428+ })
429+ } else {
430+ log .Debugf ("BEAM Found function %s" , mfaName )
431+ frames .Append (& libpf.Frame {
432+ Type : libpf .BEAMFrame ,
433+ FunctionName : mfaName ,
434+ })
388435 }
389436
390- log .Debugf ("BEAM Found function %s at %s:%d" , mfaName , fileName , lineNumber )
391- frames .Append (& libpf.Frame {
392- Type : libpf .BEAMFrame ,
393- FunctionName : libpf .Intern (mfaName ),
394- SourceFile : fileName ,
395- SourceLine : libpf .SourceLineno (lineNumber ),
396- })
397-
398437 return nil
399438}
400439
401- func (i * beamInstance ) findMFA (pc libpf.Address , codeHeader libpf.Address ) (functionIndex uint64 , moduleID uint32 , functionID uint32 , arity uint32 , err error ) {
440+ func (i * beamInstance ) findMFA (pc libpf.Address , codeHeader libpf.Address ) (functionIndex uint64 , mfa beamMfa , err error ) {
402441 vms := i .data .vmStructs
403442
404443 numFunctions := i .rm .Uint32 (codeHeader + libpf .Address (vms .beamCodeHeader .numFunctions ))
405444 functions := codeHeader + libpf .Address (vms .beamCodeHeader .functions )
406445
446+ midBuffer := make ([]byte , 16 )
447+
407448 ertsCodeInfo := libpf .Address (0 )
408449 lowIdx := uint64 (0 )
409450 highIdx := uint64 (numFunctions ) - 1
410451 for lowIdx < highIdx {
411452 midIdx := lowIdx + (highIdx - lowIdx )/ 2
412- midStart := i .rm .Ptr (functions + libpf .Address (midIdx * 8 ))
413- midEnd := i .rm .Ptr (functions + libpf .Address ((midIdx + 1 )* 8 ))
453+ err := i .rm .Read (functions + libpf .Address (midIdx * 8 ), midBuffer )
454+ if err != nil {
455+ return 0 , beamMfa {}, fmt .Errorf ("BEAM unable to read codeHeader.functions[%d] for codeHeader 0x%x" , midIdx , codeHeader )
456+ }
457+ midStart := nopanicslicereader .Ptr (midBuffer , 0 )
458+ midEnd := nopanicslicereader .Ptr (midBuffer , 8 )
414459 if pc < midStart {
415460 highIdx = midIdx
416461 } else if pc >= midEnd {
@@ -419,16 +464,20 @@ func (i *beamInstance) findMFA(pc libpf.Address, codeHeader libpf.Address) (func
419464 ertsCodeInfo = midStart
420465 functionIndex = midIdx
421466
422- mfa := ertsCodeInfo + libpf .Address (vms .ertsCodeInfo .mfa )
423- moduleID := i .rm .Uint32 (mfa + libpf .Address (vms .ertsCodeMfa .module ))
424- functionID := i .rm .Uint32 (mfa + libpf .Address (vms .ertsCodeMfa .function ))
425- arity := i .rm .Uint32 (mfa + libpf .Address (vms .ertsCodeMfa .arity ))
467+ data := make ([]byte , vms .ertsCodeMfa .sizeOf )
468+ err = i .rm .Read (ertsCodeInfo + libpf .Address (vms .ertsCodeInfo .mfa ), data )
469+ if err != nil {
470+ return 0 , beamMfa {}, fmt .Errorf ("BEAM unable to look up MFA at for ertsCodeInfo 0x%x" , ertsCodeInfo )
471+ }
472+ mfa .module = nopanicslicereader .Uint32 (data , uint (vms .ertsCodeMfa .module ))
473+ mfa .function = nopanicslicereader .Uint32 (data , uint (vms .ertsCodeMfa .function ))
474+ mfa .arity = nopanicslicereader .Uint32 (data , uint (vms .ertsCodeMfa .arity ))
426475
427- return functionIndex , moduleID , functionID , arity , nil
476+ return functionIndex , mfa , nil
428477 }
429478 }
430479
431- return 0 , 0 , 0 , 0 , fmt .Errorf ("BEAM unable to find the MFA for PC 0x%x in expected code range" , pc )
480+ return 0 , beamMfa {} , fmt .Errorf ("BEAM unable to find the MFA for PC 0x%x in expected code range" , pc )
432481}
433482
434483func (i * beamInstance ) findFileLocation (codeHeader libpf.Address , functionIndex uint64 , pc libpf.Address ) (fileName libpf.String , lineNumber uint64 , err error ) {
@@ -437,21 +486,35 @@ func (i *beamInstance) findFileLocation(codeHeader libpf.Address, functionIndex
437486 lineTable := i .rm .Ptr (codeHeader + libpf .Address (vms .beamCodeHeader .lineTable ))
438487 functionTable := lineTable + libpf .Address (vms .beamCodeLineTab .funcTab )
439488
440- lineLow := i .rm .Ptr (functionTable + libpf .Address (8 * functionIndex ))
441- lineHigh := i .rm .Ptr (functionTable + libpf .Address (8 * (functionIndex + 1 )))
489+ lineRange := make ([]byte , 16 )
490+ err = i .rm .Read (functionTable + libpf .Address (8 * functionIndex ), lineRange )
491+ if err != nil {
492+ return libpf .NullString , 0 , fmt .Errorf ("BEAM failed to read function table info" )
493+ }
494+ lineLow := nopanicslicereader .Ptr (lineRange , 0 )
495+ lineHigh := nopanicslicereader .Ptr (lineRange , 8 )
442496
497+ lineMidBuffer := make ([]byte , 16 )
443498 // We need to align the lineMid values on 8-byte address boundaries
444499 bitmask := libpf .Address (^ (uint64 (0xf )))
445500 for lineHigh > lineLow {
446501 lineMid := lineLow + ((lineHigh - lineLow )/ 2 )& bitmask
447-
448- if pc < i .rm .Ptr (lineMid ) {
502+ err := i .rm .Read (lineMid , lineMidBuffer )
503+ if err != nil {
504+ return libpf .NullString , 0 , fmt .Errorf ("BEAM failed to read line table" )
505+ }
506+ if pc < nopanicslicereader .Ptr (lineMidBuffer , 0 ) {
449507 lineHigh = lineMid
450- } else if pc < i . rm . Ptr (lineMid + libpf . Address ( 8 ) ) {
508+ } else if pc < nopanicslicereader . Ptr (lineMidBuffer , 8 ) {
451509 firstLine := i .rm .Ptr (functionTable )
452510 locIndex := uint32 ((lineMid - firstLine ) / 8 )
453- locSize := i .rm .Uint32 (lineTable + libpf .Address (vms .beamCodeLineTab .locSize ))
454- locTab := i .rm .Ptr (lineTable + libpf .Address (vms .beamCodeLineTab .locTab ))
511+ lineTab := make ([]byte , vms .beamCodeLineTab .sizeOf )
512+ err = i .rm .Read (lineTable , lineTab )
513+ if err != nil {
514+ return libpf .NullString , 0 , fmt .Errorf ("BEAM failed to read line table info" )
515+ }
516+ locSize := nopanicslicereader .Uint32 (lineTab , uint (vms .beamCodeLineTab .locSize ))
517+ locTab := nopanicslicereader .Ptr (lineTab , uint (vms .beamCodeLineTab .locTab ))
455518 locAddr := locTab + libpf .Address (locSize * locIndex )
456519 loc := uint64 (0 )
457520 if locSize == 2 {
@@ -472,8 +535,8 @@ func (i *beamInstance) findFileLocation(codeHeader libpf.Address, functionIndex
472535 return libpf .NullString , 0 , fmt .Errorf ("BEAM unable to find file and line number" )
473536}
474537
475- func (i * beamInstance ) lookupAtom (index uint32 ) (string , error ) {
476- if value , ok := i .atomCache [ index ] ; ok {
538+ func (i * beamInstance ) lookupAtom (index uint32 ) (libpf. String , error ) {
539+ if value , ok := i .atomCache . Get ( index ) ; ok {
477540 return value , nil
478541 }
479542
@@ -490,7 +553,7 @@ func (i *beamInstance) lookupAtom(index uint32) (string, error) {
490553 case "27" :
491554 err := i .rm .Read (i .rm .Ptr (entry + libpf .Address (vms .atom .name )), name )
492555 if err != nil {
493- return "" , fmt .Errorf ("BEAM Unable to lookup atom with index %d: %v" , index , err )
556+ return libpf . NullString , fmt .Errorf ("BEAM Unable to lookup atom with index %d: %v" , index , err )
494557 }
495558 case "28" :
496559 // Implementation based on https://github.com/erlang/otp/blob/OTP-28.0.2/erts/etc/unix/etp-commands.in#L657-L674
@@ -500,15 +563,16 @@ func (i *beamInstance) lookupAtom(index uint32) (string, error) {
500563 if subtag == uint64 (i .data .etpHeapBitsSubtag ) {
501564 err := i .rm .Read (unboxed + libpf .Address (vms .erlHeapBits .data ), name )
502565 if err != nil {
503- return "" , fmt .Errorf ("BEAM Unable to lookup atom with index %d (ErlHeapBits tag): %v" , index , err )
566+ return libpf . NullString , fmt .Errorf ("BEAM Unable to lookup atom with index %d (ErlHeapBits tag): %v" , index , err )
504567 }
505568 } else {
506- return "" , fmt .Errorf ("BEAM Unable to lookup atom with index %d: expected boxed value subtag 0x%x, found 0x%x" , index , i .data .etpHeapBitsSubtag , subtag )
569+ return libpf . NullString , fmt .Errorf ("BEAM Unable to lookup atom with index %d: expected boxed value subtag 0x%x, found 0x%x" , index , i .data .etpHeapBitsSubtag , subtag )
507570 }
508571 }
509572
510- i .atomCache [index ] = string (name )
511- return string (name ), nil
573+ nameString := libpf .Intern (string (name ))
574+ i .atomCache .Add (index , nameString )
575+ return nameString , nil
512576}
513577
514578func (i * beamInstance ) readErlangString (eterm libpf.Address , maxLength uint64 ) libpf.String {
0 commit comments