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

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

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

The following explain the reasoning for changing selftests:
- test_fill_link_info.c kprobe→kprobe.single:
  this test calls bpf_link_get_info_by_fd and asserts
  BPF_LINK_TYPE_PERF_EVENT and it explicitly needs the perf_event
  attachment path, which breaks after auto-upgrade to multi.

- test_attach_probe_manual.c kprobe→kprobe.single,
  kretprobe→kretprobe.single:
  this test exercises all four explicit attachment modes
  (default, legacy, perf, link) and PROBE_ATTACH_MODE_LINK
  creates a perf_event BPF link which the kernel rejects
  for a prog loaded with expected_attach_type = BPF_TRACE_KPROBE_MULTI.

- missed_kprobe.c kprobe->kprobe.single:
  The link is explicitly checked in the tests due to which it fails if
  we do not specifically kprobe.single this.

- get_func_ip_test.c ?kprobe→?kprobe.single: the ? is stripped from
  sec_name by libbpf at init time so the prog still gets auto-upgraded.
  It is then manually attached with a non-zero body offset,
  which kprobe_multi doesn't support.

Signed-off-by: Varun R Mallya <varunrmallya@gmail.com>
---
 tools/lib/bpf/libbpf.c                        | 104 ++++++++++++++----
 .../selftests/bpf/progs/get_func_ip_test.c    |   2 +-
 .../selftests/bpf/progs/missed_kprobe.c       |   4 +-
 .../bpf/progs/test_attach_probe_manual.c      |   4 +-
 .../selftests/bpf/progs/test_fill_link_info.c |   2 +-
 5 files changed, 88 insertions(+), 28 deletions(-)

diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index 6f56bdc243eb..7c6680759b0a 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -8286,6 +8286,43 @@ bpf_object__load_progs(struct bpf_object *obj, int log_level)
 	return 0;
 }
 
