[RFC PATCH bpf-next 10/12] bpf: Support attach and detach for SDT observer program
From: Xu Kuohai
Date: Sat Jun 27 2026 - 10:58:27 EST
From: Xu Kuohai <xukuohai@xxxxxxxxxx>
Add support to attach SDT observer program by reusing the bpf trampoline
attach and detach mechanism. The trampoline is keyed by target program
fd and offset of the probe in the target program. Unlike normal tracing
program, the target function address is the jited address of the probe site,
and function prototype is resolved from prog->aux->sdt_probe.
Signed-off-by: Xu Kuohai <xukuohai@xxxxxxxxxx>
---
include/linux/bpf.h | 9 ++++-
kernel/bpf/syscall.c | 85 +++++++++++++++++++++++++++++++++++++++++
kernel/bpf/trampoline.c | 4 ++
3 files changed, 97 insertions(+), 1 deletion(-)
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 93c196f38b87..6548a2dd76da 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -1270,6 +1270,12 @@ struct btf_func_model {
*/
#define BPF_TRAMP_F_INDIRECT BIT(8)
+/* Trampoline is for an SDT probe site: arguments arrive in
+ * BPF registers recorded by the SDT macro at build time,
+ * described by btf_func_model.arg_regs[] instead of ABI order.
+ */
+#define BPF_TRAMP_F_SDT_PROBE BIT(9)
+
/* Each call __bpf_prog_enter + call bpf_func + call __bpf_prog_exit is ~50
* bytes on x86.
*/
@@ -1393,7 +1399,8 @@ struct bpf_trampoline {
struct {
struct btf_func_model model;
void *addr;
- bool ftrace_managed;
+ u8 ftrace_managed:1;
+ u8 sdt_probe_site:1;
} func;
/* if !NULL this is BPF_PROG_TYPE_EXT program that extends another BPF
* program by replacing one of its functions. func.addr is the address
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index 8b2c73bb6c2a..53897ff1100f 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -3737,6 +3737,89 @@ static const struct bpf_link_ops bpf_tracing_link_lops = {
.fill_link_info = bpf_tracing_link_fill_link_info,
};
+struct bpf_sdt_link {
+ struct bpf_tramp_link link;
+ struct bpf_trampoline *trampoline;
+};
+
+static void bpf_sdt_link_release(struct bpf_link *link)
+{
+ struct bpf_sdt_link *sdt_link = container_of(link, struct bpf_sdt_link, link.link);
+ struct bpf_prog *prog = sdt_link->link.link.prog;
+ struct bpf_prog *tgt_prog = prog->aux->dst_prog;
+
+ WARN_ON_ONCE(bpf_trampoline_unlink_prog(&sdt_link->link.node, sdt_link->trampoline,
+ tgt_prog));
+ bpf_trampoline_put(sdt_link->trampoline);
+}
+
+static void bpf_sdt_link_dealloc(struct bpf_link *link)
+{
+ struct bpf_sdt_link *sdt_link = container_of(link, struct bpf_sdt_link, link.link);
+
+ kfree(sdt_link);
+}
+
+static const struct bpf_link_ops bpf_sdt_link_lops = {
+ .release = bpf_sdt_link_release,
+ .dealloc = bpf_sdt_link_dealloc,
+};
+
+static int bpf_sdt_link_attach(struct bpf_prog *prog)
+{
+ struct bpf_sdt_probe_info *sdt = prog->aux->sdt_probe;
+ struct bpf_attach_target_info tgt_info = {};
+ struct bpf_link_primer primer;
+ struct bpf_sdt_link *sdt_link;
+ struct bpf_prog *tgt_prog = prog->aux->dst_prog;
+ u64 tr_key;
+ int i, err;
+
+ if (!sdt || !tgt_prog)
+ return -EINVAL;
+
+ sdt_link = kzalloc_obj(struct bpf_sdt_link, GFP_USER);
+ if (!sdt_link)
+ return -ENOMEM;
+
+ bpf_tramp_link_init(&sdt_link->link, BPF_LINK_TYPE_SDT, &bpf_sdt_link_lops, prog,
+ BPF_TRACE_SDT, 0);
+ err = bpf_link_prime(&sdt_link->link.link, &primer);
+ if (err) {
+ kfree(sdt_link);
+ return err;
+ }
+
+ /* use orig_off to compute the key as orig_off is unique for each probe */
+ tr_key = bpf_trampoline_compute_key(tgt_prog, NULL, sdt->val.orig_off);
+ tgt_info.tgt_addr = sdt->probe_ip;
+ tgt_info.tgt_name = sdt->val.name;
+ tgt_info.fmodel.nr_args = sdt->val.nargs;
+ for (i = 0; i < sdt->val.nargs; i++) {
+ tgt_info.fmodel.arg_size[i] = 8;
+ tgt_info.fmodel.arg_regs[i] = sdt->val.arg_reg[i];
+ }
+
+ sdt_link->trampoline = bpf_trampoline_get(tr_key, &tgt_info);
+ if (!sdt_link->trampoline) {
+ err = -ENOMEM;
+ goto err_cleanup;
+ }
+ sdt_link->trampoline->func.sdt_probe_site = 1;
+
+ err = bpf_trampoline_link_prog(&sdt_link->link.node, sdt_link->trampoline, tgt_prog);
+ if (err)
+ goto err_put_tramp;
+
+ return bpf_link_settle(&primer);
+
+err_put_tramp:
+ bpf_trampoline_put(sdt_link->trampoline);
+err_cleanup:
+ bpf_link_cleanup(&primer);
+ return err;
+}
+
static int bpf_tracing_prog_attach(struct bpf_prog *prog,
int tgt_prog_fd,
u32 btf_id,
@@ -5945,6 +6028,8 @@ static int link_create(union bpf_attr *attr, bpfptr_t uattr)
ret = cgroup_bpf_link_attach(attr, prog);
else if (is_tracing_multi(prog->expected_attach_type))
ret = bpf_tracing_multi_attach(prog, attr);
+ else if (prog->expected_attach_type == BPF_TRACE_SDT)
+ ret = bpf_sdt_link_attach(prog);
else
ret = bpf_tracing_prog_attach(prog,
attr->link_create.target_fd,
diff --git a/kernel/bpf/trampoline.c b/kernel/bpf/trampoline.c
index 1a721fc4bef5..68f87e7b901f 100644
--- a/kernel/bpf/trampoline.c
+++ b/kernel/bpf/trampoline.c
@@ -691,6 +691,9 @@ static int bpf_trampoline_update(struct bpf_trampoline *tr, bool lock_direct_mut
/* clear all bits except SHARE_IPMODIFY and TAIL_CALL_CTX */
tr->flags &= (BPF_TRAMP_F_SHARE_IPMODIFY | BPF_TRAMP_F_TAIL_CALL_CTX);
+ if (tr->func.sdt_probe_site)
+ tr->flags |= BPF_TRAMP_F_SDT_PROBE;
+
if (tnodes[BPF_TRAMP_FEXIT].nr_nodes ||
tnodes[BPF_TRAMP_MODIFY_RETURN].nr_nodes) {
/* NOTE: BPF_TRAMP_F_RESTORE_REGS and BPF_TRAMP_F_SKIP_FRAME
@@ -784,6 +787,7 @@ static enum bpf_tramp_prog_type bpf_attach_type_to_tramp(struct bpf_prog *prog)
switch (prog->expected_attach_type) {
case BPF_TRACE_FENTRY:
case BPF_TRACE_FENTRY_MULTI:
+ case BPF_TRACE_SDT:
return BPF_TRAMP_FENTRY;
case BPF_MODIFY_RETURN:
return BPF_TRAMP_MODIFY_RETURN;
--
2.47.3