Skip to content

Commit

Permalink
Merge pull request #7 from umanwizard/customlabels
Browse files Browse the repository at this point in the history
Support native custom labels
  • Loading branch information
umanwizard authored Sep 2, 2024
2 parents d519b70 + 00ceac9 commit 365fe03
Show file tree
Hide file tree
Showing 10 changed files with 321 additions and 17 deletions.
130 changes: 130 additions & 0 deletions interpreter/customlabels/customlabels.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package customlabels

// #include <stdlib.h>
// #include "../../support/ebpf/types.h"
import "C"
import (
"debug/elf"
"errors"
"fmt"
"regexp"
"unsafe"

"github.com/open-telemetry/opentelemetry-ebpf-profiler/interpreter"
"github.com/open-telemetry/opentelemetry-ebpf-profiler/libpf"
"github.com/open-telemetry/opentelemetry-ebpf-profiler/libpf/pfelf"
"github.com/open-telemetry/opentelemetry-ebpf-profiler/remotememory"
)

const (
abiVersionExport = "custom_labels_abi_version"
tlsExport = "custom_labels_thread_local_data"
)

var dsoRegex = regexp.MustCompile(`.*/libcustomlabels.*\.so`)

type data struct {
abiVersionElfVA libpf.Address
tlsAddr libpf.Address
isSharedLibrary bool
}

var _ interpreter.Data = &data{}

func Loader(_ interpreter.EbpfHandler, info *interpreter.LoaderInfo) (interpreter.Data, error) {
ef, err := info.GetELF()
if err != nil {
return nil, err
}

abiVersionSym, err := ef.LookupSymbol(abiVersionExport)
if err != nil {
if errors.Is(err, pfelf.ErrSymbolNotFound) {
return nil, nil
}

return nil, err
}

if abiVersionSym.Size != 4 {
return nil, fmt.Errorf("abi version export has wrong size %d", abiVersionSym.Size)
}

// If this is the libcustomlabels.so library, we are using
// global-dynamic TLS model and have to look up the TLS descriptor.
// Otherwise, assume we're the main binary and just look up the
// symbol.
isSharedLibrary := dsoRegex.MatchString(info.FileName())
var tlsAddr libpf.Address
if isSharedLibrary {
// Resolve thread info TLS export.
tlsDescs, err := ef.TLSDescriptors()
if err != nil {
return nil, errors.New("failed to extract TLS descriptors")
}
var ok bool
tlsAddr, ok = tlsDescs[tlsExport]
if !ok {
return nil, errors.New("failed to locate TLS descriptor for custom labels")
}
} else {
tlsSym, err := ef.LookupSymbol(tlsExport)
if err != nil {
return nil, err
}
if ef.Machine == elf.EM_AARCH64 {
tlsAddr = libpf.Address(tlsSym.Address + 16)
} else if ef.Machine == elf.EM_X86_64 {
tbss, err := ef.Tbss()
if err != nil {
return nil, err
}
tlsAddr = libpf.Address(int64(tlsSym.Address) - int64(tbss.Size))
} else {
return nil, fmt.Errorf("unrecognized machine: %s", ef.Machine.String())
}
}

d := data{
abiVersionElfVA: libpf.Address(abiVersionSym.Address),
tlsAddr: tlsAddr,
isSharedLibrary: isSharedLibrary,
}
return &d, nil
}

type instance struct {
interpreter.InstanceStubs
}

func (d data) Attach(ebpf interpreter.EbpfHandler, pid libpf.PID,
bias libpf.Address, rm remotememory.RemoteMemory) (interpreter.Instance, error) {

abiVersionPtr := rm.Ptr(bias + d.abiVersionElfVA)
abiVersion := rm.Uint32(abiVersionPtr)

if abiVersion != 0 {
return nil, fmt.Errorf("unsupported custom labels ABI version: %d"+
" (only 0 is supported)", abiVersion)
}

var tlsOffset uint64
if d.isSharedLibrary {
// Read TLS offset from the TLS descriptor
tlsOffset = rm.Uint64(bias + d.tlsAddr + 8)
} else {
// We're in the main executable: TLS offset is known statically.
tlsOffset = uint64(d.tlsAddr)
}

procInfo := C.NativeCustomLabelsProcInfo{tls_offset: C.u64(tlsOffset)}
if err := ebpf.UpdateProcData(libpf.CustomLabels, pid, unsafe.Pointer(&procInfo)); err != nil {
return nil, err
}

return &instance{}, nil
}

