Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

poc: use frame pointer unwinding for go arm64 #241

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 35 additions & 9 deletions nativeunwind/elfunwindinfo/elfgopclntab.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,14 @@
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 {
Expand Down Expand Up @@ -550,7 +558,19 @@
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
}
Expand Down Expand Up @@ -628,7 +648,8 @@

// 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 {

Check failure on line 651 in nativeunwind/elfunwindinfo/elfgopclntab.go

View workflow job for this annotation

GitHub Actions / Lint (arm64)

unnecessary leading newline (whitespace)

if fun.pcspOff == 0 {
// Some CGO functions don't have PCSP info: skip them.
return nil
Expand All @@ -637,20 +658,25 @@
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() {
var info sdtypes.UnwindInfo
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{
Expand Down
7 changes: 6 additions & 1 deletion support/ebpf/native_stack_trace.ebpf.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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)
Expand Down
Binary file modified support/ebpf/tracer.ebpf.release.arm64
Binary file not shown.
75 changes: 75 additions & 0 deletions tools/coredump/testdata/arm64/hello.1900091.hello5.json
Original file line number Diff line number Diff line change
@@ -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"
}
]
}
Loading