[RFC PATCH bpf-next v3 1/3] libbpf: Auto-upgrade uprobes to multi-uprobes when supported

Varun R Mallya posted 3 patches 12 hours ago
[RFC PATCH bpf-next v3 1/3] libbpf: Auto-upgrade uprobes to multi-uprobes when supported
Posted by Varun R Mallya 12 hours ago
This patch modifies libbpf to automatically "upgrade" standard
SEC("uprobe") and SEC("uretprobe") programs to use the multi-uprobe
infrastructure (BPF_TRACE_UPROBE_MULTI) at load time if the kernel
supports it, making them compatible with BPF tokens.

To maintain backward compatibility and handle rare cases where singular
uprobes are required, new SEC("uprobe.single") and SEC("uretprobe.single")
section types are introduced. These force libbpf to use the legacy
perf_event_open() attachment path.

tools/testing/selftests/bpf/progs/test_fill_link_info.c has been
modified to use SEC("uprobe.single") as it asserts the program type to be
`BPF_LINK_TYPE_PERF_EVENT` and checks properties related to uprobes that
use perf.

Signed-off-by: Varun R Mallya <varunrmallya@gmail.com>
---
 tools/lib/bpf/libbpf.c                        | 42 ++++++++++++++++++-
 .../selftests/bpf/progs/test_fill_link_info.c |  2 +-
 2 files changed, 42 insertions(+), 2 deletions(-)

diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index 9ea41f40dc82..6f56bdc243eb 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -8294,6 +8294,22 @@ static int bpf_object_prepare_progs(struct bpf_object *obj)
 
 	for (i = 0; i < obj->nr_programs; i++) {
 		prog = &obj->programs[i];
+
+		if (kernel_supports(obj, FEAT_UPROBE_MULTI_LINK)) {
+			const char *sec_name = prog->sec_name;
+			/* Here, we filter out for u[ret]probe or "u[ret]probe/"
+			 * but we leave out anything with an '@'
+			 * in it as uprobe_multi does not support versioned
+			 * symbols yet, so we don't upgrade.
+			 */
+			if ((strcmp(sec_name, "uprobe") == 0 ||
+			     str_has_pfx(sec_name, "uprobe/") ||
+			     strcmp(sec_name, "uretprobe") == 0 ||
+			     str_has_pfx(sec_name, "uretprobe/")) &&
+			    !strchr(sec_name, '@'))
+				prog->expected_attach_type = BPF_TRACE_UPROBE_MULTI;
+		}
+
 		err = bpf_object__sanitize_prog(obj, prog);
 		if (err)
 			return err;
@@ -9955,9 +9971,11 @@ static const struct bpf_sec_def section_defs[] = {
 	SEC_DEF("kprobe+",		KPROBE,	0, SEC_NONE, attach_kprobe),
 	SEC_DEF("uprobe+",		KPROBE,	0, SEC_NONE, attach_uprobe),
 	SEC_DEF("uprobe.s+",		KPROBE,	0, SEC_SLEEPABLE, attach_uprobe),
+	SEC_DEF("uprobe.single+",	KPROBE,	0, SEC_NONE, attach_uprobe),
 	SEC_DEF("kretprobe+",		KPROBE, 0, SEC_NONE, attach_kprobe),
 	SEC_DEF("uretprobe+",		KPROBE, 0, SEC_NONE, attach_uprobe),
 	SEC_DEF("uretprobe.s+",		KPROBE, 0, SEC_SLEEPABLE, attach_uprobe),
+	SEC_DEF("uretprobe.single+",	KPROBE,	0, SEC_NONE, attach_uprobe),
 	SEC_DEF("kprobe.multi+",	KPROBE,	BPF_TRACE_KPROBE_MULTI, SEC_NONE, attach_kprobe_multi),
 	SEC_DEF("kretprobe.multi+",	KPROBE,	BPF_TRACE_KPROBE_MULTI, SEC_NONE, attach_kprobe_multi),
 	SEC_DEF("kprobe.session+",	KPROBE,	BPF_TRACE_KPROBE_SESSION, SEC_NONE, attach_kprobe_session),
@@ -12783,6 +12801,27 @@ bpf_program__attach_uprobe_opts(const struct bpf_program *prog, pid_t pid,
 		func_offset += sym_off;
 	}
 
+	/* This provides backwards compatibility to programs using uprobe, but
+	 * have been auto-upgraded to multi uprobe.
+	 */
+	if (prog->expected_attach_type == BPF_TRACE_UPROBE_MULTI) {
+		LIBBPF_OPTS(bpf_uprobe_multi_opts, multi_opts);
+		__u64 bpf_cookie;
+
+		multi_opts.cnt = 1;
+		multi_opts.retprobe = OPTS_GET(opts, retprobe, false);
+		if (func_offset || func_name)
+			multi_opts.offsets = &func_offset;
+		if (ref_ctr_off)
+			multi_opts.ref_ctr_offsets = &ref_ctr_off;
+
+		bpf_cookie = OPTS_GET(opts, bpf_cookie, 0);
+		if (bpf_cookie)
+			multi_opts.cookies = &bpf_cookie;
+
+		return bpf_program__attach_uprobe_multi(prog, pid, binary_path,
+							NULL, &multi_opts);
+	}
 	legacy = determine_uprobe_perf_type() < 0;
 	switch (attach_mode) {
 	case PROBE_ATTACH_MODE_LEGACY:
@@ -12903,7 +12942,8 @@ static int attach_uprobe(const struct bpf_program *prog, long cookie, struct bpf
 				offset = 0;
 		}
 		opts.retprobe = strcmp(probe_type, "uretprobe") == 0 ||
-				strcmp(probe_type, "uretprobe.s") == 0;
+				strcmp(probe_type, "uretprobe.s") == 0 ||
+				strcmp(probe_type, "uretprobe.single") == 0;
 		if (opts.retprobe && offset != 0) {
 			pr_warn("prog '%s': uretprobes do not support offset specification\n",
 				prog->name);
diff --git a/tools/testing/selftests/bpf/progs/test_fill_link_info.c b/tools/testing/selftests/bpf/progs/test_fill_link_info.c
index fac33a14f200..8e47a818462f 100644
--- a/tools/testing/selftests/bpf/progs/test_fill_link_info.c
+++ b/tools/testing/selftests/bpf/progs/test_fill_link_info.c
@@ -28,7 +28,7 @@ int BPF_PROG(kprobe_run)
 	return 0;
 }
 
-SEC("uprobe")
+SEC("uprobe.single")
 int BPF_PROG(uprobe_run)
 {
 	return 0;
-- 
2.53.0
Re: [RFC PATCH bpf-next v3 1/3] libbpf: Auto-upgrade uprobes to multi-uprobes when supported
Posted by bot+bpf-ci@kernel.org 11 hours ago
> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> index 42bdba4efd0c..bc749b9c291e 100644
> --- a/tools/lib/bpf/libbpf.c
> +++ b/tools/lib/bpf/libbpf.c

[ ... ]

> @@ -12812,6 +12830,27 @@ bpf_program__attach_uprobe_opts(const struct bpf_program *prog, pid_t pid,
>  		func_offset += sym_off;
>  	}
>
> +	/* This provides backwards compatibility to programs using uprobe, but
> +	 * have been auto-upgraded to multi uprobe.
> +	 */
> +	if (prog->expected_attach_type == BPF_TRACE_UPROBE_MULTI) {
> +		LIBBPF_OPTS(bpf_uprobe_multi_opts, multi_opts);
> +		__u64 bpf_cookie;
> +
> +		multi_opts.cnt = 1;
> +		multi_opts.retprobe = OPTS_GET(opts, retprobe, false);
> +		if (func_offset || func_name)
> +			multi_opts.offsets = &func_offset;
> +		if (ref_ctr_off)
> +			multi_opts.ref_ctr_offsets = &ref_ctr_off;
> +
> +		bpf_cookie = OPTS_GET(opts, bpf_cookie, 0);
> +		if (bpf_cookie)
> +			multi_opts.cookies = &bpf_cookie;
> +
> +		return bpf_program__attach_uprobe_multi(prog, pid, binary_path,
> +							NULL, &multi_opts);
> +	}
>  	legacy = determine_uprobe_perf_type() < 0;
>  	switch (attach_mode) {
>  	case PROBE_ATTACH_MODE_LEGACY:

Should the redirect to bpf_program__attach_uprobe_multi() take attach_mode
into account?

The attach_mode field is extracted from opts earlier in this function:

    attach_mode = OPTS_GET(opts, attach_mode, PROBE_ATTACH_MODE_DEFAULT);

but the multi-uprobe redirect returns before the switch(attach_mode) is
ever reached. If a caller explicitly passes PROBE_ATTACH_MODE_LEGACY or
PROBE_ATTACH_MODE_PERF, that request is silently ignored and the
attachment goes through the multi-uprobe path regardless.

The kernel's __perf_event_set_bpf_prog() does not check
prog->expected_attach_type for KPROBE programs, so perf-event-based
attachment would still work for auto-upgraded programs if the code were
to fall through.

For example, test_attach_probe_manual() in
prog_tests/attach_probe.c calls bpf_program__attach_uprobe_opts()
with PROBE_ATTACH_MODE_LEGACY and PROBE_ATTACH_MODE_PERF on programs
using SEC("uprobe") and SEC("uretprobe") from
progs/test_attach_probe_manual.c. After auto-upgrade, these tests
would silently use multi-uprobe attachment instead of exercising the
requested attachment modes.

Would it make sense to skip the multi-uprobe redirect when attach_mode
is explicitly set to a non-default value, something like:

    if (prog->expected_attach_type == BPF_TRACE_UPROBE_MULTI &&
        attach_mode == PROBE_ATTACH_MODE_DEFAULT) {

This would preserve the transparent upgrade for the common case while
still respecting explicit attachment mode requests.


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/23947048141