selftests/bpf: Add tests for symbol versioning for uprobe
authorHengqi Chen <hengqi.chen@gmail.com>
Mon, 18 Sep 2023 02:48:13 +0000 (02:48 +0000)
committerAndrii Nakryiko <andrii@kernel.org>
Fri, 22 Sep 2023 21:27:41 +0000 (14:27 -0700)
This exercises the newly added dynsym symbol versioning logics.
Now we accept symbols in form of func, func@LIB_VERSION or
func@@LIB_VERSION.

The test rely on liburandom_read.so. For liburandom_read.so, we have:

    $ nm -D liburandom_read.so
                     w __cxa_finalize@GLIBC_2.17
                     w __gmon_start__
                     w _ITM_deregisterTMCloneTable
                     w _ITM_registerTMCloneTable
    0000000000000000 A LIBURANDOM_READ_1.0.0
    0000000000000000 A LIBURANDOM_READ_2.0.0
    000000000000081c T urandlib_api@@LIBURANDOM_READ_2.0.0
    0000000000000814 T urandlib_api@LIBURANDOM_READ_1.0.0
    0000000000000824 T urandlib_api_sameoffset@LIBURANDOM_READ_1.0.0
    0000000000000824 T urandlib_api_sameoffset@@LIBURANDOM_READ_2.0.0
    000000000000082c T urandlib_read_without_sema@@LIBURANDOM_READ_1.0.0
    00000000000007c4 T urandlib_read_with_sema@@LIBURANDOM_READ_1.0.0
    0000000000011018 D urandlib_read_with_sema_semaphore@@LIBURANDOM_READ_1.0.0

For `urandlib_api`, specifying `urandlib_api` will cause a conflict because
there are two symbols named urandlib_api and both are global bind.
For `urandlib_api_sameoffset`, there are also two symbols in the .so, but
both are at the same offset and essentially they refer to the same function
so no conflict.

Signed-off-by: Hengqi Chen <hengqi.chen@gmail.com>
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
Reviewed-by: Alan Maguire <alan.maguire@oracle.com>
Acked-by: Jiri Olsa <jolsa@kernel.org>
Link: https://lore.kernel.org/bpf/20230918024813.237475-4-hengqi.chen@gmail.com
tools/testing/selftests/bpf/Makefile
tools/testing/selftests/bpf/liburandom_read.map [new file with mode: 0644]
tools/testing/selftests/bpf/prog_tests/uprobe.c [new file with mode: 0644]
tools/testing/selftests/bpf/progs/test_uprobe.c [new file with mode: 0644]
tools/testing/selftests/bpf/urandom_read.c
tools/testing/selftests/bpf/urandom_read_lib1.c

index caede9b574cb1632fdcb00061a662f6151d61208..47365161b6fc4a50d77dde064232ea8fbca23d37 100644 (file)
@@ -196,11 +196,12 @@ endif
 
 # Filter out -static for liburandom_read.so and its dependent targets so that static builds
 # do not fail. Static builds leave urandom_read relying on system-wide shared libraries.
-$(OUTPUT)/liburandom_read.so: urandom_read_lib1.c urandom_read_lib2.c
+$(OUTPUT)/liburandom_read.so: urandom_read_lib1.c urandom_read_lib2.c liburandom_read.map
        $(call msg,LIB,,$@)
        $(Q)$(CLANG) $(filter-out -static,$(CFLAGS) $(LDFLAGS))   \
-                    $^ $(filter-out -static,$(LDLIBS))      \
+                    $(filter %.c,$^) $(filter-out -static,$(LDLIBS)) \
                     -fuse-ld=$(LLD) -Wl,-znoseparate-code -Wl,--build-id=sha1 \
+                    -Wl,--version-script=liburandom_read.map \
                     -fPIC -shared -o $@
 
 $(OUTPUT)/urandom_read: urandom_read.c urandom_read_aux.c $(OUTPUT)/liburandom_read.so