func (i *instance) Detach(ebpf interpreter.EbpfHandler, pid libpf.PID) error {
return ebpf.DeleteProcData(libpf.CustomLabels, pid)
}
3 changes: 3 additions & 0 deletions libpf/interpretertype.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ const (

// Go identifies the pseudo-interpreter for Go custom labels support.
Go InterpreterType = 0x101

// CustomLabels identifies the pseudo-interpreter for native custom labels support.
CustomLabels InterpreterType = 0x102
)

// Frame converts the interpreter type into the corresponding frame type.
Expand Down
13 changes: 13 additions & 0 deletions libpf/pfelf/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,19 @@ func (f *File) Section(name string) *Section {
return nil
}

// Tbss gets the thread-local uninitialized data section
func (f *File) Tbss() (*Section, error) {
if err := f.LoadSections(); err != nil {
return nil, err
}
for _, sec := range f.Sections {
if sec.Type == elf.SHT_NOBITS && sec.Flags&elf.SHF_TLS != 0 {
return &sec, nil
}
}
return nil, errors.New("no thread-local uninitialized data section (tbss)")
}

// ReadVirtualMemory reads bytes from given virtual address
func (f *File) ReadVirtualMemory(p []byte, addr int64) (int, error) {
if len(p) == 0 {
Expand Down
9 changes: 9 additions & 0 deletions processmanager/ebpf/ebpf.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ type ebpfMapsImpl struct {
v8Procs *cebpf.Map
apmIntProcs *cebpf.Map
goProcs *cebpf.Map
clProcs *cebpf.Map

// Stackdelta and process related eBPF maps
exeIDToStackDeltaMaps []*cebpf.Map
Expand Down Expand Up @@ -213,6 +214,12 @@ func LoadMaps(ctx context.Context, maps map[string]*cebpf.Map) (EbpfHandler, err
}
impl.goProcs = goProcs

clProcs, ok := maps["cl_procs"]
if !ok {
log.Fatalf("Map cl_procs is not available")
}
impl.clProcs = clProcs

impl.stackDeltaPageToInfo, ok = maps["stack_delta_page_to_info"]
if !ok {
log.Fatalf("Map stack_delta_page_to_info is not available")
Expand Down Expand Up @@ -306,6 +313,8 @@ func (impl *ebpfMapsImpl) getInterpreterTypeMap(typ libpf.InterpreterType) (*ceb
return impl.apmIntProcs, nil
case libpf.Go:
return impl.goProcs, nil
case libpf.CustomLabels:
return impl.clProcs, nil
default:
return nil, fmt.Errorf("type %d is not (yet) supported", typ)
}
Expand Down
3 changes: 2 additions & 1 deletion processmanager/execinfomanager/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/open-telemetry/opentelemetry-ebpf-profiler/host"
"github.com/open-telemetry/opentelemetry-ebpf-profiler/interpreter"
"github.com/open-telemetry/opentelemetry-ebpf-profiler/interpreter/apmint"
"github.com/open-telemetry/opentelemetry-ebpf-profiler/interpreter/customlabels"
"github.com/open-telemetry/opentelemetry-ebpf-profiler/interpreter/dotnet"
"github.com/open-telemetry/opentelemetry-ebpf-profiler/interpreter/golang"
"github.com/open-telemetry/opentelemetry-ebpf-profiler/interpreter/hotspot"
Expand Down Expand Up @@ -132,7 +133,7 @@ func NewExecutableInfoManager(

interpreterLoaders = append(interpreterLoaders, apmint.Loader)
if collectCustomLabels {
interpreterLoaders = append(interpreterLoaders, golang.Loader)
interpreterLoaders = append(interpreterLoaders, golang.Loader, customlabels.Loader)
}

deferredFileIDs, err := lru.NewSynced[host.FileID, libpf.Void](deferredFileIDSize,
Expand Down
129 changes: 115 additions & 14 deletions support/ebpf/interpreter_dispatcher.ebpf.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "types.h"
#include "tracemgmt.h"
#include "tsd.h"
#include "util.h"

// Begin shared maps

Expand Down Expand Up @@ -135,6 +136,13 @@ bpf_map_def SEC("maps") go_procs = {
.max_entries = 128,
};

bpf_map_def SEC("maps") cl_procs = {
.type = BPF_MAP_TYPE_HASH,
.key_size = sizeof(pid_t),
.value_size = sizeof(NativeCustomLabelsProcInfo),
.max_entries = 128,
};

static inline __attribute__((__always_inline__))
void *get_m_ptr(struct GoCustomLabelsOffsets *offs, UnwindState *state) {
long res;
Expand Down Expand Up @@ -227,7 +235,8 @@ bpf_map_def SEC("maps") custom_labels = {
// curg is nil, then g is either a system stack (called g0) or a signal handler
// g (gsignal). Neither one will ever have label.
static inline __attribute__((__always_inline__))
bool get_custom_labels(struct pt_regs *ctx, UnwindState *state, GoCustomLabelsOffsets *offs, CustomLabelsArray *out) {
bool get_go_custom_labels(struct pt_regs *ctx, UnwindState *state, void *offsets, CustomLabelsArray *out) {
GoCustomLabelsOffsets *offs = offsets;
bpf_large_memzero((void *)out, sizeof(*out));
long res;
size_t m_ptr_addr = (size_t)get_m_ptr(offs, state);
Expand Down Expand Up @@ -357,45 +366,136 @@ bool get_custom_labels(struct pt_regs *ctx, UnwindState *state, GoCustomLabelsOf
return true;
}

typedef bool (*label_getter)(struct pt_regs *ctx, UnwindState *state, void *data, CustomLabelsArray *out);

static inline __attribute__((__always_inline__))
void maybe_add_go_custom_labels(struct pt_regs *ctx, Trace *trace, UnwindState *state) {
u32 pid = trace->pid;
GoCustomLabelsOffsets *offsets = bpf_map_lookup_elem(&go_procs, &pid);
if (!offsets) {
DEBUG_PRINT("no offsets, %d not recognized as a go binary", pid);
return;
}
DEBUG_PRINT("Trace is within a process with Go custom labels enabled");
increment_metric(metricID_UnwindGoCustomLabelsAttempts);
bool get_and_add_custom_labels(struct pt_regs *ctx, Trace *trace, UnwindState *state, label_getter getter, void *data) {
u32 map_id = 0;
CustomLabelsArray *lbls = bpf_map_lookup_elem(&custom_labels_storage, &map_id);
bool success = false;
if (lbls) {
bool success = get_custom_labels(ctx, state, offsets, lbls);
bool success = getter(ctx, state, data, lbls);
if (success) {
DEBUG_PRINT("got %d custom labels", lbls->len);
u64 hash;
success = hash_custom_labels(lbls, 0, &hash);
if (success) {
int err = bpf_map_update_elem(&custom_labels, &hash, lbls, BPF_ANY);
if (err) {
DEBUG_PRINT("failed to update go custom labels with error %d\n", err);
DEBUG_PRINT("failed to update custom labels with error %d\n", err);
}
else {
trace->custom_labels_hash = hash;
success = true;
DEBUG_PRINT("successfully computed hash 0x%llx for Go custom labels", hash);
DEBUG_PRINT("successfully computed hash 0x%llx for custom labels", hash);
}
} else
DEBUG_PRINT("failed to compute hash for go custom labels");
DEBUG_PRINT("failed to compute hash for custom labels");
} else
DEBUG_PRINT("failed to get custom labels");
} else
DEBUG_PRINT("failed to get custom labels storage");
return success;
}

static inline __attribute__((__always_inline__))
void maybe_add_go_custom_labels(struct pt_regs *ctx, Trace *trace, UnwindState *state) {
u32 pid = trace->pid;
GoCustomLabelsOffsets *offsets = bpf_map_lookup_elem(&go_procs, &pid);
if (!offsets) {
DEBUG_PRINT("no offsets, %d not recognized as a go binary", pid);
return;
}
DEBUG_PRINT("Trace is within a process with Go custom labels enabled");
increment_metric(metricID_UnwindGoCustomLabelsAttempts);
bool success = get_and_add_custom_labels(ctx, trace, state, get_go_custom_labels, offsets);

if (!success)
increment_metric(metricID_UnwindGoCustomLabelsFailures);
}

static inline __attribute__((__always_inline__))
bool get_native_custom_labels(struct pt_regs *ctx, UnwindState *state, void *data, CustomLabelsArray *out) {
NativeCustomLabelsProcInfo *proc = data;

u64 tsd_base;
if (tsd_get_base((void **)&tsd_base) != 0) {
increment_metric(metricID_UnwindNativeCustomLabelsErrReadTsdBase);
DEBUG_PRINT("Failed to get TSD base for native custom labels");
return false;
}

u64 offset = tsd_base + proc->tls_offset;
DEBUG_PRINT("native custom labels data at 0x%llx", offset);

NativeCustomLabelsThreadLocalData tls;
int err;
if ((err = bpf_probe_read_user(&tls, sizeof(tls), (void *)(offset)))) {
increment_metric(metricID_UnwindNativeCustomLabelsErrReadData);
DEBUG_PRINT("Failed to read custom labels data: %d", err);
return false;
}

DEBUG_PRINT("Native custom labels count: %lu", tls.count);

int remaining = MIN(tls.count, MAX_CUSTOM_LABELS);
int i = 0;
int ct = 0;

while (remaining) {
if (i >= MAX_CUSTOM_LABELS)
break;
NativeCustomLabel *lbl_ptr = tls.storage + i;
++i;
NativeCustomLabel lbl;
if ((err = bpf_probe_read_user(&lbl, sizeof(lbl), (void *)(lbl_ptr)))) {
increment_metric(metricID_UnwindNativeCustomLabelsErrReadData);
DEBUG_PRINT("Failed to read label storage struct: %d", err);
return false;
}
if (!lbl.key.buf)
continue;
CustomLabel *out_lbl = &out->labels[ct];
unsigned key_len = MIN(lbl.key.len, CUSTOM_LABEL_MAX_KEY_LEN);
out_lbl->key_len = key_len;
if ((err = bpf_probe_read_user(out_lbl->key.key_bytes, key_len, (void *)lbl.key.buf))) {
increment_metric(metricID_UnwindNativeCustomLabelsErrReadKey);
DEBUG_PRINT("Failed to read label key: %d", err);
return false;
}
unsigned val_len = MIN(lbl.value.len, CUSTOM_LABEL_MAX_VAL_LEN);
out_lbl->val_len = val_len;
if ((err = bpf_probe_read_user(out_lbl->val.val_bytes, val_len, (void *)lbl.value.buf))) {
increment_metric(metricID_UnwindNativeCustomLabelsErrReadValue);
DEBUG_PRINT("Failed to read label value: %d", err);
return false;
}
++ct;
--remaining;
}
out->len = ct;
increment_metric(metricID_UnwindNativeCustomLabelsReadSuccesses);
return true;
}

static inline __attribute__((__always_inline__))
void maybe_add_native_custom_labels(struct pt_regs *ctx, Trace *trace, UnwindState *state) {
u32 pid = trace->pid;
NativeCustomLabelsProcInfo *proc = bpf_map_lookup_elem(&cl_procs, &pid);
if (!proc) {
DEBUG_PRINT("%d does not support native custom labels", pid);
return;
}
DEBUG_PRINT("Trace is within a process with native custom labels enabled");
bool success = get_and_add_custom_labels(ctx, trace, state, get_native_custom_labels, proc);
if (success)
increment_metric(metricID_UnwindNativeCustomLabelsAddSuccesses);
else
increment_metric(metricID_UnwindNativeCustomLabelsAddErrors);
}



static inline __attribute__((__always_inline__))
void maybe_add_apm_info(Trace *trace) {
u32 pid = trace->pid; // verifier needs this to be on stack on 4.15 kernel
Expand Down Expand Up @@ -452,6 +552,7 @@ int unwind_stop(struct pt_regs *ctx) {
UnwindState *state = &record->state;

maybe_add_apm_info(trace);
maybe_add_native_custom_labels(ctx, trace, state);
maybe_add_go_custom_labels(ctx, trace, state);

// If the stack is otherwise empty, push an error for that: we should
Expand Down
Binary file modified support/ebpf/tracer.ebpf.amd64
Binary file not shown.
Binary file modified support/ebpf/tracer.ebpf.arm64
Binary file not shown.
Loading

0 comments on commit 365fe03

Please sign in to comment.