[PATCH perf/core 18/22] selftests/bpf: Add uprobe_regs_equal test

Jiri Olsa posted 22 patches 7 months, 4 weeks ago
There is a newer version of this series
[PATCH perf/core 18/22] selftests/bpf: Add uprobe_regs_equal test
Posted by Jiri Olsa 7 months, 4 weeks ago
Changing uretprobe_regs_trigger to allow the test for both
uprobe and uretprobe and renaming it to uprobe_regs_equal.

We check that both uprobe and uretprobe probes (bpf programs)
see expected registers with few exceptions.

Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
 .../selftests/bpf/prog_tests/uprobe_syscall.c | 58 ++++++++++++++-----
 .../selftests/bpf/progs/uprobe_syscall.c      |  4 +-
 2 files changed, 45 insertions(+), 17 deletions(-)

diff --git a/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c b/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c
index f001986981ab..6d88c5b0f6aa 100644
--- a/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c
+++ b/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c
@@ -18,15 +18,17 @@
 
 #pragma GCC diagnostic ignored "-Wattributes"
 
-__naked unsigned long uretprobe_regs_trigger(void)
+__attribute__((aligned(16)))
+__nocf_check __weak __naked unsigned long uprobe_regs_trigger(void)
 {
 	asm volatile (
-		"movq $0xdeadbeef, %rax\n"
+		".byte 0x0f, 0x1f, 0x44, 0x00, 0x00	\n"
+		"movq $0xdeadbeef, %rax			\n"
 		"ret\n"
 	);
 }
 
-__naked void uretprobe_regs(struct pt_regs *before, struct pt_regs *after)
+__naked void uprobe_regs(struct pt_regs *before, struct pt_regs *after)
 {
 	asm volatile (
 		"movq %r15,   0(%rdi)\n"
@@ -47,15 +49,17 @@ __naked void uretprobe_regs(struct pt_regs *before, struct pt_regs *after)
 		"movq   $0, 120(%rdi)\n" /* orig_rax */
 		"movq   $0, 128(%rdi)\n" /* rip      */
 		"movq   $0, 136(%rdi)\n" /* cs       */
+		"pushq %rax\n"
 		"pushf\n"
 		"pop %rax\n"
 		"movq %rax, 144(%rdi)\n" /* eflags   */
+		"pop %rax\n"
 		"movq %rsp, 152(%rdi)\n" /* rsp      */
 		"movq   $0, 160(%rdi)\n" /* ss       */
 
 		/* save 2nd argument */
 		"pushq %rsi\n"
-		"call uretprobe_regs_trigger\n"
+		"call uprobe_regs_trigger\n"
 
 		/* save  return value and load 2nd argument pointer to rax */
 		"pushq %rax\n"
@@ -95,25 +99,37 @@ __naked void uretprobe_regs(struct pt_regs *before, struct pt_regs *after)
 );
 }
 
