Adding elf_find_multi_func_offset function that looks up offsets for symbols specified in syms array argument. Offsets are returned in allocated array with the 'cnt' size, that needs to be released by the caller. Signed-off-by: Jiri Olsa <jolsa@xxxxxxxxxx> --- tools/lib/bpf/libbpf.c | 161 +++++++++++++++++++++++++++++++++++++++ tools/lib/bpf/libbpf.h | 6 ++ tools/lib/bpf/libbpf.map | 1 + 3 files changed, 168 insertions(+) diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c index 92c92ed2101f..0b15609d4573 100644 --- a/tools/lib/bpf/libbpf.c +++ b/tools/lib/bpf/libbpf.c @@ -10713,6 +10713,7 @@ struct elf_func_offset { int last_bind; size_t name_len; bool is_name_qualified; + int idx; }; static int single_done(void *_data) @@ -10891,6 +10892,166 @@ static long elf_find_func_offset(Elf *elf, const char *binary_path, const char * return ret; } +struct match_multi_data { + int cnt; + int cnt_done; + struct elf_func_offset *func_offs; +}; + +static int cmp_func_offset(const void *_a, const void *_b) +{ + const struct elf_func_offset *a = _a; + const struct elf_func_offset *b = _b; + + return strcmp(a->name, b->name); +} + +static int multi_done(void *_data) +{ + struct match_multi_data *data = _data; + + return data->cnt == data->cnt_done; +} + +static int multi_match(Elf *elf, const char *binary_path, const char *sname, + GElf_Sym *sym, void *_data) +{ + struct match_multi_data *data = _data; + struct elf_func_offset *fo, func_offs = { + .name = sname, + }; + Elf_Scn *sym_scn; + GElf_Shdr sym_sh; + int curr_bind; + + fo = bsearch(&func_offs, data->func_offs, data->cnt, sizeof(*data->func_offs), + cmp_func_offset); + if (!fo) + return 0; + + curr_bind = GELF_ST_BIND(sym->st_info); + + if (fo->offset > 0) { + /* handle multiple matches */ + if (fo->last_bind != STB_WEAK && curr_bind != STB_WEAK) { + /* Only accept one non-weak bind. */ + pr_warn("elf: ambiguous match for '%s', '%s' in '%s'\n", + sname, fo->name, binary_path); + return -LIBBPF_ERRNO__FORMAT; + } else if (curr_bind == STB_WEAK) { + /* already have a non-weak bind, and + * this is a weak bind, so ignore. + */ + return 0; + } + } + + /* Transform symbol's virtual address (absolute for + * binaries and relative for shared libs) into file + * offset, which is what kernel is expecting for + * uprobe/uretprobe attachment. + * See Documentation/trace/uprobetracer.rst for more + * details. + * This is done by looking up symbol's containing + * section's header and using it's virtual address + * (sh_addr) and corresponding file offset (sh_offset) + * to transform sym.st_value (virtual address) into + * desired final file offset. + */ + sym_scn = elf_getscn(elf, sym->st_shndx); + if (!sym_scn) + return 0; + if (!gelf_getshdr(sym_scn, &sym_sh)) + return 0; + + if (!fo->offset) + data->cnt_done++; + + fo->offset = sym->st_value - sym_sh.sh_addr + sym_sh.sh_offset; + fo->last_bind = curr_bind; + return 0; +} + +static int +__elf_find_multi_func_offset(Elf *elf, const char *binary_path, int cnt, + const char **syms, unsigned long **poffsets) +{ + struct match_multi_data data = { + .cnt = cnt, + }; + struct elf_func_offset *func_offs; + unsigned long *offsets = NULL; + int err, i, idx; + + data.func_offs = func_offs = calloc(cnt, sizeof(*func_offs)); + if (!func_offs) + return -ENOMEM; + + for (i = 0; i < cnt; i++) { + func_offs[i].name = syms[i]; + func_offs[i].idx = i; + } + + qsort(func_offs, cnt, sizeof(*func_offs), cmp_func_offset); + + err = elf_for_each_symbol(elf, binary_path, multi_match, multi_done, &data); + if (err) + goto out; + + if (cnt != data.cnt_done) { + err = -ENOENT; + goto out; + } + + offsets = calloc(cnt, sizeof(*offsets)); + if (!offsets) { + err = -ENOMEM; + goto out; + } + + for (i = 0; i < cnt; i++) { + idx = func_offs[i].idx; + offsets[idx] = func_offs[i].offset; + } + +out: + *poffsets = offsets; + free(func_offs); + return err; +} + +LIBBPF_API int +elf_find_multi_func_offset(const char *binary_path, int cnt, + const char **syms, unsigned long **poffsets) +{ + char errmsg[STRERR_BUFSIZE]; + long ret = -ENOENT; + Elf *elf; + int fd; + + fd = open(binary_path, O_RDONLY | O_CLOEXEC); + if (fd < 0) { + ret = -errno; + pr_warn("failed to open %s: %s\n", binary_path, + libbpf_strerror_r(ret, errmsg, sizeof(errmsg))); + return ret; + } + if (elf_version(EV_CURRENT) == EV_NONE) { + pr_warn("failed to init libelf for %s\n", binary_path); + return -LIBBPF_ERRNO__LIBELF; + } + elf = elf_begin(fd, ELF_C_READ_MMAP, NULL); + if (!elf) { + pr_warn("elf: could not read elf from %s: %s\n", binary_path, elf_errmsg(-1)); + close(fd); + return -LIBBPF_ERRNO__FORMAT; + } + ret = __elf_find_multi_func_offset(elf, binary_path, cnt, syms, poffsets); + elf_end(elf); + close(fd); + return ret; +} + /* Find offset of function name in ELF object specified by path. "name" matches * symbol name or name@@LIB for library functions. */ diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h index 0b7362397ea3..b1b159263d47 100644 --- a/tools/lib/bpf/libbpf.h +++ b/tools/lib/bpf/libbpf.h @@ -1649,6 +1649,12 @@ LIBBPF_API int libbpf_register_prog_handler(const char *sec, */ LIBBPF_API int libbpf_unregister_prog_handler(int handler_id); +/** + * @brief *elf_find_multi_func_offset()* return offsets for given *syms* + */ +LIBBPF_API int elf_find_multi_func_offset(const char *binary_path, int cnt, + const char **syms, unsigned long **poffsets); + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/tools/lib/bpf/libbpf.map b/tools/lib/bpf/libbpf.map index a5aa3a383d69..7b1bf3f9ce4f 100644 --- a/tools/lib/bpf/libbpf.map +++ b/tools/lib/bpf/libbpf.map @@ -390,4 +390,5 @@ LIBBPF_1.2.0 { bpf_link_get_info_by_fd; bpf_map_get_info_by_fd; bpf_prog_get_info_by_fd; + elf_find_multi_func_offset; } LIBBPF_1.1.0; -- 2.40.0