diff --git a/interpreter/ruby/ruby.go b/interpreter/ruby/ruby.go index 445011690..942955658 100644 --- a/interpreter/ruby/ruby.go +++ b/interpreter/ruby/ruby.go @@ -25,9 +25,12 @@ import ( "go.opentelemetry.io/ebpf-profiler/libpf" "go.opentelemetry.io/ebpf-profiler/libpf/pfelf" "go.opentelemetry.io/ebpf-profiler/libpf/pfunsafe" + "go.opentelemetry.io/ebpf-profiler/lpm" "go.opentelemetry.io/ebpf-profiler/metrics" npsr "go.opentelemetry.io/ebpf-profiler/nopanicslicereader" + "go.opentelemetry.io/ebpf-profiler/process" "go.opentelemetry.io/ebpf-profiler/remotememory" + "go.opentelemetry.io/ebpf-profiler/reporter" "go.opentelemetry.io/ebpf-profiler/successfailurecounter" "go.opentelemetry.io/ebpf-profiler/support" "go.opentelemetry.io/ebpf-profiler/util" @@ -104,14 +107,16 @@ var ( // regex to extract a version from a string rubyVersionRegex = regexp.MustCompile(`^(\d+)\.(\d+)\.(\d+)$`) - unknownCfunc = libpf.Intern("") - cfuncDummyFile = libpf.Intern("") - rubyGcFrame = libpf.Intern("(garbage collection)") - rubyGcRunning = libpf.Intern("(running)") - rubyGcMarking = libpf.Intern("(marking)") - rubyGcSweeping = libpf.Intern("(sweeping)") - rubyGcCompacting = libpf.Intern("(compacting)") - rubyGcDummyFile = libpf.Intern("") + unknownCfunc = libpf.Intern("") + cfuncDummyFile = libpf.Intern("") + rubyGcFrame = libpf.Intern("(garbage collection)") + rubyGcRunning = libpf.Intern("(running)") + rubyGcMarking = libpf.Intern("(marking)") + rubyGcSweeping = libpf.Intern("(sweeping)") + rubyGcCompacting = libpf.Intern("(compacting)") + rubyGcDummyFile = libpf.Intern("") + rubyJitDummyFrame = libpf.Intern("") + rubyJitDummyFile = libpf.Intern("") // compiler check to make sure the needed interfaces are satisfied _ interpreter.Data = &rubyData{} _ interpreter.Instance = &rubyInstance{} @@ -376,6 +381,8 @@ func (r *rubyData) Attach(ebpf interpreter.EbpfHandler, pid libpf.PID, bias libp procInfo: &cdata, globalSymbolsAddr: r.globalSymbolsAddr + bias, addrToString: addrToString, + mappings: make(map[process.RawMapping]uint32), + prefixes: make(map[lpm.Prefix]uint32), memPool: sync.Pool{ New: func() any { buf := make([]byte, 512) @@ -425,6 +432,7 @@ type rubyInstance struct { // lastId is a cached copy index of the final entry in the global symbol table lastId uint32 + // globalSymbolsAddr is the offset of the global symbol table, for looking up ruby symbolic ids globalSymbolsAddr libpf.Address @@ -437,10 +445,31 @@ type rubyInstance struct { // maxSize is the largest number we did see in the last reporting interval for size // in getRubyLineNo. maxSize atomic.Uint32 + + // mappings is indexed by the Mapping to its generation. + // Entries are pruned each SynchronizeMappings call; the map size is bounded + // by the number of executable anonymous mappings for this process (typically + // a handful for JIT code pages plus any native gems with anonymous exec pages). + mappings map[process.RawMapping]uint32 + // prefixes is indexed by the prefix added to ebpf maps (to be cleaned up) to its generation + prefixes map[lpm.Prefix]uint32 + // mappingGeneration is the current generation (so old entries can be pruned) + mappingGeneration uint32 } func (r *rubyInstance) Detach(ebpf interpreter.EbpfHandler, pid libpf.PID) error { - return ebpf.DeleteProcData(libpf.Ruby, pid) + var err error + err = ebpf.DeleteProcData(libpf.Ruby, pid) + + for prefix := range r.prefixes { + if err2 := ebpf.DeletePidInterpreterMapping(pid, prefix); err2 != nil { + err = errors.Join(err, + fmt.Errorf("failed to remove ruby prefix 0x%x/%d: %v", + prefix.Key, prefix.Length, err2)) + } + } + + return err } // UpdateLibcInfo is called when libc introspection data becomes available. @@ -1115,6 +1144,15 @@ func (r *rubyInstance) Symbolize(ef libpf.EbpfFrame, frames *libpf.Frames, _ lib SourceLine: 0, }) return nil + case support.RubyFrameTypeJit: + label := rubyJitDummyFrame + frames.Append(&libpf.Frame{ + Type: libpf.RubyFrame, + FunctionName: label, + SourceFile: rubyJitDummyFile, + SourceLine: 0, + }) + return nil default: return fmt.Errorf("Unable to get CME or ISEQ from frame address (%d)", frameAddrType) } @@ -1244,6 +1282,123 @@ func profileFrameFullLabel(classPath, label, baseLabel, methodName libpf.String, return libpf.Intern(profileLabel) } +// findJITRegion detects the YJIT JIT code region from process memory mappings. +// YJIT reserves a large contiguous address range (typically 48-128 MiB) via mmap +// with PROT_NONE and then mprotects individual 16k codepages to r-x as needed. +// On systems with CONFIG_ANON_VMA_NAME, Ruby labels the region via prctl(PR_SET_VMA) +// giving it a path like "[anon:Ruby:rb_yjit_reserve_addr_space]". +// On systems without that config, we fall back to a heuristic: the first anonymous +// executable mapping (by address) is assumed to be the JIT region since YJIT +// initializes before any gems could create anonymous executable mappings. +// Returns (start, end, found). +func findJITRegion(mappings []process.RawMapping) (uint64, uint64, bool) { + var jitStart, jitEnd uint64 + labelFound := false + var heuristicStart, heuristicEnd uint64 + heuristicFound := false + + for idx := range mappings { + m := &mappings[idx] + + // Check for prctl-labeled JIT region. These mappings may be ---p (PROT_NONE) + // or r-xp depending on whether YJIT has activated codepages in this region. + if strings.Contains(m.Path, "jit_reserve_addr_space") { + if !labelFound || m.Vaddr < jitStart { + jitStart = m.Vaddr + } + if !labelFound || m.Vaddr+m.Length > jitEnd { + jitEnd = m.Vaddr + m.Length + } + labelFound = true + continue + } + + // Heuristic fallback: first anonymous executable mapping by address. + // Mappings from /proc/pid/maps are sorted by address, so the first + // match is the lowest address. + if !heuristicFound && m.IsExecutable() && m.IsAnonymous() { + heuristicStart = m.Vaddr + heuristicEnd = m.Vaddr + m.Length + heuristicFound = true + } + } + + if labelFound { + return jitStart, jitEnd, true + } + if heuristicFound { + return heuristicStart, heuristicEnd, true + } + return 0, 0, false +} + +func (r *rubyInstance) SynchronizeMappings(ebpf interpreter.EbpfHandler, + _ reporter.ExecutableReporter, pr process.Process, mappings []process.RawMapping) error { + pid := pr.PID() + r.mappingGeneration++ + + log.Debugf("Synchronizing ruby mappings") + + // Register LPM prefixes for executable anonymous mappings. + for idx := range mappings { + m := &mappings[idx] + if !m.IsExecutable() || !m.IsAnonymous() { + continue + } + + isNew := false + if _, exists := r.mappings[*m]; !exists { + isNew = true + log.Debugf("Enabling Ruby interpreter for %#x/%#x", m.Vaddr, m.Length) + } + r.mappings[*m] = r.mappingGeneration + + prefixes, err := lpm.CalculatePrefixList(m.Vaddr, m.Vaddr+m.Length) + if err != nil { + return fmt.Errorf("new anonymous mapping lpm failure %#x/%#x: %w", m.Vaddr, m.Length, err) + } + + for _, prefix := range prefixes { + if isNew { + if err := ebpf.UpdatePidInterpreterMapping(pid, prefix, + support.ProgUnwindRuby, 0, 0); err != nil { + return err + } + } + r.prefixes[prefix] = r.mappingGeneration + } + } + // Detect JIT region from all mappings and update proc data if changed. + jitStart, jitEnd, jitFound := findJITRegion(mappings) + if jitFound && (r.procInfo.Jit_start != jitStart || r.procInfo.Jit_end != jitEnd) { + r.procInfo.Jit_start = jitStart + r.procInfo.Jit_end = jitEnd + if err := ebpf.UpdateProcData(libpf.Ruby, pr.PID(), unsafe.Pointer(r.procInfo)); err != nil { + return err + } + log.Debugf("Updated JIT region %#x-%#x in ruby proc info", jitStart, jitEnd) + } + // Remove prefixes not seen + for prefix, gen := range r.prefixes { + if gen == r.mappingGeneration { + continue + } + if err := ebpf.DeletePidInterpreterMapping(pid, prefix); err != nil { + log.Debugf("Failed to delete Ruby prefix %#v: %v", prefix, err) + } + delete(r.prefixes, prefix) + } + for m, gen := range r.mappings { + if gen == r.mappingGeneration { + continue + } + log.Debugf("Disabling Ruby for %#x/%#x", m.Vaddr, m.Length) + delete(r.mappings, m) + } + + return nil +} + func (r *rubyInstance) GetAndResetMetrics() ([]metrics.Metric, error) { addrToStringStats := r.addrToString.ResetMetrics() diff --git a/interpreter/ruby/ruby_test.go b/interpreter/ruby/ruby_test.go index e3328b68b..c57f773ac 100644 --- a/interpreter/ruby/ruby_test.go +++ b/interpreter/ruby/ruby_test.go @@ -4,9 +4,11 @@ package ruby // import "go.opentelemetry.io/ebpf-profiler/interpreter/ruby" import ( + "debug/elf" "testing" "go.opentelemetry.io/ebpf-profiler/libpf" + "go.opentelemetry.io/ebpf-profiler/process" "github.com/stretchr/testify/assert" ) @@ -234,3 +236,129 @@ func TestProfileFrameFullLabel(t *testing.T) { }) } } + +func TestFindJITRegion(t *testing.T) { + execAnon := func(vaddr, length uint64) process.RawMapping { + return process.RawMapping{ + Vaddr: vaddr, + Length: length, + Flags: elf.PF_R | elf.PF_X, + Path: "", + } + } + labeled := func(vaddr, length uint64) process.RawMapping { + return process.RawMapping{ + Vaddr: vaddr, + Length: length, + Flags: 0, // ---p (PROT_NONE) + Path: "[anon:Ruby:rb_yjit_reserve_addr_space]", + } + } + fileBacked := func(vaddr, length uint64, path string) process.RawMapping { + return process.RawMapping{ + Vaddr: vaddr, + Length: length, + Flags: elf.PF_R | elf.PF_X, + Path: path, + } + } + + tests := []struct { + name string + mappings []process.RawMapping + wantStart uint64 + wantEnd uint64 + wantFound bool + }{ + { + name: "no mappings", + mappings: nil, + wantFound: false, + }, + { + name: "only file-backed mappings", + mappings: []process.RawMapping{ + fileBacked(0x400000, 0x1000, "/usr/bin/ruby"), + fileBacked(0x7f0000, 0x2000, "/lib/libc.so.6"), + }, + wantFound: false, + }, + { + name: "labeled JIT region (single mapping)", + mappings: []process.RawMapping{ + fileBacked(0x400000, 0x1000, "/usr/bin/ruby"), + labeled(0x7f17d99b9000, 0x8000000), + }, + wantStart: 0x7f17d99b9000, + wantEnd: 0x7f17d99b9000 + 0x8000000, + wantFound: true, + }, + { + name: "labeled JIT region with holes (multiple contiguous mappings)", + mappings: []process.RawMapping{ + fileBacked(0x400000, 0x1000, "/usr/bin/ruby"), + { + Vaddr: 0x7f17d99b9000, + Length: 0x15f000, + Flags: elf.PF_R | elf.PF_X, + Path: "[anon:Ruby:rb_yjit_reserve_addr_space]", + }, + { + Vaddr: 0x7f17d9b18000, + Length: 0x119000, + Flags: elf.PF_R | elf.PF_X, + Path: "[anon:Ruby:rb_yjit_reserve_addr_space]", + }, + { + Vaddr: 0x7f17d9c31000, + Length: 0x7d88000, + Flags: 0, // ---p reserved + Path: "[anon:Ruby:rb_yjit_reserve_addr_space]", + }, + }, + wantStart: 0x7f17d99b9000, + wantEnd: 0x7f17d9c31000 + 0x7d88000, + wantFound: true, + }, + { + name: "heuristic fallback - first anonymous executable mapping", + mappings: []process.RawMapping{ + fileBacked(0x400000, 0x1000, "/usr/bin/ruby"), + execAnon(0x7f0000100000, 0x4000), + execAnon(0x7f0000200000, 0x8000), + }, + wantStart: 0x7f0000100000, + wantEnd: 0x7f0000100000 + 0x4000, + wantFound: true, + }, + { + name: "labeled takes precedence over heuristic", + mappings: []process.RawMapping{ + execAnon(0x1000000, 0x4000), + labeled(0x7f0000000000, 0x3000000), + }, + wantStart: 0x7f0000000000, + wantEnd: 0x7f0000000000 + 0x3000000, + wantFound: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + start, end, found := findJITRegion(tt.mappings) + if found != tt.wantFound { + t.Errorf("found = %v, want %v", found, tt.wantFound) + return + } + if !found { + return + } + if start != tt.wantStart { + t.Errorf("start = %#x, want %#x", start, tt.wantStart) + } + if end != tt.wantEnd { + t.Errorf("end = %#x, want %#x", end, tt.wantEnd) + } + }) + } +} diff --git a/process/process.go b/process/process.go index 46e341945..213154931 100644 --- a/process/process.go +++ b/process/process.go @@ -275,14 +275,18 @@ func iterateMappings(mapsFile io.Reader, callback func(m RawMapping) bool) (uint var path string if inode == 0 { - if fields[5] == "[vdso]" { + switch fieldValue := fields[5]; { + case fieldValue == "[vdso]": // Map to something filename looking with synthesized inode path = VdsoPathName device = 0 inode = vdsoInode - } else if fields[5] == "" { + case fieldValue == "": // This is an anonymous mapping, keep it - } else { + case strings.HasPrefix(fieldValue, "[anon:"): + // This is an anonymous mapping named with prctl(PR_SET_VMA), keep the name + path = trimMappingPath(fieldValue) + default: // Ignore other mappings that are invalid, non-existent or are special pseudo-files continue } diff --git a/process/types.go b/process/types.go index f838586e9..52b77f49c 100644 --- a/process/types.go +++ b/process/types.go @@ -58,7 +58,14 @@ func (m *RawMapping) IsAnonymous() bool { } func (m *RawMapping) IsFileBacked() bool { - return m.Path != "" && !m.IsVDSO() && !m.IsMemFD() + return m.Path != "" && !m.IsVDSO() && !m.IsMemFD() && !m.IsPrctlNamed() +} + +// IsPrctlNamed returns true if the mapping was named via prctl(PR_SET_VMA), +// which gives anonymous mappings a path like "[anon:name]". These are still +// anonymous memory, not file-backed. +func (m *RawMapping) IsPrctlNamed() bool { + return strings.HasPrefix(m.Path, "[anon:") } func (m *RawMapping) IsMemFD() bool { diff --git a/support/ebpf/frametypes.h b/support/ebpf/frametypes.h index aae841d98..4026d4922 100644 --- a/support/ebpf/frametypes.h +++ b/support/ebpf/frametypes.h @@ -54,4 +54,5 @@ #define RUBY_FRAME_TYPE_CME_CFUNC 2 #define RUBY_FRAME_TYPE_ISEQ 3 #define RUBY_FRAME_TYPE_GC 4 +#define RUBY_FRAME_TYPE_JIT 5 #endif diff --git a/support/ebpf/ruby_tracer.ebpf.c b/support/ebpf/ruby_tracer.ebpf.c index 7acb6eac5..08e7a46ba 100644 --- a/support/ebpf/ruby_tracer.ebpf.c +++ b/support/ebpf/ruby_tracer.ebpf.c @@ -270,6 +270,11 @@ static EBPF_INLINE ErrorCode read_ruby_frame( // continue unwinding Ruby VM frames. Due to this issue, the ordering of Ruby and native // frames will almost certainly be incorrect for Ruby versions < 2.6. frame_type = RUBY_FRAME_TYPE_CME_CFUNC; + } else if (record->rubyUnwindState.jit_detected) { + // JIT is active but frame pointers are not available, so we cannot unwind + // through JIT frames to get back to native code. Push the cfunc inline + // instead of handing off to the native unwinder. + frame_type = RUBY_FRAME_TYPE_CME_CFUNC; } else { // We save this cfp on in the "Record" entry, and when we start the unwinder // again we'll push it so that the order is correct and the cfunc "owns" any native code we @@ -446,6 +451,22 @@ static EBPF_INLINE ErrorCode walk_ruby_stack( record->rubyUnwindState.cfunc_saved_frame = 0; } + // Detect if the CPU PC is in the JIT region. + bool in_jit = rubyinfo->jit_start > 0 && record->state.pc >= rubyinfo->jit_start && + record->state.pc < rubyinfo->jit_end; + + if (in_jit) { + record->rubyUnwindState.jit_detected = true; + + // Push a JIT frame with the raw machine PC. This can be used to + // symbolize the JIT frame via perf map later. + ErrorCode jit_error = + push_ruby(&record->state, trace, RUBY_FRAME_TYPE_JIT, (u64)record->state.pc, 0, 0); + if (jit_error) { + return jit_error; + } + } + for (u32 i = 0; i < FRAMES_PER_WALK_RUBY_STACK; ++i) { error = read_ruby_frame(record, rubyinfo, stack_ptr, next_unwinder); if (error != ERR_OK) @@ -453,7 +474,10 @@ static EBPF_INLINE ErrorCode walk_ruby_stack( if (last_stack_frame <= stack_ptr) { // We have processed all frames in the Ruby VM and can stop here. - *next_unwinder = PROG_UNWIND_NATIVE; + // If JIT was detected, the PC is in the JIT region and native + // unwinding would fail, so we stop. + *next_unwinder = record->rubyUnwindState.jit_detected ? PROG_UNWIND_STOP : PROG_UNWIND_NATIVE; + goto save_state; } else { // If we aren't at the end, advance the stack pointer to continue from the next frame stack_ptr += rubyinfo->size_of_control_frame_struct; diff --git a/support/ebpf/tracemgmt.h b/support/ebpf/tracemgmt.h index 24ba91d40..976853dc0 100644 --- a/support/ebpf/tracemgmt.h +++ b/support/ebpf/tracemgmt.h @@ -231,6 +231,7 @@ static inline EBPF_INLINE PerCPURecord *get_pristine_per_cpu_record() record->rubyUnwindState.stack_ptr = 0; record->rubyUnwindState.last_stack_frame = 0; record->rubyUnwindState.cfunc_saved_frame = 0; + record->rubyUnwindState.jit_detected = false; record->unwindersDone = 0; record->tailCalls = 0; record->ratelimitAction = RATELIMIT_ACTION_DEFAULT; diff --git a/support/ebpf/tracer.ebpf.amd64 b/support/ebpf/tracer.ebpf.amd64 index 9a44839d7..61bf81219 100644 Binary files a/support/ebpf/tracer.ebpf.amd64 and b/support/ebpf/tracer.ebpf.amd64 differ diff --git a/support/ebpf/tracer.ebpf.arm64 b/support/ebpf/tracer.ebpf.arm64 index 4d55359b6..a4a8d56d5 100644 Binary files a/support/ebpf/tracer.ebpf.arm64 and b/support/ebpf/tracer.ebpf.arm64 differ diff --git a/support/ebpf/types.h b/support/ebpf/types.h index 048e3c02c..adf677c09 100644 --- a/support/ebpf/types.h +++ b/support/ebpf/types.h @@ -488,6 +488,9 @@ typedef struct RubyProcInfo { // is reading gc state from objspace supported for this version? bool has_objspace; + + // JIT regions, for detecting if a native PC was JIT + u64 jit_start, jit_end; // Offsets and sizes of Ruby internal structs // rb_execution_context_struct offsets: @@ -728,6 +731,8 @@ typedef struct RubyUnwindState { void *last_stack_frame; // Frame for last cfunc before we switched to native unwinder u64 cfunc_saved_frame; + // Detect if JIT code ran in the process (at any time) + bool jit_detected; } RubyUnwindState; // Container for additional scratch space needed by the HotSpot unwinder. diff --git a/support/types.go b/support/types.go index e9cfd7143..72583d3a1 100644 --- a/support/types.go +++ b/support/types.go @@ -286,6 +286,8 @@ type RubyProcInfo struct { Tls_module_id uint32 Current_ctx_ptr uint64 Has_objspace bool + Jit_start uint64 + Jit_end uint64 Vm_stack uint8 Vm_stack_size uint8 Cfp uint8 @@ -334,7 +336,7 @@ const ( sizeof_ApmIntProcInfo = 0x8 sizeof_DotnetProcInfo = 0x4 sizeof_PHPProcInfo = 0x18 - sizeof_RubyProcInfo = 0x48 + sizeof_RubyProcInfo = 0x60 ) const ( @@ -395,6 +397,7 @@ const ( RubyFrameTypeCmeCfunc = 0x2 RubyFrameTypeIseq = 0x3 RubyFrameTypeGc = 0x4 + RubyFrameTypeJit = 0x5 ) var MetricsTranslation = []metrics.MetricID{ diff --git a/support/types_def.go b/support/types_def.go index 6c05aac10..7fc4d3080 100644 --- a/support/types_def.go +++ b/support/types_def.go @@ -205,6 +205,7 @@ const ( RubyFrameTypeCmeCfunc = C.RUBY_FRAME_TYPE_CME_CFUNC RubyFrameTypeIseq = C.RUBY_FRAME_TYPE_ISEQ RubyFrameTypeGc = C.RUBY_FRAME_TYPE_GC + RubyFrameTypeJit = C.RUBY_FRAME_TYPE_JIT ) var MetricsTranslation = []metrics.MetricID{ diff --git a/tools/coredump/testdata/amd64/ruby-3.4.7-yjit-loop.json b/tools/coredump/testdata/amd64/ruby-3.4.7-yjit-loop.json new file mode 100644 index 000000000..1258ee169 --- /dev/null +++ b/tools/coredump/testdata/amd64/ruby-3.4.7-yjit-loop.json @@ -0,0 +1,80 @@ +{ + "coredump-ref": "7918f8b3303996c779d53a84af6aea00b197529ae02e51337592c8bb0d69df09", + "threads": [ + { + "lwp": 780083, + "frames": [ + "libruby.so.3.4.7+0x33c55f", + "libruby.so.3.4.7+0x241573", + "+0 in :0", + "Range#each+0 in :0", + "Object#is_prime+0 in /home/dalehamel/repo/otel-profiler/tools/coredump/testsources/ruby/loop.rb:14", + "Object#sum_of_primes+0 in /home/dalehamel/repo/otel-profiler/tools/coredump/testsources/ruby/loop.rb:24", + "block (2 levels) in
+0 in /home/dalehamel/repo/otel-profiler/tools/coredump/testsources/ruby/loop.rb:34", + "Range#each+0 in :0", + "block in
+0 in /home/dalehamel/repo/otel-profiler/tools/coredump/testsources/ruby/loop.rb:33", + "Kernel#loop+0 in :168", + "
+0 in /home/dalehamel/repo/otel-profiler/tools/coredump/testsources/ruby/loop.rb:32" + ] + }, + { + "lwp": 780085, + "frames": [ + "libc.so.6+0x108f26", + "libruby.so.3.4.7+0x2d7c94", + "libc.so.6+0x891f4", + "libc.so.6+0x1098db" + ] + } + ], + "modules": [ + { + "ref": "7f2ca87f652f56b094462474b076749e90e689d0ecb9cb63c7679820b271b4e7", + "local-path": "/usr/lib/x86_64-linux-gnu/libm.so.6" + }, + { + "ref": "af3b11eb5acff87eae2761c08c645f3753b1a9245c56626ea4ae5e0d17d33ae5", + "local-path": "/opt/ruby-yjit/lib/ruby/3.4.0/x86_64-linux/enc/trans/transdb.so" + }, + { + "ref": "7376c9af0afd6e7698a64ee19de3c8a0199418664974384c70435a51c7ff7f3f", + "local-path": "/usr/lib/x86_64-linux-gnu/libgmp.so.10.4.1" + }, + { + "ref": "7e2a72b4c4b38c61e6962de6e3f4a5e9ae692e732c68deead10a7ce2135a7f68", + "local-path": "/usr/lib/x86_64-linux-gnu/libz.so.1.2.13" + }, + { + "ref": "58bef3cc4efc5b32f3ec7dfa978d4401be09e7d3245ee294570fce8bc0789364", + "local-path": "/opt/ruby-yjit/lib/ruby/3.4.0/x86_64-linux/enc/encdb.so" + }, + { + "ref": "593bb1d5355658e645f36e6b1f49832691b24e177209765914e4cce51499dbb4", + "local-path": "/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2" + }, + { + "ref": "79bc04a41245d221b215c2bab17aff0fe981257d0a2386b3a653d1945ea38d80", + "local-path": "/opt/ruby-yjit/bin/ruby" + }, + { + "ref": "bff8750fe719e6000791b88b11747dce8772c37118d0b2348044b70819d13835", + "local-path": "/usr/lib/x86_64-linux-gnu/libc.so.6" + }, + { + "ref": "5db18e8a8894ef4746eb8230855b638a5e52e782b2f10deede5f1dad846178bb", + "local-path": "/usr/lib/x86_64-linux-gnu/libcrypt.so.1.1.0" + }, + { + "ref": "2f5641d2f866c73d349141261fb08ae2ed11a2dd416263bd575afc998259173d", + "local-path": "/opt/ruby-yjit/lib/libruby.so.3.4.7" + }, + { + "ref": "631d4d8d03ee4735b853b4b8d8cf8aa18666233c3952e421863a9fbbf29c5467", + "local-path": "/opt/ruby-yjit/lib/ruby/3.4.0/x86_64-linux/monitor.so" + }, + { + "ref": "2bd1552c47799ef67e701e81d4383061fd76059868e446e63560f0dd0d5ec14e", + "local-path": "/usr/lib/x86_64-linux-gnu/libgcc_s.so.1" + } + ] +} diff --git a/tools/coredump/testdata/arm64/ruby-3.4.7-yjit-loop.json b/tools/coredump/testdata/arm64/ruby-3.4.7-yjit-loop.json new file mode 100644 index 000000000..26264d206 --- /dev/null +++ b/tools/coredump/testdata/arm64/ruby-3.4.7-yjit-loop.json @@ -0,0 +1,80 @@ +{ + "coredump-ref": "8c1e6935075c6928d549a8830a58514d864668a0a962c28c1562bedc29947034", + "threads": [ + { + "lwp": 53260, + "frames": [ + "libruby.so.3.4.7+0x1cda60", + "libruby.so.3.4.7+0x1cfa43", + "libruby.so.3.4.7+0x23001b", + "+0 in :0", + "Object#is_prime+0 in /Users/dalehamel/src/github.com/Shopify/otel-ebpf-pr1102/tools/coredump/testsources/ruby/loop.rb:14", + "Object#sum_of_primes+0 in /Users/dalehamel/src/github.com/Shopify/otel-ebpf-pr1102/tools/coredump/testsources/ruby/loop.rb:24", + "block (2 levels) in
+0 in /Users/dalehamel/src/github.com/Shopify/otel-ebpf-pr1102/tools/coredump/testsources/ruby/loop.rb:34", + "Range#each+0 in :0", + "block in
+0 in /Users/dalehamel/src/github.com/Shopify/otel-ebpf-pr1102/tools/coredump/testsources/ruby/loop.rb:33", + "Kernel#loop+0 in :168", + "
+0 in /Users/dalehamel/src/github.com/Shopify/otel-ebpf-pr1102/tools/coredump/testsources/ruby/loop.rb:32" + ] + }, + { + "lwp": 53262, + "frames": [ + "libc.so.6+0xebe34", + "libruby.so.3.4.7+0x2c240b", + "libc.so.6+0x8595b", + "libc.so.6+0xebb0b" + ] + } + ], + "modules": [ + { + "ref": "f572bd122e2b3b14d2f9d8d4345e1351fd9fbf26f9402e3e42ef36b65f833411", + "local-path": "/usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1" + }, + { + "ref": "e40a4700d11cbe5d31e18beb057a8cc1bed7471e301ed41090c88aa8f15194a8", + "local-path": "/opt/ruby-yjit/bin/ruby" + }, + { + "ref": "719e40832505ae5304a0fab773f3f04691b3a6693196147ad538fe89a67c6593", + "local-path": "/opt/ruby-yjit/lib/ruby/3.4.0/aarch64-linux/monitor.so" + }, + { + "ref": "2fd7c8f96a208532befb77cec1cfee8a08217a97b77d8eed382537fa6cc6e72c", + "local-path": "/usr/lib/aarch64-linux-gnu/libc.so.6" + }, + { + "ref": "ac92dc80c7d902e3852986e858ba682a981c15a12ec0e163bd4d56dde9b3dd6e", + "local-path": "/usr/lib/aarch64-linux-gnu/libm.so.6" + }, + { + "ref": "b41cebf0be70f869bf60228cb5761f875ced865b0f4016f544d82f7a9ded28b0", + "local-path": "/usr/lib/aarch64-linux-gnu/libcrypt.so.1.1.0" + }, + { + "ref": "5e1b2f482c2468a98837cd22d2a502886fbcc0731f968dccc3a1604843211406", + "local-path": "/opt/ruby-yjit/lib/libruby.so.3.4.7" + }, + { + "ref": "39b6701812ed7135f28df49352b3f6664c7a9f56880a3fe50c1b87cd7681db9b", + "local-path": "/usr/lib/aarch64-linux-gnu/libgmp.so.10.5.0" + }, + { + "ref": "170380b4e7ab28ec86eb090b48df90f84089392cb72fecd5067e5b7a4dc5239f", + "local-path": "/usr/lib/aarch64-linux-gnu/libz.so.1.3" + }, + { + "ref": "b35fabfedcf48048c544675e47782141df9479350469e7fe7c8f468ff0f0202e", + "local-path": "/opt/ruby-yjit/lib/ruby/3.4.0/aarch64-linux/enc/trans/transdb.so" + }, + { + "ref": "62342fd24aeea77a34f72d587d2fe0bdeb6306c2803932b61e852fe83eb919f8", + "local-path": "/opt/ruby-yjit/lib/ruby/3.4.0/aarch64-linux/enc/encdb.so" + }, + { + "ref": "1a8c3655af0173b4dd4a3444233778098f2e1a4c33bcc65752f3342f9eeae9a5", + "local-path": "/usr/lib/aarch64-linux-gnu/libgcc_s.so.1" + } + ] +}