diff mbox series

[RFC,bpf-next,1/4] libbpf: support function name-based attach for uprobes

Message ID 1642004329-23514-2-git-send-email-alan.maguire@oracle.com (mailing list archive)
State RFC
Delegated to: BPF
Headers show
Series libbpf: userspace attach by name | expand

Checks

Context Check Description
netdev/tree_selection success Clearly marked for bpf-next
netdev/fixes_present success Fixes tag not required for -next series
netdev/subject_prefix success Link
netdev/cover_letter success Series has a cover letter
netdev/patch_count success Link
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 0 this patch: 0
netdev/cc_maintainers success CCed 10 of 10 maintainers
netdev/build_clang success Errors and warnings before: 0 this patch: 0
netdev/module_param success Was 0 now: 0
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 0 this patch: 0
netdev/checkpatch warning CHECK: Please use a blank line after function/struct/union/enum declarations WARNING: line length of 81 exceeds 80 columns WARNING: line length of 82 exceeds 80 columns WARNING: line length of 84 exceeds 80 columns WARNING: line length of 85 exceeds 80 columns WARNING: line length of 88 exceeds 80 columns
netdev/kdoc success Errors and warnings before: 34 this patch: 34
netdev/source_inline success Was 0 now: 0
bpf/vmtest-bpf-next fail VM_Test
bpf/vmtest-bpf-next-PR fail PR summary

Commit Message

Alan Maguire Jan. 12, 2022, 4:18 p.m. UTC
kprobe attach is name-based, using lookups of kallsyms to translate
a function name to an address.  Currently uprobe attach is done
via an offset value as described in [1].  Extend uprobe opts
for attach to include a function name which can then be converted
into a uprobe-friendly offset.  The calcualation is done in two
steps:

- first, determine the symbol address using libelf; this gives us
  the offset as reported by objdump; then
- subtract the base address associated with the object.

The resultant value is then added to the func_offset value passed
in to specify the uprobe attach address.  So specifying a func_offset
of 0 along with a function name "printf" will attach to printf entry.

[1] https://www.kernel.org/doc/html/latest/trace/uprobetracer.html

Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
---
 tools/lib/bpf/libbpf.c | 172 +++++++++++++++++++++++++++++++++++++++++++++++++
 tools/lib/bpf/libbpf.h |  10 ++-
 2 files changed, 181 insertions(+), 1 deletion(-)
diff mbox series

Patch

diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index cf862a1..bccc26a 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -10155,6 +10155,126 @@  static int perf_event_uprobe_open_legacy(const char *probe_name, bool retprobe,
 	return pfd;
 }
 
