Skip to content

Commit 96b8db1

Browse files
committed
Add prctl monitor
1 parent 91218c3 commit 96b8db1

14 files changed

Lines changed: 132 additions & 7 deletions

File tree

internal/controller/controller.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,11 @@ func (c *Controller) Start(ctx context.Context) error {
162162
// So if you change this log line update also the system test.
163163
log.Info("Attached sched monitor")
164164

165+
if err := trc.AttachPrctlMonitor(); err != nil {
166+
return fmt.Errorf("failed to attach prctl monitor: %w", err)
167+
}
168+
log.Info("Attached prctl monitor")
169+
165170
if err := c.startTraceHandling(ctx, trc); err != nil {
166171
return fmt.Errorf("failed to start trace handling: %w", err)
167172
}

metrics/ids.go

Lines changed: 4 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

metrics/metrics.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2096,5 +2096,12 @@
20962096
"name": "UnwindErrBadDTVRead",
20972097
"field": "bpf.errors.bad_dtv_read",
20982098
"id": 286
2099+
},
2100+
{
2101+
"description": "Number of prctl(PR_SET_VMA) calls naming an anonymous mapping OTEL_CTX",
2102+
"type": "counter",
2103+
"name": "NumPrctlSetVmaOtelCtx",
2104+
"field": "bpf.num_prctl_set_vma_otel_ctx",
2105+
"id": 287
20992106
}
21002107
]