diff --git a/tools/testing/selftests/bpf/liburandom_read.map b/tools/testing/selftests/bpf/liburandom_read.map
new file mode 100644 (file)
index 0000000..38a97a4
--- /dev/null
@@ -0,0 +1,15 @@
+LIBURANDOM_READ_1.0.0 {
+       global:
+               urandlib_api;
+               urandlib_api_sameoffset;
+               urandlib_read_without_sema;
+               urandlib_read_with_sema;
+               urandlib_read_with_sema_semaphore;
+       local:
+               *;
+};
+
+LIBURANDOM_READ_2.0.0 {
+       global:
+               urandlib_api;
+} LIBURANDOM_READ_1.0.0;
diff --git a/tools/testing/selftests/bpf/prog_tests/uprobe.c b/tools/testing/selftests/bpf/prog_tests/uprobe.c
new file mode 100644 (file)
index 0000000..cf3e0e7
--- /dev/null
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2023 Hengqi Chen */
+
+#include <test_progs.h>
+#include "test_uprobe.skel.h"
+
+static FILE *urand_spawn(int *pid)
+{
+       FILE *f;
+
+       /* urandom_read's stdout is wired into f */
+       f = popen("./urandom_read 1 report-pid", "r");
+       if (!f)
+               return NULL;
+
+       if (fscanf(f, "%d", pid) != 1) {
+               pclose(f);
+               errno = EINVAL;
+               return NULL;
+       }
+
+       return f;
+}
+
+static int urand_trigger(FILE **urand_pipe)
+{
+       int exit_code;
+
+       /* pclose() waits for child process to exit and returns their exit code */
+       exit_code = pclose(*urand_pipe);
+       *urand_pipe = NULL;
+
+       return exit_code;
+}
+
+void test_uprobe(void)
+{
+       LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts);
+       struct test_uprobe *skel;
+       FILE *urand_pipe = NULL;
+       int urand_pid = 0, err;
+
+       skel = test_uprobe__open_and_load();
+       if (!ASSERT_OK_PTR(skel, "skel_open"))
+               return;
+
+       urand_pipe = urand_spawn(&urand_pid);
+       if (!ASSERT_OK_PTR(urand_pipe, "urand_spawn"))
+               goto cleanup;
+
+       skel->bss->my_pid = urand_pid;
+
+       /* Manual attach uprobe to urandlib_api
+        * There are two `urandlib_api` symbols in .dynsym section:
+        *   - urandlib_api@LIBURANDOM_READ_1.0.0
+        *   - urandlib_api@@LIBURANDOM_READ_2.0.0
+        * Both are global bind and would cause a conflict if user
+        * specify the symbol name without a version suffix
+        */
+       uprobe_opts.func_name = "urandlib_api";
+       skel->links.test4 = bpf_program__attach_uprobe_opts(skel->progs.test4,
+                                                           urand_pid,
+                                                           "./liburandom_read.so",
+                                                           0 /* offset */,
+                                                           &uprobe_opts);
+       if (!ASSERT_ERR_PTR(skel->links.test4, "urandlib_api_attach_conflict"))
+               goto cleanup;
+
+       uprobe_opts.func_name = "urandlib_api@LIBURANDOM_READ_1.0.0";
+       skel->links.test4 = bpf_program__attach_uprobe_opts(skel->progs.test4,
+                                                           urand_pid,
+                                                           "./liburandom_read.so",
+                                                           0 /* offset */,
+                                                           &uprobe_opts);
+       if (!ASSERT_OK_PTR(skel->links.test4, "urandlib_api_attach_ok"))
+               goto cleanup;
+
+       /* Auto attach 3 u[ret]probes to urandlib_api_sameoffset */
+       err = test_uprobe__attach(skel);
+       if (!ASSERT_OK(err, "skel_attach"))
+               goto cleanup;
+
+       /* trigger urandom_read */
+       ASSERT_OK(urand_trigger(&urand_pipe), "urand_exit_code");
+
+       ASSERT_EQ(skel->bss->test1_result, 1, "urandlib_api_sameoffset");
+       ASSERT_EQ(skel->bss->test2_result, 1, "urandlib_api_sameoffset@v1");
+       ASSERT_EQ(skel->bss->test3_result, 3, "urandlib_api_sameoffset@@v2");
+       ASSERT_EQ(skel->bss->test4_result, 1, "urandlib_api");
+
+cleanup:
+       if (urand_pipe)
+               pclose(urand_pipe);
+       test_uprobe__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/progs/test_uprobe.c b/tools/testing/selftests/bpf/progs/test_uprobe.c
new file mode 100644 (file)
index 0000000..896c88a
--- /dev/null
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2023 Hengqi Chen */
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+pid_t my_pid = 0;
+
+int test1_result = 0;
+int test2_result = 0;
+int test3_result = 0;
+int test4_result = 0;
+
+SEC("uprobe/./liburandom_read.so:urandlib_api_sameoffset")
+int BPF_UPROBE(test1)
+{
+       pid_t pid = bpf_get_current_pid_tgid() >> 32;
+
+       if (pid != my_pid)
+               return 0;
+
+       test1_result = 1;
+       return 0;
+}
+
+SEC("uprobe/./liburandom_read.so:urandlib_api_sameoffset@LIBURANDOM_READ_1.0.0")
+int BPF_UPROBE(test2)
+{
+       pid_t pid = bpf_get_current_pid_tgid() >> 32;
+
+       if (pid != my_pid)
+               return 0;
+
+       test2_result = 1;
+       return 0;
+}
+
+SEC("uretprobe/./liburandom_read.so:urandlib_api_sameoffset@@LIBURANDOM_READ_2.0.0")
+int BPF_URETPROBE(test3, int ret)
+{
+       pid_t pid = bpf_get_current_pid_tgid() >> 32;
+
+       if (pid != my_pid)
+               return 0;
+
+       test3_result = ret;
+       return 0;
+}
+
+SEC("uprobe")
+int BPF_UPROBE(test4)
+{
+       pid_t pid = bpf_get_current_pid_tgid() >> 32;
+
+       if (pid != my_pid)
+               return 0;
+
+       test4_result = 1;
+       return 0;
+}
index e92644d0fa75a31b3e386063d85b4e8a0d009601..4ed795655b9ffbd19c039edf02e16f5dc1f87a6d 100644 (file)
@@ -11,6 +11,9 @@
 #define _SDT_HAS_SEMAPHORES 1
 #include "sdt.h"
 
