Re: [PATCHv2 bpf-next 2/2] selftests/bpf: Add uprobe multi consumers test

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Thu, Jul 18, 2024 at 6:28 AM Jiri Olsa <jolsa@xxxxxxxxxx> wrote:
>
> Adding test that attached/detaches multiple consumers on

typo: attaches

> single uprobe and verifies all were hit as expected.
>
> Signed-off-by: Jiri Olsa <jolsa@xxxxxxxxxx>
> ---
>  .../bpf/prog_tests/uprobe_multi_test.c        | 211 +++++++++++++++++-
>  .../bpf/progs/uprobe_multi_consumers.c        |  39 ++++
>  2 files changed, 249 insertions(+), 1 deletion(-)
>  create mode 100644 tools/testing/selftests/bpf/progs/uprobe_multi_consumers.c
>

LGTM, took me a bit of extra time to validate the counting logic, but
it looks correct.

> diff --git a/tools/testing/selftests/bpf/prog_tests/uprobe_multi_test.c b/tools/testing/selftests/bpf/prog_tests/uprobe_multi_test.c
> index da8873f24a53..5228085c2240 100644
> --- a/tools/testing/selftests/bpf/prog_tests/uprobe_multi_test.c
> +++ b/tools/testing/selftests/bpf/prog_tests/uprobe_multi_test.c
> @@ -6,6 +6,7 @@
>  #include "uprobe_multi.skel.h"
>  #include "uprobe_multi_bench.skel.h"
>  #include "uprobe_multi_usdt.skel.h"
> +#include "uprobe_multi_consumers.skel.h"
>  #include "bpf/libbpf_internal.h"
>  #include "testing_helpers.h"
>  #include "../sdt.h"
> @@ -581,7 +582,7 @@ static void attach_uprobe_fail_refctr(struct uprobe_multi *skel)
>                 goto cleanup;
>
>         /*
> -        * We attach to 3 uprobes on 2 functions so 2 uprobes share single function,
> +        * We attach to 3 uprobes on 2 functions, so 2 uprobes share single function,

this probably belongs in patch #1

>          * but with different ref_ctr_offset which is not allowed and results in fail.
>          */
>         offsets[0] = tmp_offsets[0]; /* uprobe_multi_func_1 */
> @@ -722,6 +723,212 @@ static void test_link_api(void)
>         __test_link_api(child);
>  }
>
> +static struct bpf_program *
> +get_program(struct uprobe_multi_consumers *skel, int prog)
> +{
> +       switch (prog) {
> +       case 0:
> +               return skel->progs.uprobe_0;
> +       case 1:
> +               return skel->progs.uprobe_1;
> +       case 2:
> +               return skel->progs.uprobe_2;
> +       case 3:
> +               return skel->progs.uprobe_3;
> +       default:
> +               ASSERT_FAIL("get_program");
> +               return NULL;
> +       }
> +}
> +
> +static struct bpf_link **
> +get_link(struct uprobe_multi_consumers *skel, int link)
> +{
> +       switch (link) {
> +       case 0:
> +               return &skel->links.uprobe_0;
> +       case 1:
> +               return &skel->links.uprobe_1;
> +       case 2:
> +               return &skel->links.uprobe_2;
> +       case 3:
> +               return &skel->links.uprobe_3;
> +       default:
> +               ASSERT_FAIL("get_link");
> +               return NULL;
> +       }
> +}
> +
> +static int uprobe_attach(struct uprobe_multi_consumers *skel, int idx)
> +{
> +       struct bpf_program *prog = get_program(skel, idx);
> +       struct bpf_link **link = get_link(skel, idx);
> +       LIBBPF_OPTS(bpf_uprobe_multi_opts, opts);
> +
> +       /*
> +        * bit/prog: 0,1 uprobe entry
> +        * bit/prog: 2,3 uprobe return
> +        */
> +       opts.retprobe = idx == 2 || idx == 3;
> +
> +       *link = bpf_program__attach_uprobe_multi(prog, 0, "/proc/self/exe",


this will crash if idx is wrong, let's add explicit NULL checks for
link and prog, just to fail gracefully?


> +                                               "uprobe_session_consumer_test",
> +                                               &opts);
> +       if (!ASSERT_OK_PTR(*link, "bpf_program__attach_uprobe_multi"))
> +               return -1;
> +       return 0;
> +}
> +
> +static void uprobe_detach(struct uprobe_multi_consumers *skel, int idx)
> +{
> +       struct bpf_link **link = get_link(skel, idx);
> +
> +       bpf_link__destroy(*link);
> +       *link = NULL;
> +}
> +
> +static bool test_bit(int bit, unsigned long val)
> +{
> +       return val & (1 << bit);
> +}
> +
> +noinline int
> +uprobe_session_consumer_test(struct uprobe_multi_consumers *skel,

this gave me pause, I was frantically recalling when did we end up
landing uprobe sessions support :)


