Skip to content

Commit d610a75

Browse files
committed
Get custom labels working with clang-17 and 4.19 kernel
1 parent ed07a3c commit d610a75

20 files changed

+456
-472
lines changed

.github/workflows/unit-test-on-pull-request.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ jobs:
120120
# https://github.com/cilium/ci-kernels/pkgs/container/ci-kernels/versions?filters%5Bversion_type%5D=tagged
121121

122122
# AMD64
123+
- { target_arch: amd64, kernel: 4.19.314 }
123124
- { target_arch: amd64, kernel: 5.4.276 }
124125
- { target_arch: amd64, kernel: 5.10.217 }
125126
- { target_arch: amd64, kernel: 5.15.159 }

support/ebpf/Makefile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
SHELL ?= bash
2-
BPF_CLANG ?= clang-16
3-
BPF_LINK ?= llvm-link-16
4-
LLC ?= llc-16
2+
BPF_CLANG ?= clang-17
3+
BPF_LINK ?= llvm-link-17
4+
LLC ?= llc-17
55

66
DEBUG_FLAGS = -DOPTI_DEBUG -g
77

support/ebpf/bpfdefs.h

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -157,14 +157,4 @@ static long (*bpf_perf_prog_read_value)(struct pt_regs *ctx, struct bpf_perf_eve
157157

158158
#endif // !TESTING_COREDUMP
159159

160-
// HACK: On failure, bpf_perf_prog_read_value() zeroes the buffer. We ensure that this always
161-
// fail with a compile time assert that ensures that the struct size is different to the size
162-
// of the expected structure.
163-
#define bpf_large_memzero(_d, _l) \
164-
({ \
165-
_Static_assert(_l != sizeof(struct bpf_perf_event_value), "stack size must be different to the valid argument"); \
166-
bpf_perf_prog_read_value(ctx, _d, _l); \
167-
})
168-
169-
170160
#endif // OPTI_BPFDEFS_H

support/ebpf/extmaps.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ extern bpf_map_def inhibit_events;
1717
extern bpf_map_def interpreter_offsets;
1818
extern bpf_map_def system_config;
1919
extern bpf_map_def trace_events;
20+
extern bpf_map_def go_procs;
2021

2122
#if defined(TESTING_COREDUMP)
2223

support/ebpf/go_labels.ebpf.c

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
// This file contains the code and map definitions that are shared between
2+
// the tracers, as well as a dispatcher program that can be attached to a
3+
// perf event and will call the appropriate tracer for a given process
4+
5+
#include "bpfdefs.h"
6+
#include "util.h"
7+
#include "hash.h"
8+
#include "kernel.h"
9+
#include "tracemgmt.h"
10+
#include "tsd.h"
11+
#include "types.h"
12+
13+
static inline __attribute__((__always_inline__))
14+
void process_value(GoMapBucket *map_value, CustomLabelsArray *out, unsigned i) {
15+
if (map_value->tophash[i] == 0)
16+
return;
17+
if (out->len >= MAX_CUSTOM_LABELS)
18+
return;
19+
CustomLabel *lbl = &out->labels[out->len];
20+
if (map_value->keys[i].str != NULL) {
21+
long res = bpf_probe_read_user(lbl->key.key_bytes, CUSTOM_LABEL_MAX_KEY_LEN, map_value->keys[i].str);
22+
if (res) {
23+
DEBUG_PRINT("cl: failed to read key for custom label (%lx): %ld", (unsigned long) map_value->keys[i].str, res);
24+
return;
25+
}
26+
res = bpf_probe_read_user(lbl->val.val_bytes, CUSTOM_LABEL_MAX_VAL_LEN, map_value->values[i].str);
27+
if (res) {
28+
DEBUG_PRINT("cl: failed to read value for custom label: %ld", res);
29+
return;
30+
}
31+
lbl->key_len = map_value->keys[i].len;
32+
lbl->val_len = map_value->values[i].len;
33+
}
34+
out->len++;
35+
}
36+
37+
static inline __attribute__((__always_inline__))
38+
bool process_bucket(PerCPURecord *record, void *label_buckets, int j) {
39+
CustomLabelsArray *out = &record->customLabelsState.cla;
40+
GoMapBucket *map_value = &record->goMapBucket;
41+
long res = bpf_probe_read(map_value, sizeof(GoMapBucket), label_buckets + (j * sizeof(GoMapBucket)));
42+
if (res < 0) {
43+
return false;
44+
}
45+
46+
process_value(map_value, out, 0);
47+
process_value(map_value, out, 1);
48+
process_value(map_value, out, 2);
49+
process_value(map_value, out, 3);
50+
process_value(map_value, out, 4);
51+
process_value(map_value, out, 5);
52+
process_value(map_value, out, 6);
53+
process_value(map_value, out, 7);
54+
55+
return false;
56+
}
57+
58+
// Go processes store the current goroutine in thread local store. From there
59+
// this reads the g (aka goroutine) struct, then the m (the actual operating
60+
// system thread) of that goroutine, and finally curg (current goroutine). This
61+
// chain is necessary because getg().m.curg points to the current user g
62+
// assigned to the thread (curg == getg() when not on the system stack). curg
63+
// may be nil if there is no user g, such as when running in the scheduler. If
64+
// curg is nil, then g is either a system stack (called g0) or a signal handler
65+
// g (gsignal). Neither one will ever have label.
66+
static inline __attribute__((__always_inline__))
67+
bool get_go_custom_labels(struct pt_regs *ctx, PerCPURecord *record, GoCustomLabelsOffsets *offs) {
68+
long res;
69+
70+
size_t curg_ptr_addr;
71+
res = bpf_probe_read_user(&curg_ptr_addr, sizeof(void *), (void *)(record->customLabelsState.go_m_ptr + offs->curg));
72+
if (res < 0) {
73+
DEBUG_PRINT("cl: failed to read value for m_ptr->curg: %ld", res);
74+
return false;
75+
}
76+
77+
void *labels_map_ptr_ptr;
78+
res = bpf_probe_read_user(&labels_map_ptr_ptr, sizeof(void *), (void *)(curg_ptr_addr + offs->labels));
79+
if (res < 0) {
80+
DEBUG_PRINT("cl: failed to read value for curg->labels (%lx->%lx): %ld", (unsigned long)curg_ptr_addr,
81+
(unsigned long) offs->labels, res);
82+
return false;
83+
}
84+
85+
void *labels_map_ptr;
86+
res = bpf_probe_read(&labels_map_ptr, sizeof(labels_map_ptr), labels_map_ptr_ptr);
87+
if (res < 0) {
88+
DEBUG_PRINT("cl: failed to read value for labels_map_ptr (%lx): %ld", (unsigned long)labels_map_ptr_ptr, res);
89+
return false;
90+
}
91+
92+
u64 labels_count = 0;
93+
res = bpf_probe_read(&labels_count, sizeof(labels_count), labels_map_ptr + offs->hmap_count);
94+
if (res < 0) {
95+
DEBUG_PRINT("cl: failed to read value for labels_count: %ld", res);
96+
return false;
97+
}
98+
if (labels_count == 0) {
99+
DEBUG_PRINT("cl: no labels");
100+
return false;
101+
}
102+
103+
unsigned char log_2_bucket_count;
104+
res = bpf_probe_read(&log_2_bucket_count, sizeof(log_2_bucket_count), labels_map_ptr + offs->hmap_log2_bucket_count);
105+
if (res < 0) {
106+
DEBUG_PRINT("cl: failed to read value for bucket_count: %ld", res);
107+
return false;
108+
}
109+
void *label_buckets;
110+
res = bpf_probe_read(&label_buckets, sizeof(label_buckets), labels_map_ptr + offs->hmap_buckets);
111+
if (res < 0) {
112+
DEBUG_PRINT("cl: failed to read value for label_buckets: %ld", res);
113+
return false;
114+
}
115+
116+
// Manually unroll loop to support 4.19 kernel, auto unroll doesn't work as well
117+
// and we can't support as many buckets.
118+
u64 bucket_count = MIN(MAX_CUSTOM_LABELS, 1 << log_2_bucket_count);
119+
switch (bucket_count) {
120+
case 14: if (process_bucket(record, label_buckets, 13)) return true;
121+
case 13: if (process_bucket(record, label_buckets, 12)) return true;
122+
case 12: if (process_bucket(record, label_buckets, 11)) return true;
123+
case 11: if (process_bucket(record, label_buckets, 10)) return true;
124+
case 10: if (process_bucket(record, label_buckets, 9)) return true;
125+
case 9: if (process_bucket(record, label_buckets, 8)) return true;
126+
case 8: if (process_bucket(record, label_buckets, 7)) return true;
127+
case 7: if (process_bucket(record, label_buckets, 6)) return true;
128+
case 6: if (process_bucket(record, label_buckets, 5)) return true;
129+
case 5: if (process_bucket(record, label_buckets, 4)) return true;
130+
case 4: if (process_bucket(record, label_buckets, 3)) return true;
131+
case 3: if (process_bucket(record, label_buckets, 2)) return true;
132+
case 2: if (process_bucket(record, label_buckets, 1)) return true;
133+
case 1: if (process_bucket(record, label_buckets, 0)) return true;
134+
}
135+
136+
return false;
137+
}
138+
139+
140+
SEC("perf_event/go_labels")
141+
int go_labels(struct pt_regs *ctx) {
142+
PerCPURecord *record = get_per_cpu_record();
143+
if (!record)
144+
return -1;
145+
146+
u32 pid = record->trace.pid;
147+
GoCustomLabelsOffsets *offsets = bpf_map_lookup_elem(&go_procs, &pid);
148+
if (!offsets) {
149+
DEBUG_PRINT("cl: no offsets, %d not recognized as a go binary", pid);
150+
return -1;
151+
}
152+
DEBUG_PRINT("cl: go offsets found, %d recognized as a go binary: m_ptr: %lx", pid, (unsigned long)record->customLabelsState.go_m_ptr);
153+
bool success = get_go_custom_labels(ctx, record, offsets);
154+
if (!success) {
155+
increment_metric(metricID_UnwindGoCustomLabelsFailures);
156+
}
157+
158+
tail_call(ctx, PROG_UNWIND_STOP);
159+
return 0;
160+
}

support/ebpf/hash.h

Lines changed: 23 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -3,90 +3,33 @@
33

44
#include "types.h"
55

6-
#define ROUNDUP_8(x) ((x + 7) & ~7)
7-
static inline __attribute__((__always_inline__))
8-
bool hash_custom_labels(CustomLabelsArray *lbls, int seed, u64 *out) {
9-
// apply murmurhash2 as though this is an array of
10-
// the number of labels (8 bytes), followed by all the key/val lengths,
11-
// followed by all the keys/vals.
12-
const u64 m = 0xc6a4a7935bd1e995LLU;
13-
const int r = 47;
14-
15-
int len = 8;
16-
for (int i = 0; i < MAX_CUSTOM_LABELS; ++i) {
17-
if (i >= lbls->len)
18-
break;
19-
len += 8;
20-
len += ROUNDUP_8(lbls->labels[i].key_len);
21-
len += ROUNDUP_8(lbls->labels[i].val_len);
22-
}
23-
24-
u64 h = seed ^ (len * m);
25-
26-
// hash the number of labels
27-
{
28-
u64 k = lbls->len;
29-
k *= m;
30-
k ^= k >> r;
31-
k *= m;
32-
33-
h ^= k;
34-
h *= m;
35-
}
36-
37-
// hash each k/v len
38-
for (int i = 0; i < MAX_CUSTOM_LABELS; ++i) {
39-
// force clang not to unroll the loop by hiding the value of i.
40-
// Unrolling this loop confuses the verifier.
41-
asm volatile("" : "=r"(i) : "0"(i));
42-
if (i >= lbls->len)
43-
break;
44-
u64 k = (((u64)lbls->labels[i].key_len) << 32) | ((u64)lbls->labels[i].val_len);
45-
k *= m;
46-
k ^= k >> r;
47-
k *= m;
6+
#define M 0xc6a4a7935bd1e995LLU
487

49-
h ^= k;
50-
h *= m;
8+
static inline __attribute__((__always_inline__))
9+
u64 clear_or_hash_custom_labels(CustomLabelsArray *lbls, bool clear) {
10+
u64 h = lbls->len * M;
11+
u64 *bits = (u64 *)lbls;
12+
#pragma unroll
13+
for (int i=0; i < sizeof(CustomLabelsArray)/8; i++) {
14+
if (clear) {
15+
bits[i] = 0;
16+
} else {
17+
h ^= bits[i];
18+
h *= M;
19+
}
5120
}
5221

53-
// hash each k/v
54-
for (int i = 0; i < MAX_CUSTOM_LABELS; ++i) {
55-
if (i >= lbls->len)
56-
break;
57-
CustomLabel *lbl = &lbls->labels[i];
58-
u64 kl = ROUNDUP_8(lbl->key_len);
59-
for (int j = 0; j < CUSTOM_LABEL_MAX_VAL_LEN / 8; ++j) {
60-
if (j >= kl)
61-
return false;
62-
u64 k = lbl->key.key_u64[j];
63-
k *= m;
64-
k ^= k >> r;
65-
k *= m;
66-
67-
h ^= k;
68-
h *= m;
69-
}
70-
u64 vl = ROUNDUP_8(lbl->val_len);
71-
for (int j = 0; j < CUSTOM_LABEL_MAX_VAL_LEN / 8; ++j) {
72-
if (j >= vl)
73-
return false;
74-
u64 k = lbl->val.val_u64[j];
75-
k *= m;
76-
k ^= k >> r;
77-
k *= m;
78-
79-
h ^= k;
80-
h *= m;
81-
}
82-
}
22+
return h;
23+
}
8324

84-
h ^= h >> r;
85-
h *= m;
86-
h ^= h >> r;
25+
static inline __attribute__((__always_inline__))
26+
void clear_custom_labels(CustomLabelsArray *lbls) {
27+
clear_or_hash_custom_labels(lbls, true);
28+
}
8729

88-
*out = h;
89-
return true;
30+
static inline __attribute__((__always_inline__))
31+
u64 hash_custom_labels(CustomLabelsArray *lbls) {
32+
return clear_or_hash_custom_labels(lbls, false);
9033
}
9134

92-
#endif
35+
#endif // OPTI_HASH_H

0 commit comments

Comments
 (0)