diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index 49cd304ae3bc..ef5e11ce6241 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -10620,31 +10620,94 @@ static int perf_event_uprobe_open_legacy(const char *probe_name, bool retprobe,
 }
 
 /* Return next ELF section of sh_type after scn, or first of that type if scn is NULL. */
-static Elf_Scn *elf_find_next_scn_by_type(Elf *elf, int sh_type, Elf_Scn *scn)
+static Elf_Scn *elf_find_next_scn_by_type(Elf *elf, int sh_type, Elf_Scn *scn, size_t *idx)
 {
 	while ((scn = elf_nextscn(elf, scn)) != NULL) {
 		GElf_Shdr sh;
 
 		if (!gelf_getshdr(scn, &sh))
 			continue;
-		if (sh.sh_type == sh_type)
+		if (sh.sh_type == sh_type) {
+			if (idx)
+				*idx = sh.sh_link;
 			return scn;
+		}
+	}
+	return NULL;
+}
+
+static Elf_Data *elf_find_data_by_type(Elf *elf, int sh_type, size_t *idx)
+{
+	Elf_Scn *scn = elf_find_next_scn_by_type(elf, sh_type, NULL, idx);
+
+	if (scn)
+		return elf_getdata(scn, NULL);
+
+	return NULL;
+}
+
+static Elf64_Verdef *elf_verdef_by_offset(Elf_Data *data, size_t offset)
+{
+	if (offset + sizeof(Elf64_Verdef) > data->d_size)
+		return NULL;
+
+	return (Elf64_Verdef *)((char *) data->d_buf + offset);
+}
+
+static Elf64_Versym *elf_versym_by_idx(Elf_Data *data, size_t idx)
+{
+	if (idx >= data->d_size / sizeof(Elf64_Versym))
+		return NULL;
+
+	return (Elf64_Versym *)(data->d_buf + idx * sizeof(Elf64_Versym));
+}
+
+static Elf64_Verdaux *elf_verdaux_by_offset(Elf_Data *data, size_t offset)
+{
+	if (offset + sizeof(Elf64_Verdaux) > data->d_size)
+		return NULL;
+
+	return (Elf64_Verdaux *)((char *) data->d_buf + offset);
+}
+
+#define ELF_VERSYM_HIDDEN 0x8000
+#define ELF_VERSYM_IDX_MASK 0x7fff
+
+static Elf64_Verdaux *elf_get_verdaux_by_versym(Elf_Data *verdef_data, Elf64_Versym *versym)
+{
+	size_t offset = 0;
+
+	while (offset + sizeof(Elf64_Verdef) <= verdef_data->d_size) {
+		Elf64_Verdef *verdef = elf_verdef_by_offset(verdef_data, offset);
+
+		if (!verdef)
+			break;
+
+		if (verdef->vd_ndx == (*versym & ELF_VERSYM_IDX_MASK))
+			return elf_verdaux_by_offset(verdef_data, offset + verdef->vd_aux);
+
+		if (verdef->vd_next == 0)
+			break;
+
+		offset += verdef->vd_next;
 	}
 	return NULL;
 }
 
 /* Find offset of function name in the provided ELF object. "binary_path" is
  * the path to the ELF binary represented by "elf", and only used for error
- * reporting matters. "name" matches symbol name or name@@LIB for library
- * functions.
+ * reporting matters. "name" matches symbol name, name@LIB or name@@LIB for
+ * library functions.
  */
 static long elf_find_func_offset(Elf *elf, const char *binary_path, const char *name)
 {
 	int i, sh_types[2] = { SHT_DYNSYM, SHT_SYMTAB };
 	bool is_shared_lib, is_name_qualified;
 	long ret = -ENOENT;
-	size_t name_len;
 	GElf_Ehdr ehdr;
+	Elf_Data *versym_data = NULL;
+	Elf_Data *verdef_data = NULL;
+	size_t verdef_stridx = 0;
 
 	if (!gelf_getehdr(elf, &ehdr)) {
 		pr_warn("elf: failed to get ehdr from %s: %s\n", binary_path, elf_errmsg(-1));
@@ -10654,9 +10717,12 @@ static long elf_find_func_offset(Elf *elf, const char *binary_path, const char *
 	/* for shared lib case, we do not need to calculate relative offset */
 	is_shared_lib = ehdr.e_type == ET_DYN;
 
-	name_len = strlen(name);
-	/* Does name specify "@@LIB"? */
-	is_name_qualified = strstr(name, "@@") != NULL;
+	/* Does name specify "@@LIB" or "@LIB"? */
+	is_name_qualified = strstr(name, "@") != NULL;
+
+	/* Extract version definition and version symbol table */
+	versym_data = elf_find_data_by_type(elf, SHT_GNU_versym, NULL);
+	verdef_data = elf_find_data_by_type(elf, SHT_GNU_verdef, &verdef_stridx);
 
 	/* Search SHT_DYNSYM, SHT_SYMTAB for symbol. This search order is used because if
 	 * a binary is stripped, it may only have SHT_DYNSYM, and a fully-statically
@@ -10671,10 +10737,10 @@ static long elf_find_func_offset(Elf *elf, const char *binary_path, const char *
 		const char *sname;
 		GElf_Shdr sh;
 
-		scn = elf_find_next_scn_by_type(elf, sh_types[i], NULL);
+		scn = elf_find_next_scn_by_type(elf, sh_types[i], NULL, NULL);
 		if (!scn) {
 			pr_debug("elf: failed to find symbol table ELF sections in '%s'\n",
-				 binary_path);
+				binary_path);
 			continue;
 		}
 		if (!gelf_getshdr(scn, &sh))
@@ -10705,16 +10771,60 @@ static long elf_find_func_offset(Elf *elf, const char *binary_path, const char *
 			if (!sname)
 				continue;
 
-			curr_bind = GELF_ST_BIND(sym.st_info);
+			if (is_name_qualified) {
+				Elf64_Versym *versym;
+				Elf64_Verdaux *verdaux;
+				int res;
+				char full_name[256];
 
-			/* User can specify func, func@@LIB or func@@LIB_VERSION. */
-			if (strncmp(sname, name, name_len) != 0)
-				continue;
-			/* ...but we don't want a search for "foo" to match 'foo2" also, so any
-			 * additional characters in sname should be of the form "@@LIB".
-			 */
-			if (!is_name_qualified && sname[name_len] != '\0' && sname[name_len] != '@')
-				continue;
+				/* check that name at least starts with sname before building
+				 * the full name
+				 */
+				if (strncmp(name, sname, strlen(sname)) != 0)
+					continue;
+
+				if (!versym_data || !verdef_data) {
+					pr_warn("elf: failed to find version definition or version symbol table in '%s'\n",
+						binary_path);
+					break;
+				}
+
+				versym = elf_versym_by_idx(versym_data, idx);
+				if (!versym) {
+					pr_warn("elf: failed to lookup versym for '%s' in '%s'\n",
+						sname, binary_path);
+					continue;
+				}
+
+				verdaux = elf_get_verdaux_by_versym(verdef_data, versym);
+				if (!verdaux) {
+					pr_warn("elf: failed to lookup verdaux for '%s' in '%s'\n",
+						sname, binary_path);
+					continue;
+				}
+
+				res = snprintf(full_name, sizeof(full_name),
+					       (*versym & ELF_VERSYM_HIDDEN) ? "%s@%s" :
+								    "%s@@%s",
+					       sname,
+					       elf_strptr(elf, verdef_stridx,
+							  verdaux->vda_name));
+
+				if (res < 0 || res >= sizeof(full_name)) {
+					pr_warn("elf: failed to build full name for '%s' in '%s'\n",
+						sname, binary_path);
+					continue;
+				}
+
+				if (strcmp(full_name, name) != 0)
+					continue;
+			} else {
+				/* If name is not qualified, we want to match the symbol name */
+				if (strcmp(sname, name) != 0)
+					continue;
+			}
+
+			curr_bind = GELF_ST_BIND(sym.st_info);
 
 			if (ret >= 0) {
 				/* handle multiple matches */
diff --git a/tools/testing/selftests/bpf/prog_tests/attach_probe.c b/tools/testing/selftests/bpf/prog_tests/attach_probe.c
index 7175af39134f..c3f33f7e9d12 100644
--- a/tools/testing/selftests/bpf/prog_tests/attach_probe.c
+++ b/tools/testing/selftests/bpf/prog_tests/attach_probe.c
@@ -192,6 +192,41 @@ static void test_uprobe_lib(struct test_attach_probe *skel)
 	ASSERT_EQ(skel->bss->uretprobe_byname2_res, 8, "check_uretprobe_byname2_res");
 }
 
+static void test_uprobe_lib_with_versions(struct test_attach_probe *skel)
+{
+	DECLARE_LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts);
+	char absolute_path[256];
+
+	/* test attach with a versioned name.
+	 * realpath has two implementations in libc, only the default version will be used.
+	 */
+	uprobe_opts.func_name = "realpath@@GLIBC_2.3";
+	uprobe_opts.retprobe = false;
+	skel->links.handle_uprobe_byversionedname_a =
+			bpf_program__attach_uprobe_opts(skel->progs.handle_uprobe_byversionedname_a,
+							0 /* this pid */,
+							"libc.so.6",
+							0, &uprobe_opts);
+	if (!ASSERT_OK_PTR(skel->links.handle_uprobe_byversionedname_a, "attach_handle_uprobe_byversionedname_a"))
+		return;
+
+	uprobe_opts.func_name = "realpath@GLIBC_2.2.5";
+	uprobe_opts.retprobe = false;
+	skel->links.handle_uprobe_byversionedname_b =
+			bpf_program__attach_uprobe_opts(skel->progs.handle_uprobe_byversionedname_b,
+							0 /* this pid */,
+							"libc.so.6",
+							0, &uprobe_opts);
+	if (!ASSERT_OK_PTR(skel->links.handle_uprobe_byversionedname_b, "attach_handle_uprobe_byversionedname_b"))
+		return;
+
+	/* trigger & validate probes */
+	realpath("/", absolute_path);
+
+	ASSERT_EQ(skel->bss->uprobe_byversionedname_a_res, 13, "check_uprobe_byversionedname_a_res");
+	ASSERT_NEQ(skel->bss->uprobe_byversionedname_b_res, 14, "check_uprobe_byversionedname_b_res");
+}
+
 static void test_uprobe_ref_ctr(struct test_attach_probe *skel)
 {
 	DECLARE_LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts);
@@ -316,6 +351,8 @@ void test_attach_probe(void)
 		test_kprobe_sleepable();
 	if (test__start_subtest("uprobe-lib"))
 		test_uprobe_lib(skel);
+	if (test__start_subtest("uprobe-lib-with-versions"))
+		test_uprobe_lib_with_versions(skel);
 	if (test__start_subtest("uprobe-sleepable"))
 		test_uprobe_sleepable(skel);
 	if (test__start_subtest("uprobe-ref_ctr"))
diff --git a/tools/testing/selftests/bpf/progs/test_attach_probe.c b/tools/testing/selftests/bpf/progs/test_attach_probe.c
index 68466a6ad18c..079b58901ff8 100644
--- a/tools/testing/selftests/bpf/progs/test_attach_probe.c
+++ b/tools/testing/selftests/bpf/progs/test_attach_probe.c
@@ -17,6 +17,8 @@ int uprobe_byname3_sleepable_res = 0;
 int uprobe_byname3_res = 0;
 int uretprobe_byname3_sleepable_res = 0;
 int uretprobe_byname3_res = 0;
+int uprobe_byversionedname_a_res = 0;
+int uprobe_byversionedname_b_res = 0;
 void *user_ptr = 0;
 
 SEC("ksyscall/nanosleep")
@@ -121,5 +123,18 @@ int handle_uretprobe_byname3(struct pt_regs *ctx)
 	return 0;
 }
 
+SEC("uprobe")
+int BPF_UPROBE(handle_uprobe_byversionedname_a, const char *a, char *b)
+{
+	uprobe_byversionedname_a_res = 13;
+	return 0;
+}
+
+SEC("uprobe")
+int BPF_UPROBE(handle_uprobe_byversionedname_b, const char *a, char *b)
+{
+	uprobe_byversionedname_b_res = 14;
+	return 0;
+}
 
 char _license[] SEC("license") = "GPL";
