On Sun, Sep 10, 2023 at 6:51 PM Hengqi Chen <hengqi.chen@xxxxxxxxx> wrote: > > 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. > > Reviewed-by: Alan Maguire <alan.maguire@xxxxxxxxxx> > Acked-by: Jiri Olsa <jolsa@xxxxxxxxxx> > Signed-off-by: Hengqi Chen <hengqi.chen@xxxxxxxxx> > --- > tools/testing/selftests/bpf/Makefile | 5 +- > .../testing/selftests/bpf/liburandom_read.map | 15 +++ > .../testing/selftests/bpf/prog_tests/uprobe.c | 95 +++++++++++++++++++ > .../testing/selftests/bpf/progs/test_uprobe.c | 61 ++++++++++++ > tools/testing/selftests/bpf/urandom_read.c | 9 ++ > .../testing/selftests/bpf/urandom_read_lib1.c | 41 ++++++++ > 6 files changed, 224 insertions(+), 2 deletions(-) > create mode 100644 tools/testing/selftests/bpf/liburandom_read.map > create mode 100644 tools/testing/selftests/bpf/prog_tests/uprobe.c > create mode 100644 tools/testing/selftests/bpf/progs/test_uprobe.c > > diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile > index caede9b574cb..47365161b6fc 100644 > --- a/tools/testing/selftests/bpf/Makefile > +++ b/tools/testing/selftests/bpf/Makefile > @@ -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 > index 000000000000..38a97a419a04 > --- /dev/null > +++ b/tools/testing/selftests/bpf/liburandom_read.map > @@ -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 > index 000000000000..cf3e0e7a64fa > --- /dev/null > +++ b/tools/testing/selftests/bpf/prog_tests/uprobe.c > @@ -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 > index 000000000000..896c88a4960d > --- /dev/null > +++ b/tools/testing/selftests/bpf/progs/test_uprobe.c > @@ -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; > +} > diff --git a/tools/testing/selftests/bpf/urandom_read.c b/tools/testing/selftests/bpf/urandom_read.c > index e92644d0fa75..b28e910a8fbb 100644 > --- a/tools/testing/selftests/bpf/urandom_read.c > +++ b/tools/testing/selftests/bpf/urandom_read.c > @@ -21,6 +21,11 @@ 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); > +__asm__(".symver urandlib_api_old,urandlib_api@LIBURANDOM_READ_1.0.0"); any reason to not use COMPAT_VERSION() macro here? > +int urandlib_api_old(void); > +int urandlib_api_sameoffset(void); > + > unsigned short urand_read_with_sema_semaphore SEC(".probes"); > > static __attribute__((noinline)) > @@ -83,6 +88,10 @@ int main(int argc, char *argv[]) > > urandom_read(fd, count); > > + urandlib_api(); > + urandlib_api_old(); > + urandlib_api_sameoffset(); > + > close(fd); > return 0; > } > diff --git a/tools/testing/selftests/bpf/urandom_read_lib1.c b/tools/testing/selftests/bpf/urandom_read_lib1.c > index 86186e24b740..403b0735e223 100644 > --- a/tools/testing/selftests/bpf/urandom_read_lib1.c > +++ b/tools/testing/selftests/bpf/urandom_read_lib1.c > @@ -11,3 +11,44 @@ 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); > } > + > +/* Symbol versioning is different between static and shared library. > + * Properly versioned symbols are needed for shared library, but > + * only the symbol of the new version is needed for static library. > + * Starting with GNU C 10, use symver attribute instead of .symver assembler > + * directive, which works better with GCC LTO builds. > + */ > +#if defined(__GNUC__) && __GNUC__ >= 10 > + > +#define DEFAULT_VERSION(internal_name, api_name, version) \ > + __attribute__((symver(#api_name "@@" #version))) > +#define COMPAT_VERSION(internal_name, api_name, version) \ > + __attribute__((symver(#api_name "@" #version))) > + > +#else > + > +#define COMPAT_VERSION(internal_name, api_name, version) \ > + asm(".symver " #internal_name "," #api_name "@" #version); > +#define DEFAULT_VERSION(internal_name, api_name, version) \ > + asm(".symver " #internal_name "," #api_name "@@" #version); > + > +#endif maybe let's just use libbpf_internal.h instead of copy/pasting this barely maintainable piece of magic? > + > +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; > +} > -- > 2.34.1 >