+/* uprobes deal in relative offsets; subtract the base address associated with
+ * the mapped binary.  See Documentation/trace/uprobetracer.rst for more
+ * details.
+ */
+static ssize_t get_rel_offset(pid_t pid, uintptr_t addr)
+{
+	size_t start, end, offset;
+	char msg[STRERR_BUFSIZE];
+	char maps[64];
+	char buf[256];
+	FILE *f;
+	int err;
+
+	/* pid 0 implies "this process" */
+	snprintf(maps, sizeof(maps), "/proc/%d/maps", pid ? pid : getpid());
+	f = fopen(maps, "r");
+	if (!f) {
+		err = -errno;
+		pr_warn("could not open %s: %s\n",
+			maps, libbpf_strerror_r(err, msg, sizeof(msg)));
+		return err;
+	}
+
+	while (fscanf(f, "%zx-%zx %s %zx %*[^\n]\n", &start, &end, buf, &offset) == 4) {
+		if (addr >= start && addr < end) {
+			fclose(f);
+			return (size_t)addr - start + offset;
+		}
+	}
+	fclose(f);
+	return -ENOENT;
+}
+
+/* find next ELF section of sh_type, returning fd and setting elfp and scnp to
+ * point at Elf and next Elf_Scn.
+ */
+static Elf_Scn *find_elfscn(Elf *elf, int sh_type, Elf_Scn *scn)
+{
+	Elf64_Shdr *sh;
+
+	while ((scn = elf_nextscn(elf, scn)) != NULL) {
+		sh = elf64_getshdr(scn);
+		if (sh && sh->sh_type == sh_type)
+			break;
+	}
+	return scn;
+}
+
+/* Find offset of function name in object specified by path.  "name" matches
+ * symbol name or name@@LIB for library functions.
+ */
+static ssize_t find_elf_func_offset(Elf *elf, const char *name)
+{
+	size_t si, strtabidx, nr_syms;
+	Elf_Data *symbols = NULL;
+	ssize_t ret = -ENOENT;
+	Elf_Scn *scn = NULL;
+	const char *sname;
+	Elf64_Shdr *sh;
+	int bind;
+
+	scn = find_elfscn(elf, SHT_SYMTAB, NULL);
+	if (!scn) {
+		pr_debug("elf: failed to find symbol table ELF section\n");
+		return -ENOENT;
+	}
+
+	sh = elf64_getshdr(scn);
+	strtabidx = sh->sh_link;
+	symbols = elf_getdata(scn, 0);
+	if (!symbols) {
+		pr_debug("elf: failed to get symtab section: %s\n", elf_errmsg(-1));
+		return -LIBBPF_ERRNO__FORMAT;
+	}
+
+	nr_syms = symbols->d_size / sizeof(Elf64_Sym);
+	for (si = 0; si < nr_syms; si++) {
+		Elf64_Sym *sym = (Elf64_Sym *)symbols->d_buf + si;
+		size_t matchlen;
+		int currbind;
+
+		if (ELF64_ST_TYPE(sym->st_info) != STT_FUNC)
+			continue;
+
+		sname = elf_strptr(elf, strtabidx, sym->st_name);
+		if (!sname) {
+			pr_debug("failed to get sym name string for var %s\n", name);
+			return -EIO;
+		}
+		currbind = ELF64_ST_BIND(sym->st_info);
+
+		/* If matching on func@@LIB, match on everything prior to
+		 * the '@@'; otherwise match on full string.
+		 */
+		matchlen = strstr(sname, "@@") ? strstr(sname, "@@") - sname :
+						 strlen(sname);
+
+		if (strlen(name) == matchlen &&
+		    strncmp(sname, name, matchlen) == 0) {
+			if (ret >= 0) {
+				/* handle multiple matches */
+				if (bind != STB_WEAK && currbind != STB_WEAK) {
+					/* Only accept one non-weak bind. */
+					pr_debug("got additional match for symbol %s: %s\n",
+						 sname, name);
+					return -LIBBPF_ERRNO__FORMAT;
+				} else if (currbind == STB_WEAK) {
+					/* already have a non-weak bind, and
+					 * this is a weak bind, so ignore.
+					 */
+					continue;
+				}
+			}
+			ret = sym->st_value;
+			bind = currbind;
+		}
+	}
+	return ret;
+}
+
 LIBBPF_API struct bpf_link *
 bpf_program__attach_uprobe_opts(const struct bpf_program *prog, pid_t pid,
 				const char *binary_path, size_t func_offset,
@@ -10166,6 +10286,7 @@  static int perf_event_uprobe_open_legacy(const char *probe_name, bool retprobe,
 	size_t ref_ctr_off;
 	int pfd, err;
 	bool retprobe, legacy;
+	const char *func_name;
 
 	if (!OPTS_VALID(opts, bpf_uprobe_opts))
 		return libbpf_err_ptr(-EINVAL);
@@ -10174,6 +10295,57 @@  static int perf_event_uprobe_open_legacy(const char *probe_name, bool retprobe,
 	ref_ctr_off = OPTS_GET(opts, ref_ctr_offset, 0);
 	pe_opts.bpf_cookie = OPTS_GET(opts, bpf_cookie, 0);
 
+	func_name = OPTS_GET(opts, func_name, NULL);
+	if (func_name) {
+		ssize_t sym_off, rel_off;
+		Elf *elf;
+		int fd;
+
+		if (pid == -1) {
+			/* system-wide probing is not supported; we need
+			 * a running process to determine offsets.
+			 */
+			pr_warn("name-based attach does not work for pid -1 (all processes)\n");
+			return libbpf_err_ptr(-EINVAL);
+		}
+		if (!binary_path) {
+			pr_warn("name-based attach requires binary_path\n");
+			return libbpf_err_ptr(-EINVAL);
+		}
+		if (elf_version(EV_CURRENT) == EV_NONE) {
+			pr_debug("failed to init libelf for %s\n", binary_path);
+			return libbpf_err_ptr(-LIBBPF_ERRNO__LIBELF);
+		}
+		fd = open(binary_path, O_RDONLY | O_CLOEXEC);
+		if (fd < 0) {
+			err = -errno;
+			pr_debug("failed to open %s: %s\n", binary_path,
+				 libbpf_strerror_r(err, errmsg, sizeof(errmsg)));
+			return libbpf_err_ptr(err);
+		}
+		elf = elf_begin(fd, ELF_C_READ_MMAP, NULL);
+		if (!elf) {
+			pr_debug("could not read elf from %s: %s\n",
+				 binary_path, elf_errmsg(-1));
+			close(fd);
+			return libbpf_err_ptr(-LIBBPF_ERRNO__FORMAT);
+		}
+		sym_off = find_elf_func_offset(elf, func_name);
+		close(fd);
+		elf_end(elf);
+		if (sym_off < 0) {
+			pr_debug("could not find sym offset for %s\n", func_name);
+			return libbpf_err_ptr(sym_off);
+		}
+		rel_off = get_rel_offset(pid, sym_off);
+		if (rel_off < 0) {
+			pr_debug("could not find relative offset for %s at 0x%lx\n",
+				 func_name, sym_off);
+			return libbpf_err_ptr(rel_off);
+		}
+		func_offset += (size_t)rel_off;
+	}
+
 	legacy = determine_uprobe_perf_type() < 0;
 	if (!legacy) {
 		pfd = perf_event_open_probe(true /* uprobe */, retprobe, binary_path,
diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
index 85dfef8..40cb5ae 100644
--- a/tools/lib/bpf/libbpf.h
+++ b/tools/lib/bpf/libbpf.h
@@ -431,9 +431,17 @@  struct bpf_uprobe_opts {
 	__u64 bpf_cookie;
 	/* uprobe is return probe, invoked at function return time */
 	bool retprobe;
+	/* name of function name or function@@LIBRARY.  Partial matches
+	 * work for library name, such as printf, printf@@GLIBC.
+	 * To specify function entry, func_offset argument should be 0 and
+	 * func_name should specify function to trace.  To trace an offset
+	 * within the function, specify func_name and use func_offset
+	 * argument to specify argument _within_ the function.
+	 */
+	const char *func_name;
 	size_t :0;
 };
-#define bpf_uprobe_opts__last_field retprobe
+#define bpf_uprobe_opts__last_field func_name
 
 /**
  * @brief **bpf_program__attach_uprobe()** attaches a BPF program