+/* This function is responsible for upgrading uprobes and kprobes
+ * to their multi counterparts.
+ */
+static int upgrade_program(struct bpf_object *obj, struct bpf_program *prog)
+{
+	const char *sec_name = prog->sec_name;
+
+	if (kernel_supports(obj, FEAT_UPROBE_MULTI_LINK)) {
+		/* 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;
+	}
+
+	if (kernel_supports(obj, FEAT_KPROBE_MULTI_LINK)) {
+		/* Here, we filter out for k[ret]probe or "k[ret]probe/".
+		 * We also do not allow '+' as we do not support
+		 * offsets in kprobe_multi.
+		 */
+		if ((strcmp(sec_name, "kprobe") == 0 ||
+		     str_has_pfx(sec_name, "kprobe/") ||
+		     strcmp(sec_name, "kretprobe") == 0 ||
+		     str_has_pfx(sec_name, "kretprobe/")) &&
+		    !strchr(sec_name, '+'))
+			prog->expected_attach_type = BPF_TRACE_KPROBE_MULTI;
+	}
+
+	return 0;
+}
+
 static int bpf_object_prepare_progs(struct bpf_object *obj)
 {
 	struct bpf_program *prog;
@@ -8294,22 +8331,9 @@ 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 = upgrade_program(obj, prog);
+		if (err)
+			return err;
 		err = bpf_object__sanitize_prog(obj, prog);
 		if (err)
 			return err;
@@ -9969,10 +9993,12 @@ static const struct bpf_sec_def section_defs[] = {
 	SEC_DEF("sk_reuseport/migrate",	SK_REUSEPORT, BPF_SK_REUSEPORT_SELECT_OR_MIGRATE, SEC_ATTACHABLE),
 	SEC_DEF("sk_reuseport",		SK_REUSEPORT, BPF_SK_REUSEPORT_SELECT, SEC_ATTACHABLE),
 	SEC_DEF("kprobe+",		KPROBE,	0, SEC_NONE, attach_kprobe),
+	SEC_DEF("kprobe.single+",	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("kretprobe.single+",	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),
@@ -11814,6 +11840,30 @@ bpf_program__attach_kprobe_opts(const struct bpf_program *prog,
 	offset = OPTS_GET(opts, offset, 0);
 	pe_opts.bpf_cookie = OPTS_GET(opts, bpf_cookie, 0);
 
+	/* This provides backwards compatibility to programs using kprobe, but
+	 * have been auto-upgraded to multi kprobe.
+	 */
+	if (prog->expected_attach_type == BPF_TRACE_KPROBE_MULTI &&
+	    offset == 0 && attach_mode == PROBE_ATTACH_MODE_DEFAULT) {
+		LIBBPF_OPTS(bpf_kprobe_multi_opts, multi_opts);
+		__u64 bpf_cookie;
+
+		multi_opts.retprobe = OPTS_GET(opts, retprobe, false);
+		multi_opts.syms = &func_name;
+		multi_opts.cnt = 1;
+		bpf_cookie = OPTS_GET(opts, bpf_cookie, 0);
+		if (bpf_cookie)
+			multi_opts.cookies = &bpf_cookie;
+
+		return bpf_program__attach_kprobe_multi_opts(prog, NULL, &multi_opts);
+	}
+
+	if (prog->expected_attach_type == BPF_TRACE_KPROBE_MULTI) {
+		pr_warn("prog '%s': multi-kprobe upgrade failed (off=%lu, mode=%d); use SEC(\"kprobe.single\")\n",
+			prog->name, offset, attach_mode);
+		return libbpf_err_ptr(-ENOTSUP);
+	}
+
 	legacy = determine_kprobe_perf_type() < 0;
 	switch (attach_mode) {
 	case PROBE_ATTACH_MODE_LEGACY:
@@ -12268,14 +12318,24 @@ static int attach_kprobe(const struct bpf_program *prog, long cookie, struct bpf
 	*link = NULL;
 
 	/* no auto-attach for SEC("kprobe") and SEC("kretprobe") */
-	if (strcmp(prog->sec_name, "kprobe") == 0 || strcmp(prog->sec_name, "kretprobe") == 0)
+	if (strcmp(prog->sec_name, "kprobe") == 0 ||
+	    strcmp(prog->sec_name, "kretprobe") == 0 ||
+	    strcmp(prog->sec_name, "kprobe.single") == 0 ||
+	    strcmp(prog->sec_name, "kretprobe.single") == 0)
 		return 0;
 
-	opts.retprobe = str_has_pfx(prog->sec_name, "kretprobe/");
-	if (opts.retprobe)
-		func_name = prog->sec_name + sizeof("kretprobe/") - 1;
-	else
-		func_name = prog->sec_name + sizeof("kprobe/") - 1;
+	if (str_has_pfx(prog->sec_name, "kretprobe/") ||
+	    str_has_pfx(prog->sec_name, "kretprobe.single/")) {
+		opts.retprobe = true;
+		func_name = str_has_pfx(prog->sec_name, "kretprobe/")
+			    ? prog->sec_name + sizeof("kretprobe/") - 1
+			    : prog->sec_name + sizeof("kretprobe.single/") - 1;
+	} else {
+		opts.retprobe = false;
+		func_name = str_has_pfx(prog->sec_name, "kprobe.single/")
+			    ? prog->sec_name + sizeof("kprobe.single/") - 1
+			    : prog->sec_name + sizeof("kprobe/") - 1;
+	}
 
 	n = sscanf(func_name, "%m[a-zA-Z0-9_.]+%li", &func, &offset);
 	if (n < 1) {
diff --git a/tools/testing/selftests/bpf/progs/get_func_ip_test.c b/tools/testing/selftests/bpf/progs/get_func_ip_test.c
index 2011cacdeb18..a039760b8516 100644
--- a/tools/testing/selftests/bpf/progs/get_func_ip_test.c
+++ b/tools/testing/selftests/bpf/progs/get_func_ip_test.c
@@ -73,7 +73,7 @@ int BPF_PROG(test5, int a, int *b, int ret)
 }
 
 __u64 test6_result = 0;
-SEC("?kprobe")
+SEC("?kprobe.single")
 int test6(struct pt_regs *ctx)
 {
 	__u64 addr = bpf_get_func_ip(ctx);
diff --git a/tools/testing/selftests/bpf/progs/missed_kprobe.c b/tools/testing/selftests/bpf/progs/missed_kprobe.c
index 51a4fe64c917..5f405888ed0b 100644
--- a/tools/testing/selftests/bpf/progs/missed_kprobe.c
+++ b/tools/testing/selftests/bpf/progs/missed_kprobe.c
@@ -16,14 +16,14 @@ int BPF_PROG(trigger)
 	return 0;
 }
 
-SEC("kprobe/bpf_fentry_test1")
+SEC("kprobe.single/bpf_fentry_test1")
 int test1(struct pt_regs *ctx)
 {
 	bpf_kfunc_common_test();
 	return 0;
 }
 
-SEC("kprobe/bpf_kfunc_common_test")
+SEC("kprobe.single/bpf_kfunc_common_test")
 int test2(struct pt_regs *ctx)
 {
 	return 0;
diff --git a/tools/testing/selftests/bpf/progs/test_attach_probe_manual.c b/tools/testing/selftests/bpf/progs/test_attach_probe_manual.c
index 7f08bce94596..eb0fa3b39c5f 100644
--- a/tools/testing/selftests/bpf/progs/test_attach_probe_manual.c
+++ b/tools/testing/selftests/bpf/progs/test_attach_probe_manual.c
@@ -14,14 +14,14 @@ int uretprobe_res = 0;
 int uprobe_byname_res = 0;
 void *user_ptr = 0;
 
-SEC("kprobe")
+SEC("kprobe.single")
 int handle_kprobe(struct pt_regs *ctx)
 {
 	kprobe_res = 1;
 	return 0;
 }
 
-SEC("kretprobe")
+SEC("kretprobe.single")
 int handle_kretprobe(struct pt_regs *ctx)
 {
 	kretprobe_res = 2;
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 8e47a818462f..bc6cfed71fb8 100644
--- a/tools/testing/selftests/bpf/progs/test_fill_link_info.c
+++ b/tools/testing/selftests/bpf/progs/test_fill_link_info.c
@@ -22,7 +22,7 @@ int unused(void)
 			CONFIG_PPC64 ? 0 : 1;
 }
 
-SEC("kprobe")
+SEC("kprobe.single")
 int BPF_PROG(kprobe_run)
 {
 	return 0;
-- 
2.53.0

Re: [RFC PATCH bpf-next v3 3/3] libbpf: Auto-upgrade kprobes to multi-kprobes 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 bc749b9c291e..f566527d95f3 100644
> --- a/tools/lib/bpf/libbpf.c
> +++ b/tools/lib/bpf/libbpf.c

[ ... ]

> @@ -11841,6 +11867,30 @@ bpf_program__attach_kprobe_opts(const struct bpf_program *prog,
>  	offset = OPTS_GET(opts, offset, 0);
>  	pe_opts.bpf_cookie = OPTS_GET(opts, bpf_cookie, 0);
>
> +	/* This provides backwards compatibility to programs using kprobe, but
> +	 * have been auto-upgraded to multi kprobe.
> +	 */
> +	if (prog->expected_attach_type == BPF_TRACE_KPROBE_MULTI &&
> +	    offset == 0 && attach_mode == PROBE_ATTACH_MODE_DEFAULT) {

[ ... ]

> +	if (prog->expected_attach_type == BPF_TRACE_KPROBE_MULTI) {
> +		pr_warn("prog '%s': multi-kprobe upgrade failed (off=%lu, mode=%d); use SEC(\"kprobe.single\")\n",
> +			prog->name, offset, attach_mode);

Should this use %zu instead of %lu for offset?  It is declared as
size_t in this function, and the existing pr_warn() calls further
down use %zx for the same variable.

> +		return libbpf_err_ptr(-ENOTSUP);
> +	}

[ ... ]

> @@ -12297,14 +12347,24 @@ static int attach_kprobe(const struct bpf_program *prog, long cookie, struct bpf
>  	*link = NULL;
>
>  	/* no auto-attach for SEC("kprobe") and SEC("kretprobe") */
> -	if (strcmp(prog->sec_name, "kprobe") == 0 || strcmp(prog->sec_name, "kretprobe") == 0)
> +	if (strcmp(prog->sec_name, "kprobe") == 0 ||
> +	    strcmp(prog->sec_name, "kretprobe") == 0 ||
> +	    strcmp(prog->sec_name, "kprobe.single") == 0 ||
> +	    strcmp(prog->sec_name, "kretprobe.single") == 0)
>  		return 0;

The comment above still only mentions SEC("kprobe") and
SEC("kretprobe").  Could it be updated to also mention the .single
variants that are now handled here?


---
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