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

support arm64 vdso functions with a frame #195

Merged
merged 6 commits into from
Oct 28, 2024
Merged
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
9 changes: 2 additions & 7 deletions nativeunwind/elfunwindinfo/elfgopclntab.go
Original file line number Diff line number Diff line change
Expand Up @@ -593,7 +593,7 @@ func parseX86pclntabFunc(deltas *sdtypes.StackDeltaArray, fun *pclntabFunc, data
// Use stack frame-pointer delta
deltas.Add(sdtypes.StackDelta{
Address: fun.startPc,
Info: sdtypes.UnwindInfoFramePointer,
Info: sdtypes.UnwindInfoFramePointerX64,
})
return nil
case fun.pcspOff != 0:
Expand Down Expand Up @@ -650,12 +650,7 @@ func parseArm64pclntabFunc(deltas *sdtypes.StackDeltaArray, fun *pclntabFunc,
var info sdtypes.UnwindInfo
if p.val == 0 {
// Return instruction, function prologue or leaf function body: unwind via LR.
info = sdtypes.UnwindInfo{
Opcode: sdtypes.UnwindOpcodeBaseSP,
Param: 0,
FPOpcode: sdtypes.UnwindOpcodeBaseLR,
FPParam: 0,
}
info = sdtypes.UnwindInfoLR
} else {
// Regular basic block in the function body: unwind via SP.
info = sdtypes.UnwindInfo{
Expand Down
10 changes: 8 additions & 2 deletions nativeunwind/stackdeltatypes/stackdeltatypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,20 @@ var UnwindInfoStop = UnwindInfo{Opcode: UnwindOpcodeCommand, Param: UnwindComman
// UnwindInfoSignal is the stack delta info indicating signal return frame.
var UnwindInfoSignal = UnwindInfo{Opcode: UnwindOpcodeCommand, Param: UnwindCommandSignal}

// UnwindInfoFramePointer contains the description to unwind a frame with valid frame pointer.
var UnwindInfoFramePointer = UnwindInfo{
// UnwindInfoFramePointerX64 contains the description to unwind a x86-64 frame pointer frame.
var UnwindInfoFramePointerX64 = UnwindInfo{
Opcode: UnwindOpcodeBaseFP,
Param: 16,
FPOpcode: UnwindOpcodeBaseCFA,
FPParam: -16,
}

// UnwindInfoLR contains the description to unwind ARM64 function without a frame (LR only)
var UnwindInfoLR = UnwindInfo{
Opcode: UnwindOpcodeBaseSP,
FPOpcode: UnwindOpcodeBaseLR,
}

// StackDelta defines the start address for the delta interval, along with
// the unwind information.
type StackDelta struct {
Expand Down
7 changes: 6 additions & 1 deletion processmanager/processinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,12 @@ func (pm *ProcessManager) getELFInfo(pr process.Process, mapping *process.Mappin
info.fileID = hostFileID
info.addressMapper = ef.GetAddressMapper()
if mapping.IsVDSO() {
info.err = pm.insertSynthStackDeltas(hostFileID, ef)
intervals := createVDSOSyntheticRecord(ef)
if intervals.Deltas != nil {
if err := pm.AddSynthIntervalData(hostFileID, intervals); err != nil {
info.err = fmt.Errorf("failed to add synthetic deltas: %w", err)
}
}
}
// Do not cache the entry if synthetic stack delta loading failed,
// so next encounter of the VDSO will retry loading them.
Expand Down
119 changes: 119 additions & 0 deletions processmanager/synthdeltas.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package processmanager // import "go.opentelemetry.io/ebpf-profiler/processmanager"

import (
"sort"

aa "golang.org/x/arch/arm64/arm64asm"

ah "go.opentelemetry.io/ebpf-profiler/armhelpers"
"go.opentelemetry.io/ebpf-profiler/libpf"
"go.opentelemetry.io/ebpf-profiler/libpf/pfelf"
sdtypes "go.opentelemetry.io/ebpf-profiler/nativeunwind/stackdeltatypes"
)

// regFP is the arm64 frame-pointer register (x29) number
const regFP = 29

// regLR is the arm64 link register (x30) number
const regLR = 30

// createVDSOSyntheticRecordNone returns no synthetic deltas when the kernel vDSO
// is known to have valid unwind information.
func createVDSOSyntheticRecordNone(_ *pfelf.File) sdtypes.IntervalData {
return sdtypes.IntervalData{}
}

// createVDSOSyntheticRecordArm64 creates generated stack-delta records for ARM64 vDSO.
// ARM64 kernel vDSO does not have proper `.eh_frame` section, so we synthesize it here.
// This assumes LR based unwinding for most of the vDSO. Additionally the following
// synthetization is done:
// - if matching STP/LDP is found within a dynamic symbol, an unwind rule with
// is created and the frame size is extracted
// - the sigreturn helper is detected and signal unwind info is associated for it
func createVDSOSyntheticRecordArm64(ef *pfelf.File) sdtypes.IntervalData {
deltas := sdtypes.StackDeltaArray{}
deltas = append(deltas, sdtypes.StackDelta{Address: 0, Info: sdtypes.UnwindInfoLR})

symbols, err := ef.ReadDynamicSymbols()
if err != nil {
return sdtypes.IntervalData{}
}

symbols.VisitAll(func(sym libpf.Symbol) {
addr := uint64(sym.Address)
if sym.Name == "__kernel_rt_sigreturn" {
deltas = append(
deltas,
sdtypes.StackDelta{Address: addr, Info: sdtypes.UnwindInfoSignal},
sdtypes.StackDelta{Address: addr + uint64(sym.Size), Info: sdtypes.UnwindInfoLR},
)
return
}
// Determine if LR is on stack
code := make([]byte, sym.Size)
if _, err = ef.ReadVirtualMemory(code, int64(sym.Address)); err != nil {
return
}

var frameStart uint64
var frameSize int
for offs := uint64(0); offs < uint64(sym.Size); offs += 4 {
inst, err := aa.Decode(code[offs:])
if err != nil {
continue
}
switch inst.Op {
case aa.RET:
return
case aa.STP:
if reg, ok := ah.Xreg2num(inst.Args[0]); !ok || reg != regFP {
continue
}
if reg, ok := ah.Xreg2num(inst.Args[1]); !ok || reg != regLR {
continue
}
imm, ok := ah.DecodeImmediate(inst.Args[2])
if !ok {
continue
}
imm = -imm
if imm < 1024 {
rockdaboot marked this conversation as resolved.
Show resolved Hide resolved
frameStart = offs + 4
frameSize = int(imm)
}
case aa.LDP:
if reg, ok := ah.Xreg2num(inst.Args[0]); !ok || reg != regFP {
continue
}
if reg, ok := ah.Xreg2num(inst.Args[1]); !ok || reg != regLR {
continue
}
if frameStart == 0 {
return
}
deltas = append(
deltas,
sdtypes.StackDelta{
Address: addr + frameStart,
Info: sdtypes.UnwindInfo{
Opcode: sdtypes.UnwindOpcodeBaseFP,
Param: int32(frameSize),
FPOpcode: sdtypes.UnwindOpcodeBaseFP,
FPParam: 8,
},
},
sdtypes.StackDelta{Address: addr + offs + 4, Info: sdtypes.UnwindInfoLR},
)
frameStart = 0
}
}
})
sort.Slice(deltas, func(i, j int) bool {
return deltas[i].Address < deltas[j].Address
})

return sdtypes.IntervalData{Deltas: deltas}
}
42 changes: 1 addition & 41 deletions processmanager/synthdeltas_arm64.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,44 +5,4 @@

package processmanager // import "go.opentelemetry.io/ebpf-profiler/processmanager"

import (
"fmt"

"go.opentelemetry.io/ebpf-profiler/host"
"go.opentelemetry.io/ebpf-profiler/libpf/pfelf"
sdtypes "go.opentelemetry.io/ebpf-profiler/nativeunwind/stackdeltatypes"
)

// createVDSOSyntheticRecord creates a generated stack-delta record spanning the entire vDSO binary,
// requesting LR based unwinding. On ARM64, the vDSO currently lacks a proper `.eh_frame` section,
// so we construct it here instead.
// Currently, this assumes that most calls work with the LR unwinding. Special handling
// is added for the signal frame return handler stub which uses signal unwinding.
func createVDSOSyntheticRecord(ef *pfelf.File) sdtypes.IntervalData {
useLR := sdtypes.UnwindInfo{
Opcode: sdtypes.UnwindOpcodeBaseSP,
FPOpcode: sdtypes.UnwindOpcodeBaseLR,
}

deltas := sdtypes.StackDeltaArray{}
deltas = append(deltas, sdtypes.StackDelta{Address: 0, Info: useLR})
if sym, err := ef.LookupSymbol("__kernel_rt_sigreturn"); err == nil {
addr := uint64(sym.Address)
deltas = append(
deltas,
sdtypes.StackDelta{Address: addr, Info: sdtypes.UnwindInfoSignal},
sdtypes.StackDelta{Address: addr + uint64(sym.Size), Info: useLR},
)
}
return sdtypes.IntervalData{Deltas: deltas}
}

// insertSynthStackDeltas adds synthetic stack-deltas to the given SDMM. On ARM64, this is
// currently only used for emulating proper unwinding info of the vDSO.
func (pm *ProcessManager) insertSynthStackDeltas(fileID host.FileID, ef *pfelf.File) error {
deltas := createVDSOSyntheticRecord(ef)
if err := pm.AddSynthIntervalData(fileID, deltas); err != nil {
return fmt.Errorf("failed to add synthetic deltas: %w", err)
}
return nil
}
var createVDSOSyntheticRecord = createVDSOSyntheticRecordArm64
11 changes: 1 addition & 10 deletions processmanager/synthdeltas_other.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,4 @@

package processmanager // import "go.opentelemetry.io/ebpf-profiler/processmanager"

import (
"go.opentelemetry.io/ebpf-profiler/host"
"go.opentelemetry.io/ebpf-profiler/libpf/pfelf"
)

// insertSynthStackDeltas adds synthetic stack-deltas to the given SDMM. On non-ARM64, this is
// currently unused.
func (pm *ProcessManager) insertSynthStackDeltas(_ host.FileID, _ *pfelf.File) error {
return nil
}
var createVDSOSyntheticRecord = createVDSOSyntheticRecordNone
45 changes: 45 additions & 0 deletions processmanager/synthdeltas_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package processmanager // import "go.opentelemetry.io/ebpf-profiler/processmanager"

import (
"testing"

"go.opentelemetry.io/ebpf-profiler/libpf/pfelf"
sdtypes "go.opentelemetry.io/ebpf-profiler/nativeunwind/stackdeltatypes"

"github.com/stretchr/testify/require"
)

func TestVDSOArm64(t *testing.T) {
frameSize16 := sdtypes.UnwindInfo{
Opcode: sdtypes.UnwindOpcodeBaseFP,
Param: 16,
FPOpcode: sdtypes.UnwindOpcodeBaseFP,
FPParam: 8,
}

testCases := map[string]sdtypes.StackDeltaArray{
"vdso.arch64.withframe": {
{Address: 0, Info: sdtypes.UnwindInfoLR},
{Address: 0x7d8, Info: frameSize16},
{Address: 0x7e4, Info: sdtypes.UnwindInfoLR},
{Address: 0x800, Info: frameSize16},
{Address: 0x80c, Info: sdtypes.UnwindInfoLR},
{Address: 0x8f8, Info: sdtypes.UnwindInfoSignal},
{Address: 0x900, Info: sdtypes.UnwindInfoLR},
},
}

for name, expected := range testCases {
t.Run(name, func(t *testing.T) {
ef, err := pfelf.Open("testdata/" + name)
require.NoError(t, err)
defer ef.Close()

deltas := createVDSOSyntheticRecordArm64(ef)
require.Equal(t, expected, deltas.Deltas, "vdso deltas wrong")
})
}
}
Binary file not shown.
31 changes: 31 additions & 0 deletions tools/coredump/testdata/arm64/vdso-frame.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"coredump-ref": "5b8fed37692abe90d442acbecf7d64852469af2d5c8d48858aadbcb0956586bd",
"threads": [
{
"lwp": 1016,
"frames": [
"linux-vdso.1.so+0x50c",
"linux-vdso.1.so+0x7df",
"libc.so.6+0xbbccb",
"cpuhog+0x857",
"libc.so.6+0x284c3",
"libc.so.6+0x28597",
"cpuhog+0x72f"
]
}
],
"modules": [
{
"ref": "e27e9ec53f7479924b46bdf8b22f0cba26f46cd0683e31b03d8d64b564baa6b5",
"local-path": "/home/ubuntu/cpuhog"
},
{
"ref": "0740600899deec69af5e1a2110a9007ffd6af83dfe17b25d87fd3a48ae335dd0",
"local-path": "/usr/lib/aarch64-linux-gnu/libc.so.6"
},
{
"ref": "7e1f26ba5a3b84dbf6fbfac1e318a7b0320eb1214f0e409905f207f1c9a2616b",
"local-path": "/usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1"
}
]
}
10 changes: 10 additions & 0 deletions tools/coredump/testsources/c/cpuhog.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#include <time.h>

int main(void)
{
struct timespec res;

while (1) {
clock_gettime(CLOCK_MONOTONIC, &res);
}
}