processcontext/integrationtests/processcontext_integration_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,16 @@ func Test_ProcessContext(t *testing.T) {
6464
tests := map[string]struct {
6565
exeName string
6666
args []string
67+
env []string
6768
}{
6869
"glibc_exe": {exeName: "processctx_exe_glibc"},
70+
// Publishes the process context after a delay, so the profiler discovers
71+
// the PID before the publication and the prctl monitor must trigger a
72+
// resync to pick up the OTEL_CTX mapping.
73+
"glibc_exe_delayed_publish": {
74+
exeName: "processctx_exe_glibc",
75+
env: []string{"OTEL_PROCESS_CTX_PUBLISH_DELAY_MS=200"},
76+
},
6977
// "musl_exe": {exeName: "processctx_exe_musl"},
7078
// "glibc_lib": {exeName: "processctx_lib_glibc"},
7179
// "musl_lib": {exeName: "processctx_lib_musl"},
@@ -107,6 +115,9 @@ func Test_ProcessContext(t *testing.T) {
107115

108116
cmd := exec.CommandContext(ctx, filepath.Join(exeDir, tc.exeName), tc.args...)
109117
cmd.Stderr = os.Stderr
118+
if len(tc.env) > 0 {
119+
cmd.Env = append(os.Environ(), tc.env...)
120+
}
110121
require.NoError(t, cmd.Start())
111122

112123
wg := sync.WaitGroup{}

processcontext/integrationtests/testdata/processctx.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <stdbool.h>
77
#include <stdint.h>
88
#include <stdio.h>
9+
#include <stdlib.h>
910
#include <unistd.h>
1011

1112
#include "processctx_lib.h"
@@ -82,6 +83,19 @@ int main(int argc, char *argv[]) {
8283

8384
signal(SIGTERM, handle_sigterm);
8485

86+
// OTEL_PROCESS_CTX_PUBLISH_DELAY_MS lets the test exercise the prctl monitor
87+
// path: the profiler discovers the PID first, then the publish prctl fires
88+
// and triggers a resync. We burn CPU rather than sleep so the process stays
89+
// on-CPU and is sampled by the profiler's perf event, which drives process
90+
// synchronization before the context is published.
91+
const char *delay_str = getenv("OTEL_PROCESS_CTX_PUBLISH_DELAY_MS");
92+
if (delay_str != NULL) {
93+
int delay_ms = atoi(delay_str);
94+
if (delay_ms > 0) {
95+
burn(delay_ms);
96+
}
97+
}
98+
8599
if (init_process_context()) {
86100
fprintf(stderr, "Failed to initialize process context\n");
87101
return 1;

processmanager/processinfo.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -545,10 +545,8 @@ func (pm *ProcessManager) SynchronizeProcess(pr process.Process) {
545545
numParseErrors, err := pr.IterateMappings(func(m process.RawMapping) bool {
546546
if processcontext.IsContextMapping(m.IsExecutable(), m.Path) {
547547
contextMappingAddr = m.Vaddr
548-
// Even if process context is not found, it might be published in the future.
549-
// For now, we rely on a new call to synchronizeMappings to pick it up.
550-
// TODO: Add some kind of polling mechanism or a hook on prctl to be notified
551-
// when the process context is published.
548+
// The eBPF hook on prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME) will trigger a
549+
// PID resynchronization when the process names its context mapping "OTEL_CTX".
552550
}
553551

554552
// Executable mappings and VDSO, converted directly to libpf.FrameMapping

support/ebpf/prctl_monitor.ebpf.c

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// This file contains the code for the tracepoint on prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, ...)
2+
// to detect when a process names an anonymous memory mapping "OTEL_CTX".
3+
4+
#include "bpfdefs.h"
5+
#include "tracemgmt.h"
6+
7+
#include "types.h"
8+
9+
#ifndef TESTING_COREDUMP
10+
11+
// prctl constants from include/uapi/linux/prctl.h
12+
#define PR_SET_VMA 0x53564d41
13+
#define PR_SET_VMA_ANON_NAME 0
14+
15+
// See /sys/kernel/tracing/events/syscalls/sys_enter_prctl/format
16+
struct sys_enter_prctl_ctx {
17+
unsigned char skip[16]; // common fields (8) + __syscall_nr (4) + pad (4)
18+
unsigned long option; // prctl option
19+
unsigned long arg2; // sub-option (PR_SET_VMA_ANON_NAME for PR_SET_VMA)
20+
unsigned long arg3; // addr
21+
unsigned long arg4; // len
22+
unsigned long arg5; // name (user-space pointer)
23+
};
24+
25+
// tracepoint__sys_enter_prctl hooks prctl() calls to detect when a process
26+
// names an anonymous VMA "OTEL_CTX". This triggers a PID resynchronization
27+
// so the profiler can discover the newly published process context mapping.
28+
SEC("tracepoint/syscalls/sys_enter_prctl")
29+
int tracepoint__sys_enter_prctl(struct sys_enter_prctl_ctx *ctx)
30+
{
31+
if (ctx->option != PR_SET_VMA || ctx->arg2 != PR_SET_VMA_ANON_NAME) {
32+
goto exit;
33+
}
34+
35+
u64 pid_tgid = bpf_get_current_pid_tgid();
36+
u32 pid = pid_tgid >> 32;
37+
38+
if (!bpf_map_lookup_elem(&reported_pids, &pid) && !pid_information_exists(pid)) {
39+
// Only report PIDs that we explicitly track. This avoids sending kernel worker PIDs
40+
// to userspace.
41+
goto exit;
42+
}
43+
44+
// Read the VMA name from user-space. We only need 9 bytes ("OTEL_CTX" + NUL).
45+
__attribute__((aligned(8))) char name[9] = {};
46+
if (bpf_probe_read_user(name, sizeof(name), (void *)ctx->arg5)) {
47+
goto exit;
48+
}
49+
50+
// Check for an exact "OTEL_CTX" match. We avoid bpf_strncmp (kernel 5.17+).
51+
// Instead, compare as a u64 for the 8 characters plus a byte check for the
52+
// NUL terminator.
53+
if (*(u64 *)name != *(u64 *)"OTEL_CTX" || name[8] != '\0') {
54+
goto exit;
55+
}
56+
57+
if (report_pid(ctx, pid_tgid, RATELIMIT_ACTION_DEFAULT)) {
58+
increment_metric(metricID_NumPrctlSetVmaOtelCtx);
59+
}
60+
61+
exit:
62+
return 0;
63+
}
64+
65+
#endif

support/ebpf/tracer.ebpf.amd64

8.93 KB
Binary file not shown.

support/ebpf/tracer.ebpf.arm64

8.92 KB
Binary file not shown.

support/ebpf/types.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,9 @@ enum {
328328
// number of failures to read TLS variables via the DTV
329329
metricID_UnwindErrBadDTVRead,
330330

331+
// number of prctl(PR_SET_VMA) calls naming an anonymous mapping OTEL_CTX
332+
metricID_NumPrctlSetVmaOtelCtx,
333+
331334
//
332335
// Metric IDs above are for counters (cumulative values)
333336
//

0 commit comments

Comments
 (0)