> +                            unsigned long before, unsigned long after)
> +{
> +       int idx;
> +
> +       /* detach uprobe for each unset programs in 'before' state ... */
> +       for (idx = 0; idx < 4; idx++) {
> +               if (test_bit(idx, before) && !test_bit(idx, after))
> +                       uprobe_detach(skel, idx);
> +       }
> +
> +       /* ... and attach all new programs in 'after' state */
> +       for (idx = 0; idx < 4; idx++) {
> +               if (!test_bit(idx, before) && test_bit(idx, after)) {
> +                       if (!ASSERT_OK(uprobe_attach(skel, idx), "uprobe_attach_after"))
> +                               return -1;
> +               }
> +       }
> +       return 0;
> +}
> +
> +static void session_consumer_test(struct uprobe_multi_consumers *skel,
> +                                 unsigned long before, unsigned long after)
> +{
> +       int err, idx;
> +
> +       printf("session_consumer_test before %lu after %lu\n", before, after);
> +
> +       /* 'before' is each, we attach uprobe for every set idx */
> +       for (idx = 0; idx < 4; idx++) {
> +               if (test_bit(idx, before)) {
> +                       if (!ASSERT_OK(uprobe_attach(skel, idx), "uprobe_attach_before"))
> +                               goto cleanup;
> +               }
> +       }
> +
> +       err = uprobe_session_consumer_test(skel, before, after);
> +       if (!ASSERT_EQ(err, 0, "uprobe_session_consumer_test"))
> +               goto cleanup;
> +
> +       for (idx = 0; idx < 4; idx++) {
> +               const char *fmt = "BUG";
> +               __u64 val = 0;
> +
> +               if (idx < 2) {
> +                       /*
> +                        * uprobe entry
> +                        *   +1 if define in 'before'
> +                        */
> +                       if (test_bit(idx, before))
> +                               val++;
> +                       fmt = "prog 0/1: uprobe";
> +               } else {
> +                       /* uprobe return is tricky ;-)
> +                        *
> +                        * to trigger uretprobe consumer, the uretprobe needs to be installed,
> +                        * which means one of the 'return' uprobes was alive when probe was hit:
> +                        *
> +                        *   idxs: 2/3 uprobe return in 'installed' mask
> +                        *
> +                        * in addition if 'after' state removes everything that was installed in
> +                        * 'before' state, then uprobe kernel object goes away and return uprobe
> +                        * is not installed and we won't hit it even if it's in 'after' state.
> +                        */

yeah, this is tricky, thanks for writing this out, seems correct to me

> +                       unsigned long installed = before & 0b1100; // is uretprobe installed
> +                       unsigned long exists    = before & after;  // did uprobe go away
> +
> +                       if (installed && exists && test_bit(idx, after))

nit: naming didn't really help (actually probably hurt the analysis).
installed is whether we had any uretprobes, so "had_uretprobes"?
exists is whether uprobe stayed attached during function call, right,
so maybe "probe_preserved" or something like that?

I.e., the condition should say "if we had any uretprobes, and the
probe instance stayed alive, and the program is still attached at
return".

> +                               val++;
> +                       fmt = "idx 2/3: uretprobe";
> +               }
> +
> +               ASSERT_EQ(skel->bss->uprobe_result[idx], val, fmt);
> +               skel->bss->uprobe_result[idx] = 0;
> +       }
> +
> +cleanup:
> +       for (idx = 0; idx < 4; idx++)
> +               uprobe_detach(skel, idx);
> +}
> +

[...]





[Index of Archives]     [Linux Samsung SoC]     [Linux Rockchip SoC]     [Linux Actions SoC]     [Linux for Synopsys ARC Processors]     [Linux NFS]     [Linux NILFS]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]


  Powered by Linux