diff --git a/nativeunwind/elfunwindinfo/elfgopclntab.go b/nativeunwind/elfunwindinfo/elfgopclntab.go index a0ac5b9f..2e361d1f 100644 --- a/nativeunwind/elfunwindinfo/elfgopclntab.go +++ b/nativeunwind/elfunwindinfo/elfgopclntab.go @@ -141,6 +141,14 @@ func IsGo118orNewer(magic uint32) bool { return magic == magicGo1_18 || magic == magicGo1_20 } +// IsGo121OrNewer returns true if magic matches with the Go 1.21 or newer. +// TODO(fg): This is actually checking for go1.21 right now, I need to figure +// out how to detect go1.21+ here. But it "works" b/c the existing coredump test +// cases are for go1.18. +func IsGo21orNewer(magic uint32) bool { + return magic == magicGo1_20 +} + // pclntabHeaderSignature returns a byte slice that can be // used to verify if some bytes represent a valid pclntab header. func pclntabHeaderSignature(arch elf.Machine) []byte { @@ -550,7 +558,19 @@ func (ee *elfExtractor) parseGoPclntab() error { return err } case elf.EM_AARCH64: - if err := parseArm64pclntabFunc(ee.deltas, fun, dataLen, pctab, i, + if !IsGo21orNewer(hdr.magic) { + // Before go1.21 frame pointers were not properly kept on arm64 + // when the Go runtime copies the stack during + // `runtime.morestack` calls: all old frame pointers are set to + // 0. + // + // https://github.com/golang/go/blob/c318f191/src/runtime/stack.go#L676 + // https://blog.felixge.de/waiting-for-go1-21-execution-tracing-with-less-than-one-percent-overhead/ + // + // We thus need to unwind with stack delta offsets. + strategy = strategyDeltasWithoutRBP + } + if err := parseArm64pclntabFunc(ee.deltas, fun, dataLen, pctab, strategy, i, hdr.quantum); err != nil { return err } @@ -628,7 +648,8 @@ func parseX86pclntabFunc(deltas *sdtypes.StackDeltaArray, fun *pclntabFunc, data // parseArm64pclntabFunc extracts interval information from ARM64 based pclntabFunc. func parseArm64pclntabFunc(deltas *sdtypes.StackDeltaArray, fun *pclntabFunc, - dataLen uintptr, pctab []byte, i uint64, quantum uint8) error { + dataLen uintptr, pctab []byte, strategy int, i uint64, quantum uint8) error { + if fun.pcspOff == 0 { // Some CGO functions don't have PCSP info: skip them. return nil @@ -637,13 +658,6 @@ func parseArm64pclntabFunc(deltas *sdtypes.StackDeltaArray, fun *pclntabFunc, return fmt.Errorf(".gopclntab func %v pcspOff = %d is invalid", i, fun.pcspOff) } - // On ARM64, frame pointers are not properly kept when the Go runtime copies the stack during - // `runtime.morestack` calls: all old frame pointers are set to 0. - // - // https://github.com/golang/go/blob/c318f191/src/runtime/stack.go#L676 - // - // We thus need to unwind with stack delta offsets. - hint := sdtypes.UnwindHintKeep p := newPcval(pctab[fun.pcspOff:], uint(fun.startPc), quantum) for ok := true; ok; ok = p.step() { @@ -651,6 +665,18 @@ func parseArm64pclntabFunc(deltas *sdtypes.StackDeltaArray, fun *pclntabFunc, if p.val == 0 { // Return instruction, function prologue or leaf function body: unwind via LR. info = sdtypes.UnwindInfoLR + } else if strategy == strategyFramePointer { + param, ok := sdtypes.PackDerefParam(0, 8) + if !ok { + panic("bad") + } + // Use stack frame-pointer delta + info = sdtypes.UnwindInfo{ + Opcode: sdtypes.UnwindOpcodeBaseFP | sdtypes.UnwindOpcodeFlagDeref, + Param: param, + FPOpcode: sdtypes.UnwindOpcodeBaseSP, + FPParam: 0, + } } else { // Regular basic block in the function body: unwind via SP. info = sdtypes.UnwindInfo{ diff --git a/support/ebpf/native_stack_trace.ebpf.c b/support/ebpf/native_stack_trace.ebpf.c index 959099cb..5eb8fd49 100644 --- a/support/ebpf/native_stack_trace.ebpf.c +++ b/support/ebpf/native_stack_trace.ebpf.c @@ -527,6 +527,11 @@ static ErrorCode unwind_one_frame(u64 pid, u32 frame_idx, struct UnwindState *st } } + if (state->fp == 0) { + *stop = true; + return ERR_OK; + } + UnwindInfo *info = bpf_map_lookup_elem(&unwind_info_array, &unwindInfo); if (!info) { increment_metric(metricID_UnwindNativeErrBadUnwindInfoIndex); @@ -589,7 +594,7 @@ static ErrorCode unwind_one_frame(u64 pid, u32 frame_idx, struct UnwindState *st // this implies that if no other changes are applied to the stack such // as alloca(), following the prolog SP/FP points to the frame record // itself, in such a case FP offset will be equal to 8 - if (info->fpParam == 8) { + if (info->fpParam == 8 || (info->opcode == (UNWIND_OPCODEF_DEREF | UNWIND_OPCODE_BASE_FP))) { // we can assume the presence of frame pointers if (info->fpOpcode != UNWIND_OPCODE_BASE_LR) { // FP precedes the RA on the stack (Aarch64 ABI requirement) diff --git a/support/ebpf/tracer.ebpf.release.arm64 b/support/ebpf/tracer.ebpf.release.arm64 index ecac9895..bdadaa9b 100644 Binary files a/support/ebpf/tracer.ebpf.release.arm64 and b/support/ebpf/tracer.ebpf.release.arm64 differ diff --git a/tools/coredump/testdata/arm64/hello.1900091.hello5.json b/tools/coredump/testdata/arm64/hello.1900091.hello5.json new file mode 100644 index 00000000..0fd325f6 --- /dev/null +++ b/tools/coredump/testdata/arm64/hello.1900091.hello5.json @@ -0,0 +1,75 @@ +{ + "coredump-ref": "f941d6492b38f927820f32ab65196c88a357154e4d04a1502a02e268421448c0", + "threads": [ + { + "lwp": 1900091, + "frames": [ + "hello+0x92d4c", + "hello+0x92ceb", + "hello+0x92c33", + "hello+0x92bef", + "hello+0x92afb", + "hello+0x92ac7", + "hello+0x92dcb", + "hello+0x456fb", + "hello+0x73b03" + ] + }, + { + "lwp": 1900101, + "frames": [ + "hello+0x748e4", + "hello+0x530fb", + "hello+0x48a47", + "hello+0x48997", + "hello+0x716af" + ] + }, + { + "lwp": 1900102, + "frames": [ + "hello+0x74f4c", + "hello+0x3feeb", + "hello+0x1aefb", + "hello+0x4a723", + "hello+0x4c69f", + "hello+0x4dae7", + "hello+0x4e17f", + "hello+0x71723" + ] + }, + { + "lwp": 1900103, + "frames": [ + "hello+0x74f4c", + "hello+0x3feeb", + "hello+0x1aefb", + "hello+0x4a723", + "hello+0x4b37b", + "hello+0x4daab", + "hello+0x4e17f", + "hello+0x71723" + ] + }, + { + "lwp": 1900104, + "frames": [ + "hello+0x74f4c", + "hello+0x3feeb", + "hello+0x1aefb", + "hello+0x4a723", + "hello+0x4c69f", + "hello+0x4dae7", + "hello+0x48a77", + "hello+0x48997", + "hello+0x716af" + ] + } + ], + "modules": [ + { + "ref": "a2dbc7c1f120e00e2da9881afad3eccb2a4b6d0469d28851acb59c4e570f4012", + "local-path": "/home/bits/go/src/github.com/open-telemetry/opentelemetry-ebpf-profiler/tools/coredump/hello" + } + ] +}