diff --git a/interpreter/loaderinfo.go b/interpreter/loaderinfo.go index 093569585..4d6736b44 100644 --- a/interpreter/loaderinfo.go +++ b/interpreter/loaderinfo.go @@ -68,3 +68,7 @@ func (i *LoaderInfo) FileName() string { func (i *LoaderInfo) Gaps() []util.Range { return i.gaps } + +func (i *LoaderInfo) ElfOpener() pfelf.ELFOpener { + return i.elfRef.ELFOpener +} diff --git a/interpreter/python/python.go b/interpreter/python/python.go index c73813ead..a7cafe740 100644 --- a/interpreter/python/python.go +++ b/interpreter/python/python.go @@ -750,19 +750,9 @@ func Loader(ebpf interpreter.EbpfHandler, info *interpreter.LoaderInfo) (interpr autoTLSKey += 4 } - // The Python main interpreter loop history in CPython git is: - // - //nolint:lll - // 87af12bff33 v3.11 2022-02-15 _PyEval_EvalFrameDefault(PyThreadState*,_PyInterpreterFrame*,int) - // ae0a2b75625 v3.10 2021-06-25 _PyEval_EvalFrameDefault(PyThreadState*,_interpreter_frame*,int) - // 0b72b23fb0c v3.9 2020-03-12 _PyEval_EvalFrameDefault(PyThreadState*,PyFrameObject*,int) - // 3cebf938727 v3.6 2016-09-05 _PyEval_EvalFrameDefault(PyFrameObject*,int) - // 49fd7fa4431 v3.0 2006-04-21 PyEval_EvalFrameEx(PyFrameObject*,int) - interpRanges, err := info.GetSymbolAsRanges("_PyEval_EvalFrameDefault") + interpRanges, err := findInterpreterRanges(ef, info) if err != nil { - if interpRanges, err = info.GetSymbolAsRanges("PyEval_EvalFrameEx"); err != nil { - return nil, err - } + return nil, err } pd := &pythonData{ @@ -837,3 +827,46 @@ func Loader(ebpf interpreter.EbpfHandler, info *interpreter.LoaderInfo) (interpr return pd, nil } + +func findInterpreterRanges(ef *pfelf.File, info *interpreter.LoaderInfo) ([]util.Range, error) { + // The Python main interpreter loop history in CPython git is: + // + //nolint:lll + // 87af12bff33 v3.11 2022-02-15 _PyEval_EvalFrameDefault(PyThreadState*,_PyInterpreterFrame*,int) + // ae0a2b75625 v3.10 2021-06-25 _PyEval_EvalFrameDefault(PyThreadState*,_interpreter_frame*,int) + // 0b72b23fb0c v3.9 2020-03-12 _PyEval_EvalFrameDefault(PyThreadState*,PyFrameObject*,int) + // 3cebf938727 v3.6 2016-09-05 _PyEval_EvalFrameDefault(PyFrameObject*,int) + // 49fd7fa4431 v3.0 2006-04-21 PyEval_EvalFrameEx(PyFrameObject*,int) + interpRanges, err := info.GetSymbolAsRanges("_PyEval_EvalFrameDefault") + if err != nil { + if interpRanges, err = info.GetSymbolAsRanges("PyEval_EvalFrameEx"); err != nil { + return nil, err + } + return interpRanges, nil + } + if len(interpRanges) == 0 { + return nil, errors.New("no _PyEval_EvalFrameDefault/PyEval_EvalFrameEx symbol found") + } + gnuBuildID, _ := ef.GetBuildID() + if coldRange, ok := interpreterColdRanges[gnuBuildID]; ok { + interpRanges = append(interpRanges, coldRange) + return interpRanges, nil + } + // TODO: find cold ranges for unknown binaries (not in interpreterColdRanges) + // see tools/coredump/testdata/amd64/alpine320-nobuildid.json + // https://github.com/open-telemetry/opentelemetry-ebpf-profiler/issues/416 + return interpRanges, nil +} + +var interpreterColdRanges = map[string]util.Range{ + // alpine:3.20 + "4913fe1380aebd0f4f0d69411b797d7e22d2799b": { + Start: 0x88a9a, + End: 0x88a9a + 0xdcdf, + }, + // alpine 3.21 + "f0f26a21d40d3c089975a8b136fc2469df40a0e6": { + Start: 0x89228, + End: 0x89228 + 0x35d9, + }, +} diff --git a/processmanager/ebpf/ebpf.go b/processmanager/ebpf/ebpf.go index 71308f989..6eeeea14b 100644 --- a/processmanager/ebpf/ebpf.go +++ b/processmanager/ebpf/ebpf.go @@ -253,29 +253,42 @@ func LoadMaps(ctx context.Context, maps map[string]*cebpf.Map) (EbpfHandler, err // UpdateInterpreterOffsets adds the given moduleRanges to the eBPF map interpreterOffsets. func (impl *ebpfMapsImpl) UpdateInterpreterOffsets(ebpfProgIndex uint16, fileID host.FileID, offsetRanges []util.Range) error { - if offsetRanges == nil { - return errors.New("offsetRanges is nil") - } - for _, offsetRange := range offsetRanges { - // The keys of this map are executable-id-and-offset-into-text entries, and - // the offset_range associated with them gives the precise area in that page - // where the main interpreter loop is located. This is required to unwind - // nicely from native code into interpreted code. - key := uint64(fileID) - value := C.OffsetRange{ - lower_offset: C.u64(offsetRange.Start), - upper_offset: C.u64(offsetRange.End), - program_index: C.u16(ebpfProgIndex), - } - if err := impl.interpreterOffsets.Update(unsafe.Pointer(&key), unsafe.Pointer(&value), - cebpf.UpdateAny); err != nil { - log.Fatalf("Failed to place interpreter range in map: %v", err) - } + key, value, err := InterpreterOffsetKeyValue(ebpfProgIndex, fileID, offsetRanges) + if err != nil { + return err + } + if err := impl.interpreterOffsets.Update(unsafe.Pointer(&key), unsafe.Pointer(&value), + cebpf.UpdateAny); err != nil { + log.Fatalf("Failed to place interpreter range in map: %v", err) } return nil } +func InterpreterOffsetKeyValue(ebpfProgIndex uint16, fileID host.FileID, + offsetRanges []util.Range) (key uint64, value C.OffsetRange, err error) { + if len(offsetRanges) != 1 && len(offsetRanges) != 2 { + return 0, C.OffsetRange{}, fmt.Errorf("ivalid ranges %+v", offsetRanges) + } + // The keys of this map are executable-id-and-offset-into-text entries, and + // the offset_range associated with them gives the precise area in that page + // where the main interpreter loop is located. This is required to unwind + // nicely from native code into interpreted code. + key = uint64(fileID) + first := offsetRanges[0] + value = C.OffsetRange{ + lower_offset1: C.u64(first.Start), + upper_offset1: C.u64(first.End), + program_index: C.u16(ebpfProgIndex), + } + if len(offsetRanges) == 2 { + second := offsetRanges[1] + value.lower_offset2 = C.u64(second.Start) + value.upper_offset2 = C.u64(second.End) + } + return key, value, nil +} + // getInterpreterTypeMap returns the eBPF map for the given typ // or an error if typ is not supported. func (impl *ebpfMapsImpl) getInterpreterTypeMap(typ libpf.InterpreterType) (*cebpf.Map, error) { diff --git a/support/ebpf/native_stack_trace.ebpf.c b/support/ebpf/native_stack_trace.ebpf.c index 62c6d5c63..d1f22c82c 100644 --- a/support/ebpf/native_stack_trace.ebpf.c +++ b/support/ebpf/native_stack_trace.ebpf.c @@ -60,7 +60,7 @@ bpf_map_def SEC("maps") interpreter_offsets = { .type = BPF_MAP_TYPE_HASH, .key_size = sizeof(u64), .value_size = sizeof(OffsetRange), - .max_entries = 32, + .max_entries = 256, }; // Maps fileID and page to information of stack deltas associated with that page. diff --git a/support/ebpf/tracemgmt.h b/support/ebpf/tracemgmt.h index cc2816ac9..6fba06224 100644 --- a/support/ebpf/tracemgmt.h +++ b/support/ebpf/tracemgmt.h @@ -422,7 +422,9 @@ static inline int get_next_interpreter(PerCPURecord *record) // Check if the section id happens to be in the interpreter map. OffsetRange *range = bpf_map_lookup_elem(&interpreter_offsets, §ion_id); if (range != 0) { - if ((section_offset >= range->lower_offset) && (section_offset <= range->upper_offset)) { + if ( + ((section_offset >= range->lower_offset1) && (section_offset <= range->upper_offset1)) || + ((section_offset >= range->lower_offset2) && (section_offset <= range->upper_offset2))) { DEBUG_PRINT("interpreter_offsets match %d", range->program_index); if (!unwinder_is_done(record, range->program_index)) { increment_metric(metricID_UnwindCallInterpreter); diff --git a/support/ebpf/tracer.ebpf.release.amd64 b/support/ebpf/tracer.ebpf.release.amd64 index c9fd60c3b..942626061 100644 Binary files a/support/ebpf/tracer.ebpf.release.amd64 and b/support/ebpf/tracer.ebpf.release.amd64 differ diff --git a/support/ebpf/tracer.ebpf.release.arm64 b/support/ebpf/tracer.ebpf.release.arm64 index 27aa5b678..1c7a39f1a 100644 Binary files a/support/ebpf/tracer.ebpf.release.arm64 and b/support/ebpf/tracer.ebpf.release.arm64 differ diff --git a/support/ebpf/types.h b/support/ebpf/types.h index 9a0cb63ba..c889501d5 100644 --- a/support/ebpf/types.h +++ b/support/ebpf/types.h @@ -773,8 +773,11 @@ typedef struct StackDeltaPageInfo { // the upper boundary of the loop, and the relevant index to call in the prog // array. typedef struct OffsetRange { - u64 lower_offset; - u64 upper_offset; + u64 lower_offset1; + u64 upper_offset1; + // extra range for .cold interpreter chunk + u64 lower_offset2; + u64 upper_offset2; u16 program_index; // The interpreter-specific program index to call. } OffsetRange; diff --git a/tools/coredump/coredump_test.go b/tools/coredump/coredump_test.go index 26bbde68f..92a05b65d 100644 --- a/tools/coredump/coredump_test.go +++ b/tools/coredump/coredump_test.go @@ -11,6 +11,10 @@ import ( ) func TestCoreDumps(t *testing.T) { + var skip = map[string]bool{ + // https://github.com/open-telemetry/opentelemetry-ebpf-profiler/issues/416 + "testdata/amd64/alpine320-nobuildid.json": true, + } cases, err := findTestCases(true) require.NoError(t, err) require.NotEmpty(t, cases) @@ -19,6 +23,9 @@ func TestCoreDumps(t *testing.T) { require.NoError(t, err) for _, filename := range cases { + if skip[filename] { + continue + } filename := filename t.Run(filename, func(t *testing.T) { testCase, err := readTestCase(filename) diff --git a/tools/coredump/ebpfmaps.go b/tools/coredump/ebpfmaps.go index e478be970..412864b4f 100644 --- a/tools/coredump/ebpfmaps.go +++ b/tools/coredump/ebpfmaps.go @@ -44,13 +44,11 @@ func (emc *ebpfMapsCoredump) CollectMetrics() []metrics.Metric { func (emc *ebpfMapsCoredump) UpdateInterpreterOffsets(ebpfProgIndex uint16, fileID host.FileID, offsetRanges []util.Range) error { - offsetRange := offsetRanges[0] - value := C.OffsetRange{ - lower_offset: C.u64(offsetRange.Start), - upper_offset: C.u64(offsetRange.End), - program_index: C.u16(ebpfProgIndex), + key, value, err := pmebpf.InterpreterOffsetKeyValue(ebpfProgIndex, fileID, offsetRanges) + if err != nil { + return err } - emc.ctx.addMap(&C.interpreter_offsets, C.u64(fileID), libpf.SliceFrom(&value)) + emc.ctx.addMap(&C.interpreter_offsets, C.u64(key), libpf.SliceFrom(&value)) return nil } diff --git a/tools/coredump/testdata/amd64/alpine320-nobuildid.json b/tools/coredump/testdata/amd64/alpine320-nobuildid.json new file mode 100644 index 000000000..b8aecc8f5 --- /dev/null +++ b/tools/coredump/testdata/amd64/alpine320-nobuildid.json @@ -0,0 +1,87 @@ +{ + "coredump-ref": "3eb6bae4e0089983f436d6bbd4a0b7ee0d72738eac29f15495494f53bc82263d", + "threads": [ + { + "lwp": 80, + "frames": [ + "fib+1 in /mnt/trash/qwe.py:2", + "fib+3 in /mnt/trash/qwe.py:4", + "fib+3 in /mnt/trash/qwe.py:4", + "fib+3 in /mnt/trash/qwe.py:4", + "fib+3 in /mnt/trash/qwe.py:4", + "fib+3 in /mnt/trash/qwe.py:4", + "fib+3 in /mnt/trash/qwe.py:4", + "fib+3 in /mnt/trash/qwe.py:4", + "fib+3 in /mnt/trash/qwe.py:4", + "fib+3 in /mnt/trash/qwe.py:4", + "fib+3 in /mnt/trash/qwe.py:4", + "fib+3 in /mnt/trash/qwe.py:4", + "fib+3 in /mnt/trash/qwe.py:4", + "fib+3 in /mnt/trash/qwe.py:4", + "fib+3 in /mnt/trash/qwe.py:4", + "fib+3 in /mnt/trash/qwe.py:4", + "fib+3 in /mnt/trash/qwe.py:4", + "fib+3 in /mnt/trash/qwe.py:4", + "fib+3 in /mnt/trash/qwe.py:4", + "fib+3 in /mnt/trash/qwe.py:4", + "fib+3 in /mnt/trash/qwe.py:4", + "fib+3 in /mnt/trash/qwe.py:4", + "fib+3 in /mnt/trash/qwe.py:4", + "fib+3 in /mnt/trash/qwe.py:4", + "fib+3 in /mnt/trash/qwe.py:4", + "fib+3 in /mnt/trash/qwe.py:4", + "fib+3 in /mnt/trash/qwe.py:4", + "fib+3 in /mnt/trash/qwe.py:4", + "fib+3 in /mnt/trash/qwe.py:4", + "fib+3 in /mnt/trash/qwe.py:4", + "fib+3 in /mnt/trash/qwe.py:4", + "fib+3 in /mnt/trash/qwe.py:4", + "fib+3 in /mnt/trash/qwe.py:4", + "fib+3 in /mnt/trash/qwe.py:4", + "fib+3 in /mnt/trash/qwe.py:4", + "fib+3 in /mnt/trash/qwe.py:4", + "fib+3 in /mnt/trash/qwe.py:4", + "+6 in /mnt/trash/qwe.py:7", + "+0 in :1", + "libpython3.12.so.1.0+0x95b58", + "libpython3.12.so.1.0+0x1ecfe2", + "libpython3.12.so.1.0+0x2136b6", + "libpython3.12.so.1.0+0x20c91b", + "libpython3.12.so.1.0+0x226f72", + "libpython3.12.so.1.0+0x2264f1", + "libpython3.12.so.1.0+0x2260d3", + "libpython3.12.so.1.0+0x21f742", + "libpython3.12.so.1.0+0x1d6ed6", + "ld-musl-x86_64.so.1+0x1c709", + "python3.12+0x1045", + "" + ] + } + ], + "modules": [ + { + "ref": "983f4cac5caf833fbf7d5d28ac8d6a55d3cbab6152d37a246af1a9991f72d8b1", + "local-path": "/usr/lib/debug/lib/ld-musl-x86_64.so.1.debug" + }, + { + "ref": "497dd0d2b4a80bfd11339306c84aa752d811f612a398cb526a0a9ac2f426c0b8", + "local-path": "/usr/lib/libpython3.12.so.1.0" + }, + { + "ref": "02a162bd137903ef2511ccb6edc32eaa63a9c9c66c8474f6ce7d9d47fc5a1e71", + "local-path": "/usr/lib/debug/usr/lib/libpython3.12.so.1.0.debug" + }, + { + "ref": "9cf22fb673cad95247584fd0bc21b95aa37ae152a8bd01022a494e4f7ec3854c", + "local-path": "/usr/bin/python3.12" + }, + { + "ref": "f2c83c6e5aea0e1e42b69e344a2eb0aac35b361cad7497157f727dbe30b8565e", + "local-path": "/usr/lib/debug/usr/bin/python3.12.debug" + }, + { + "ref": "c9cbfe6a266c104f74629a257e2017020cba7a0da97caefa4c1d068ea6fe698c", + "local-path": "/lib/ld-musl-x86_64.so.1" + } + ] +} diff --git a/tools/coredump/testdata/amd64/alpine320.json b/tools/coredump/testdata/amd64/alpine320.json new file mode 100644 index 000000000..61e00b91a --- /dev/null +++ b/tools/coredump/testdata/amd64/alpine320.json @@ -0,0 +1,85 @@ +{ + "coredump-ref": "4652115623df00987a1a480431a360edbd67b0795e9529543d40be190e37c74d", + "threads": [ + { + "lwp": 10, + "frames": [ + "libpython3.12.so.1.0+0x19b80d", + "fib+3 in //mnt/trash/qwe.py:4", + "fib+3 in //mnt/trash/qwe.py:4", + "fib+3 in //mnt/trash/qwe.py:4", + "fib+3 in //mnt/trash/qwe.py:4", + "fib+3 in //mnt/trash/qwe.py:4", + "fib+3 in //mnt/trash/qwe.py:4", + "fib+3 in //mnt/trash/qwe.py:4", + "fib+3 in //mnt/trash/qwe.py:4", + "fib+3 in //mnt/trash/qwe.py:4", + "fib+3 in //mnt/trash/qwe.py:4", + "fib+3 in //mnt/trash/qwe.py:4", + "fib+3 in //mnt/trash/qwe.py:4", + "fib+3 in //mnt/trash/qwe.py:4", + "fib+3 in //mnt/trash/qwe.py:4", + "fib+3 in //mnt/trash/qwe.py:4", + "fib+3 in //mnt/trash/qwe.py:4", + "fib+3 in //mnt/trash/qwe.py:4", + "fib+3 in //mnt/trash/qwe.py:4", + "fib+3 in //mnt/trash/qwe.py:4", + "fib+3 in //mnt/trash/qwe.py:4", + "fib+3 in //mnt/trash/qwe.py:4", + "fib+3 in //mnt/trash/qwe.py:4", + "fib+3 in //mnt/trash/qwe.py:4", + "fib+3 in //mnt/trash/qwe.py:4", + "fib+3 in //mnt/trash/qwe.py:4", + "fib+3 in //mnt/trash/qwe.py:4", + "fib+3 in //mnt/trash/qwe.py:4", + "fib+3 in //mnt/trash/qwe.py:4", + "fib+3 in //mnt/trash/qwe.py:4", + "fib+3 in //mnt/trash/qwe.py:4", + "fib+3 in //mnt/trash/qwe.py:4", + "fib+3 in //mnt/trash/qwe.py:4", + "fib+3 in //mnt/trash/qwe.py:4", + "fib+3 in //mnt/trash/qwe.py:4", + "+6 in //mnt/trash/qwe.py:7", + "+0 in :1", + "libpython3.12.so.1.0+0x91617", + "libpython3.12.so.1.0+0x1ecfe2", + "libpython3.12.so.1.0+0x2136b6", + "libpython3.12.so.1.0+0x20c91b", + "libpython3.12.so.1.0+0x226f72", + "libpython3.12.so.1.0+0x2264f1", + "libpython3.12.so.1.0+0x2260d3", + "libpython3.12.so.1.0+0x21f742", + "libpython3.12.so.1.0+0x1d6ed6", + "ld-musl-x86_64.so.1+0x1c709", + "python3.12+0x1045", + "" + ] + } + ], + "modules": [ + { + "ref": "a621ea443bba20ffbdb5322633c5a1bf439905baa5822973b2cafc4106c64789", + "local-path": "/usr/lib/libpython3.12.so.1.0" + }, + { + "ref": "9ef79f767e2c2cfde043a023acb0021f6261f560b27035c961d282cecc026db9", + "local-path": "/usr/lib/debug/usr/lib/libpython3.12.so.1.0.debug" + }, + { + "ref": "c9cbfe6a266c104f74629a257e2017020cba7a0da97caefa4c1d068ea6fe698c", + "local-path": "/lib/ld-musl-x86_64.so.1" + }, + { + "ref": "983f4cac5caf833fbf7d5d28ac8d6a55d3cbab6152d37a246af1a9991f72d8b1", + "local-path": "/usr/lib/debug/lib/ld-musl-x86_64.so.1.debug" + }, + { + "ref": "9cf22fb673cad95247584fd0bc21b95aa37ae152a8bd01022a494e4f7ec3854c", + "local-path": "/usr/bin/python3.12" + }, + { + "ref": "f2c83c6e5aea0e1e42b69e344a2eb0aac35b361cad7497157f727dbe30b8565e", + "local-path": "/usr/lib/debug/usr/bin/python3.12.debug" + } + ] +}