-static void test_uretprobe_regs_equal(void)
+static void test_uprobe_regs_equal(bool retprobe)
 {
+	LIBBPF_OPTS(bpf_uprobe_opts, opts,
+		.retprobe = retprobe,
+	);
 	struct uprobe_syscall *skel = NULL;
 	struct pt_regs before = {}, after = {};
 	unsigned long *pb = (unsigned long *) &before;
 	unsigned long *pa = (unsigned long *) &after;
 	unsigned long *pp;
+	unsigned long offset;
 	unsigned int i, cnt;
-	int err;
+
+	offset = get_uprobe_offset(&uprobe_regs_trigger);
+	if (!ASSERT_GE(offset, 0, "get_uprobe_offset"))
+		return;
 
 	skel = uprobe_syscall__open_and_load();
 	if (!ASSERT_OK_PTR(skel, "uprobe_syscall__open_and_load"))
 		goto cleanup;
 
-	err = uprobe_syscall__attach(skel);
-	if (!ASSERT_OK(err, "uprobe_syscall__attach"))
+	skel->links.probe = bpf_program__attach_uprobe_opts(skel->progs.probe,
+				0, "/proc/self/exe", offset, &opts);
+	if (!ASSERT_OK_PTR(skel->links.probe, "bpf_program__attach_uprobe_opts"))
 		goto cleanup;
 
-	uretprobe_regs(&before, &after);
+	/* make sure uprobe gets optimized */
+	if (!retprobe)
+		uprobe_regs_trigger();
+
+	uprobe_regs(&before, &after);
 
 	pp = (unsigned long *) &skel->bss->regs;
 	cnt = sizeof(before)/sizeof(*pb);
@@ -122,7 +138,7 @@ static void test_uretprobe_regs_equal(void)
 		unsigned int offset = i * sizeof(unsigned long);
 
 		/*
-		 * Check register before and after uretprobe_regs_trigger call
+		 * Check register before and after uprobe_regs_trigger call
 		 * that triggers the uretprobe.
 		 */
 		switch (offset) {
@@ -136,7 +152,7 @@ static void test_uretprobe_regs_equal(void)
 
 		/*
 		 * Check register seen from bpf program and register after
-		 * uretprobe_regs_trigger call
+		 * uprobe_regs_trigger call (with rax exception, check below).
 		 */
 		switch (offset) {
 		/*
@@ -149,6 +165,15 @@ static void test_uretprobe_regs_equal(void)
 		case offsetof(struct pt_regs, rsp):
 		case offsetof(struct pt_regs, ss):
 			break;
+		/*
+		 * uprobe does not see return value in rax, it needs to see the
+		 * original (before) rax value
+		 */
+		case offsetof(struct pt_regs, rax):
+			if (!retprobe) {
+				ASSERT_EQ(pp[i], pb[i], "uprobe rax prog-before value check");
+				break;
+			}
 		default:
 			if (!ASSERT_EQ(pp[i], pa[i], "register prog-after value check"))
 				fprintf(stdout, "failed register offset %u\n", offset);
@@ -186,13 +211,13 @@ static void test_uretprobe_regs_change(void)
 	unsigned long cnt = sizeof(before)/sizeof(*pb);
 	unsigned int i, err, offset;
 
-	offset = get_uprobe_offset(uretprobe_regs_trigger);
+	offset = get_uprobe_offset(uprobe_regs_trigger);
 
 	err = write_bpf_testmod_uprobe(offset);
 	if (!ASSERT_OK(err, "register_uprobe"))
 		return;
 
-	uretprobe_regs(&before, &after);
+	uprobe_regs(&before, &after);
 
 	err = write_bpf_testmod_uprobe(0);
 	if (!ASSERT_OK(err, "unregister_uprobe"))
@@ -605,7 +630,8 @@ static void test_uretprobe_shadow_stack(void)
 	/* Run all the tests with shadow stack in place. */
 	shstk_is_enabled = true;
 
-	test_uretprobe_regs_equal();
+	test_uprobe_regs_equal(false);
+	test_uprobe_regs_equal(true);
 	test_uretprobe_regs_change();
 	test_uretprobe_syscall_call();
 
@@ -728,7 +754,7 @@ static void test_uprobe_sigill(void)
 static void __test_uprobe_syscall(void)
 {
 	if (test__start_subtest("uretprobe_regs_equal"))
-		test_uretprobe_regs_equal();
+		test_uprobe_regs_equal(true);
 	if (test__start_subtest("uretprobe_regs_change"))
 		test_uretprobe_regs_change();
 	if (test__start_subtest("uretprobe_syscall_call"))
@@ -747,6 +773,8 @@ static void __test_uprobe_syscall(void)
 		test_uprobe_race();
 	if (test__start_subtest("uprobe_sigill"))
 		test_uprobe_sigill();
+	if (test__start_subtest("uprobe_regs_equal"))
+		test_uprobe_regs_equal(false);
 }
 #else
 static void __test_uprobe_syscall(void)
diff --git a/tools/testing/selftests/bpf/progs/uprobe_syscall.c b/tools/testing/selftests/bpf/progs/uprobe_syscall.c
index 8a4fa6c7ef59..e08c31669e5a 100644
--- a/tools/testing/selftests/bpf/progs/uprobe_syscall.c
+++ b/tools/testing/selftests/bpf/progs/uprobe_syscall.c
@@ -7,8 +7,8 @@ struct pt_regs regs;
 
 char _license[] SEC("license") = "GPL";
 
-SEC("uretprobe//proc/self/exe:uretprobe_regs_trigger")
-int uretprobe(struct pt_regs *ctx)
+SEC("uprobe")
+int probe(struct pt_regs *ctx)
 {
 	__builtin_memcpy(&regs, ctx, sizeof(regs));
 	return 0;
-- 
2.49.0
Re: [PATCH perf/core 18/22] selftests/bpf: Add uprobe_regs_equal test
Posted by Andrii Nakryiko 7 months, 3 weeks ago
On Mon, Apr 21, 2025 at 2:48 PM Jiri Olsa <jolsa@kernel.org> wrote:
>
> Changing uretprobe_regs_trigger to allow the test for both
> uprobe and uretprobe and renaming it to uprobe_regs_equal.
>
> We check that both uprobe and uretprobe probes (bpf programs)
> see expected registers with few exceptions.
>
> Signed-off-by: Jiri Olsa <jolsa@kernel.org>
> ---
>  .../selftests/bpf/prog_tests/uprobe_syscall.c | 58 ++++++++++++++-----
>  .../selftests/bpf/progs/uprobe_syscall.c      |  4 +-
>  2 files changed, 45 insertions(+), 17 deletions(-)
>
> diff --git a/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c b/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c
> index f001986981ab..6d88c5b0f6aa 100644
> --- a/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c
> +++ b/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c
> @@ -18,15 +18,17 @@
>
>  #pragma GCC diagnostic ignored "-Wattributes"
>
> -__naked unsigned long uretprobe_regs_trigger(void)
> +__attribute__((aligned(16)))
> +__nocf_check __weak __naked unsigned long uprobe_regs_trigger(void)
>  {
>         asm volatile (
> -               "movq $0xdeadbeef, %rax\n"
> +               ".byte 0x0f, 0x1f, 0x44, 0x00, 0x00     \n"

Is it me not being hardcore enough... But is anyone supposed to know
that this is nop5? ;) maybe add /* nop5 */ comment on the side?

> +               "movq $0xdeadbeef, %rax                 \n"

ret\n doesn't align newline, and uprobe_regs below don't either. So
maybe don't align them at all here?

>                 "ret\n"
>         );
>  }
>
> -__naked void uretprobe_regs(struct pt_regs *before, struct pt_regs *after)
> +__naked void uprobe_regs(struct pt_regs *before, struct pt_regs *after)
>  {
>         asm volatile (
>                 "movq %r15,   0(%rdi)\n"
> @@ -47,15 +49,17 @@ __naked void uretprobe_regs(struct pt_regs *before, struct pt_regs *after)
>                 "movq   $0, 120(%rdi)\n" /* orig_rax */
>                 "movq   $0, 128(%rdi)\n" /* rip      */
>                 "movq   $0, 136(%rdi)\n" /* cs       */
> +               "pushq %rax\n"
>                 "pushf\n"
>                 "pop %rax\n"
>                 "movq %rax, 144(%rdi)\n" /* eflags   */
> +               "pop %rax\n"
>                 "movq %rsp, 152(%rdi)\n" /* rsp      */
>                 "movq   $0, 160(%rdi)\n" /* ss       */
>
>                 /* save 2nd argument */
>                 "pushq %rsi\n"
> -               "call uretprobe_regs_trigger\n"
> +               "call uprobe_regs_trigger\n"
>
>                 /* save  return value and load 2nd argument pointer to rax */
>                 "pushq %rax\n"
> @@ -95,25 +99,37 @@ __naked void uretprobe_regs(struct pt_regs *before, struct pt_regs *after)
>  );
>  }
>
> -static void test_uretprobe_regs_equal(void)
> +static void test_uprobe_regs_equal(bool retprobe)
>  {
> +       LIBBPF_OPTS(bpf_uprobe_opts, opts,
> +               .retprobe = retprobe,
> +       );
>         struct uprobe_syscall *skel = NULL;
>         struct pt_regs before = {}, after = {};
>         unsigned long *pb = (unsigned long *) &before;
>         unsigned long *pa = (unsigned long *) &after;
>         unsigned long *pp;
> +       unsigned long offset;
>         unsigned int i, cnt;
> -       int err;
> +
> +       offset = get_uprobe_offset(&uprobe_regs_trigger);
> +       if (!ASSERT_GE(offset, 0, "get_uprobe_offset"))
> +               return;
>
>         skel = uprobe_syscall__open_and_load();
>         if (!ASSERT_OK_PTR(skel, "uprobe_syscall__open_and_load"))
>                 goto cleanup;
>
> -       err = uprobe_syscall__attach(skel);
> -       if (!ASSERT_OK(err, "uprobe_syscall__attach"))
> +       skel->links.probe = bpf_program__attach_uprobe_opts(skel->progs.probe,
> +                               0, "/proc/self/exe", offset, &opts);
> +       if (!ASSERT_OK_PTR(skel->links.probe, "bpf_program__attach_uprobe_opts"))
>                 goto cleanup;
>
> -       uretprobe_regs(&before, &after);
> +       /* make sure uprobe gets optimized */
> +       if (!retprobe)
> +               uprobe_regs_trigger();
> +
> +       uprobe_regs(&before, &after);
>
>         pp = (unsigned long *) &skel->bss->regs;
>         cnt = sizeof(before)/sizeof(*pb);
> @@ -122,7 +138,7 @@ static void test_uretprobe_regs_equal(void)
>                 unsigned int offset = i * sizeof(unsigned long);
>
>                 /*
> -                * Check register before and after uretprobe_regs_trigger call
> +                * Check register before and after uprobe_regs_trigger call
>                  * that triggers the uretprobe.
>                  */
>                 switch (offset) {
> @@ -136,7 +152,7 @@ static void test_uretprobe_regs_equal(void)
>
>                 /*
>                  * Check register seen from bpf program and register after
> -                * uretprobe_regs_trigger call
> +                * uprobe_regs_trigger call (with rax exception, check below).
>                  */
>                 switch (offset) {
>                 /*
> @@ -149,6 +165,15 @@ static void test_uretprobe_regs_equal(void)
>                 case offsetof(struct pt_regs, rsp):
>                 case offsetof(struct pt_regs, ss):
>                         break;
> +               /*
> +                * uprobe does not see return value in rax, it needs to see the
> +                * original (before) rax value
> +                */
> +               case offsetof(struct pt_regs, rax):
> +                       if (!retprobe) {
> +                               ASSERT_EQ(pp[i], pb[i], "uprobe rax prog-before value check");
> +                               break;
> +                       }
>                 default:
>                         if (!ASSERT_EQ(pp[i], pa[i], "register prog-after value check"))
>                                 fprintf(stdout, "failed register offset %u\n", offset);
> @@ -186,13 +211,13 @@ static void test_uretprobe_regs_change(void)
>         unsigned long cnt = sizeof(before)/sizeof(*pb);
>         unsigned int i, err, offset;
>
> -       offset = get_uprobe_offset(uretprobe_regs_trigger);
> +       offset = get_uprobe_offset(uprobe_regs_trigger);
>
>         err = write_bpf_testmod_uprobe(offset);
>         if (!ASSERT_OK(err, "register_uprobe"))
>                 return;
>
> -       uretprobe_regs(&before, &after);
> +       uprobe_regs(&before, &after);
>
>         err = write_bpf_testmod_uprobe(0);
>         if (!ASSERT_OK(err, "unregister_uprobe"))
> @@ -605,7 +630,8 @@ static void test_uretprobe_shadow_stack(void)
>         /* Run all the tests with shadow stack in place. */
>         shstk_is_enabled = true;
>
> -       test_uretprobe_regs_equal();
> +       test_uprobe_regs_equal(false);
> +       test_uprobe_regs_equal(true);
>         test_uretprobe_regs_change();
>         test_uretprobe_syscall_call();
>
> @@ -728,7 +754,7 @@ static void test_uprobe_sigill(void)
>  static void __test_uprobe_syscall(void)
>  {
>         if (test__start_subtest("uretprobe_regs_equal"))
> -               test_uretprobe_regs_equal();
> +               test_uprobe_regs_equal(true);
>         if (test__start_subtest("uretprobe_regs_change"))
>                 test_uretprobe_regs_change();
>         if (test__start_subtest("uretprobe_syscall_call"))
> @@ -747,6 +773,8 @@ static void __test_uprobe_syscall(void)
>                 test_uprobe_race();
>         if (test__start_subtest("uprobe_sigill"))
>                 test_uprobe_sigill();
> +       if (test__start_subtest("uprobe_regs_equal"))
> +               test_uprobe_regs_equal(false);
>  }
>  #else
>  static void __test_uprobe_syscall(void)
> diff --git a/tools/testing/selftests/bpf/progs/uprobe_syscall.c b/tools/testing/selftests/bpf/progs/uprobe_syscall.c
> index 8a4fa6c7ef59..e08c31669e5a 100644
> --- a/tools/testing/selftests/bpf/progs/uprobe_syscall.c
> +++ b/tools/testing/selftests/bpf/progs/uprobe_syscall.c
> @@ -7,8 +7,8 @@ struct pt_regs regs;
>
>  char _license[] SEC("license") = "GPL";
>
> -SEC("uretprobe//proc/self/exe:uretprobe_regs_trigger")
> -int uretprobe(struct pt_regs *ctx)
> +SEC("uprobe")
> +int probe(struct pt_regs *ctx)
>  {
>         __builtin_memcpy(&regs, ctx, sizeof(regs));
>         return 0;
> --
> 2.49.0
>
Re: [PATCH perf/core 18/22] selftests/bpf: Add uprobe_regs_equal test
Posted by Jiri Olsa 7 months, 3 weeks ago
On Wed, Apr 23, 2025 at 10:46:24AM -0700, Andrii Nakryiko wrote:
> On Mon, Apr 21, 2025 at 2:48 PM Jiri Olsa <jolsa@kernel.org> wrote:
> >
> > Changing uretprobe_regs_trigger to allow the test for both
> > uprobe and uretprobe and renaming it to uprobe_regs_equal.
> >
> > We check that both uprobe and uretprobe probes (bpf programs)
> > see expected registers with few exceptions.
> >
> > Signed-off-by: Jiri Olsa <jolsa@kernel.org>
> > ---
> >  .../selftests/bpf/prog_tests/uprobe_syscall.c | 58 ++++++++++++++-----
> >  .../selftests/bpf/progs/uprobe_syscall.c      |  4 +-
> >  2 files changed, 45 insertions(+), 17 deletions(-)
> >
> > diff --git a/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c b/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c
> > index f001986981ab..6d88c5b0f6aa 100644
> > --- a/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c
> > +++ b/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c
> > @@ -18,15 +18,17 @@
> >
> >  #pragma GCC diagnostic ignored "-Wattributes"
> >
> > -__naked unsigned long uretprobe_regs_trigger(void)
> > +__attribute__((aligned(16)))
> > +__nocf_check __weak __naked unsigned long uprobe_regs_trigger(void)
> >  {
> >         asm volatile (
> > -               "movq $0xdeadbeef, %rax\n"
> > +               ".byte 0x0f, 0x1f, 0x44, 0x00, 0x00     \n"
> 
> Is it me not being hardcore enough... But is anyone supposed to know
> that this is nop5? ;) maybe add /* nop5 */ comment on the side?

ok, will add the comment :)

> 
> > +               "movq $0xdeadbeef, %rax                 \n"
> 
> ret\n doesn't align newline, and uprobe_regs below don't either. So
> maybe don't align them at all here?

ok

thanks,
jirka