+#define SHARED 1
+#include "bpf/libbpf_internal.h"
+
 #define SEC(name) __attribute__((section(name), used))
 
 #define BUF_SIZE 256
@@ -21,10 +24,14 @@ void urand_read_without_sema(int iter_num, int iter_cnt, int read_sz);
 void urandlib_read_with_sema(int iter_num, int iter_cnt, int read_sz);
 void urandlib_read_without_sema(int iter_num, int iter_cnt, int read_sz);
 
+int urandlib_api(void);
+COMPAT_VERSION(urandlib_api_old, urandlib_api, LIBURANDOM_READ_1.0.0)
+int urandlib_api_old(void);
+int urandlib_api_sameoffset(void);
+
 unsigned short urand_read_with_sema_semaphore SEC(".probes");
 
-static __attribute__((noinline))
-void urandom_read(int fd, int count)
+static noinline void urandom_read(int fd, int count)
 {
        char buf[BUF_SIZE];
        int i;
@@ -83,6 +90,10 @@ int main(int argc, char *argv[])
 
        urandom_read(fd, count);
 
+       urandlib_api();
+       urandlib_api_old();
+       urandlib_api_sameoffset();
+
        close(fd);
        return 0;
 }
index 86186e24b74093cf5d7913a675052efa0497257e..8c1356d8b4ee1807b36e1ba3aa997e09f492e467 100644 (file)
@@ -3,6 +3,9 @@
 #define _SDT_HAS_SEMAPHORES 1
 #include "sdt.h"
 
+#define SHARED 1
+#include "bpf/libbpf_internal.h"
+
 #define SEC(name) __attribute__((section(name), used))
 
 unsigned short urandlib_read_with_sema_semaphore SEC(".probes");
@@ -11,3 +14,22 @@ void urandlib_read_with_sema(int iter_num, int iter_cnt, int read_sz)
 {
        STAP_PROBE3(urandlib, read_with_sema, iter_num, iter_cnt, read_sz);
 }
+
+COMPAT_VERSION(urandlib_api_v1, urandlib_api, LIBURANDOM_READ_1.0.0)
+int urandlib_api_v1(void)
+{
+       return 1;
+}
+
+DEFAULT_VERSION(urandlib_api_v2, urandlib_api, LIBURANDOM_READ_2.0.0)
+int urandlib_api_v2(void)
+{
+       return 2;
+}
+
+COMPAT_VERSION(urandlib_api_sameoffset, urandlib_api_sameoffset, LIBURANDOM_READ_1.0.0)
+DEFAULT_VERSION(urandlib_api_sameoffset, urandlib_api_sameoffset, LIBURANDOM_READ_2.0.0)
+int urandlib_api_sameoffset(void)
+{
+       return 3;
+}