15c742725SJiri Olsa // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) 25c742725SJiri Olsa 35c742725SJiri Olsa #include <libelf.h> 45c742725SJiri Olsa #include <gelf.h> 55c742725SJiri Olsa #include <fcntl.h> 65c742725SJiri Olsa #include <linux/kernel.h> 75c742725SJiri Olsa 85c742725SJiri Olsa #include "libbpf_internal.h" 95c742725SJiri Olsa #include "str_error.h" 105c742725SJiri Olsa 115c742725SJiri Olsa #define STRERR_BUFSIZE 128 125c742725SJiri Olsa 13*f90eb70dSJiri Olsa int elf_open(const char *binary_path, struct elf_fd *elf_fd) 14*f90eb70dSJiri Olsa { 15*f90eb70dSJiri Olsa char errmsg[STRERR_BUFSIZE]; 16*f90eb70dSJiri Olsa int fd, ret; 17*f90eb70dSJiri Olsa Elf *elf; 18*f90eb70dSJiri Olsa 19*f90eb70dSJiri Olsa if (elf_version(EV_CURRENT) == EV_NONE) { 20*f90eb70dSJiri Olsa pr_warn("elf: failed to init libelf for %s\n", binary_path); 21*f90eb70dSJiri Olsa return -LIBBPF_ERRNO__LIBELF; 22*f90eb70dSJiri Olsa } 23*f90eb70dSJiri Olsa fd = open(binary_path, O_RDONLY | O_CLOEXEC); 24*f90eb70dSJiri Olsa if (fd < 0) { 25*f90eb70dSJiri Olsa ret = -errno; 26*f90eb70dSJiri Olsa pr_warn("elf: failed to open %s: %s\n", binary_path, 27*f90eb70dSJiri Olsa libbpf_strerror_r(ret, errmsg, sizeof(errmsg))); 28*f90eb70dSJiri Olsa return ret; 29*f90eb70dSJiri Olsa } 30*f90eb70dSJiri Olsa elf = elf_begin(fd, ELF_C_READ_MMAP, NULL); 31*f90eb70dSJiri Olsa if (!elf) { 32*f90eb70dSJiri Olsa pr_warn("elf: could not read elf from %s: %s\n", binary_path, elf_errmsg(-1)); 33*f90eb70dSJiri Olsa close(fd); 34*f90eb70dSJiri Olsa return -LIBBPF_ERRNO__FORMAT; 35*f90eb70dSJiri Olsa } 36*f90eb70dSJiri Olsa elf_fd->fd = fd; 37*f90eb70dSJiri Olsa elf_fd->elf = elf; 38*f90eb70dSJiri Olsa return 0; 39*f90eb70dSJiri Olsa } 40*f90eb70dSJiri Olsa 41*f90eb70dSJiri Olsa void elf_close(struct elf_fd *elf_fd) 42*f90eb70dSJiri Olsa { 43*f90eb70dSJiri Olsa if (!elf_fd) 44*f90eb70dSJiri Olsa return; 45*f90eb70dSJiri Olsa elf_end(elf_fd->elf); 46*f90eb70dSJiri Olsa close(elf_fd->fd); 47*f90eb70dSJiri Olsa } 48*f90eb70dSJiri Olsa 495c742725SJiri Olsa /* Return next ELF section of sh_type after scn, or first of that type if scn is NULL. */ 505c742725SJiri Olsa static Elf_Scn *elf_find_next_scn_by_type(Elf *elf, int sh_type, Elf_Scn *scn) 515c742725SJiri Olsa { 525c742725SJiri Olsa while ((scn = elf_nextscn(elf, scn)) != NULL) { 535c742725SJiri Olsa GElf_Shdr sh; 545c742725SJiri Olsa 555c742725SJiri Olsa if (!gelf_getshdr(scn, &sh)) 565c742725SJiri Olsa continue; 575c742725SJiri Olsa if (sh.sh_type == sh_type) 585c742725SJiri Olsa return scn; 595c742725SJiri Olsa } 605c742725SJiri Olsa return NULL; 615c742725SJiri Olsa } 625c742725SJiri Olsa 635c742725SJiri Olsa /* Find offset of function name in the provided ELF object. "binary_path" is 645c742725SJiri Olsa * the path to the ELF binary represented by "elf", and only used for error 655c742725SJiri Olsa * reporting matters. "name" matches symbol name or name@@LIB for library 665c742725SJiri Olsa * functions. 675c742725SJiri Olsa */ 685c742725SJiri Olsa long elf_find_func_offset(Elf *elf, const char *binary_path, const char *name) 695c742725SJiri Olsa { 705c742725SJiri Olsa int i, sh_types[2] = { SHT_DYNSYM, SHT_SYMTAB }; 715c742725SJiri Olsa bool is_shared_lib, is_name_qualified; 725c742725SJiri Olsa long ret = -ENOENT; 735c742725SJiri Olsa size_t name_len; 745c742725SJiri Olsa GElf_Ehdr ehdr; 755c742725SJiri Olsa 765c742725SJiri Olsa if (!gelf_getehdr(elf, &ehdr)) { 775c742725SJiri Olsa pr_warn("elf: failed to get ehdr from %s: %s\n", binary_path, elf_errmsg(-1)); 785c742725SJiri Olsa ret = -LIBBPF_ERRNO__FORMAT; 795c742725SJiri Olsa goto out; 805c742725SJiri Olsa } 815c742725SJiri Olsa /* for shared lib case, we do not need to calculate relative offset */ 825c742725SJiri Olsa is_shared_lib = ehdr.e_type == ET_DYN; 835c742725SJiri Olsa 845c742725SJiri Olsa name_len = strlen(name); 855c742725SJiri Olsa /* Does name specify "@@LIB"? */ 865c742725SJiri Olsa is_name_qualified = strstr(name, "@@") != NULL; 875c742725SJiri Olsa 885c742725SJiri Olsa /* Search SHT_DYNSYM, SHT_SYMTAB for symbol. This search order is used because if 895c742725SJiri Olsa * a binary is stripped, it may only have SHT_DYNSYM, and a fully-statically 905c742725SJiri Olsa * linked binary may not have SHT_DYMSYM, so absence of a section should not be 915c742725SJiri Olsa * reported as a warning/error. 925c742725SJiri Olsa */ 935c742725SJiri Olsa for (i = 0; i < ARRAY_SIZE(sh_types); i++) { 945c742725SJiri Olsa size_t nr_syms, strtabidx, idx; 955c742725SJiri Olsa Elf_Data *symbols = NULL; 965c742725SJiri Olsa Elf_Scn *scn = NULL; 975c742725SJiri Olsa int last_bind = -1; 985c742725SJiri Olsa const char *sname; 995c742725SJiri Olsa GElf_Shdr sh; 1005c742725SJiri Olsa 1015c742725SJiri Olsa scn = elf_find_next_scn_by_type(elf, sh_types[i], NULL); 1025c742725SJiri Olsa if (!scn) { 1035c742725SJiri Olsa pr_debug("elf: failed to find symbol table ELF sections in '%s'\n", 1045c742725SJiri Olsa binary_path); 1055c742725SJiri Olsa continue; 1065c742725SJiri Olsa } 1075c742725SJiri Olsa if (!gelf_getshdr(scn, &sh)) 1085c742725SJiri Olsa continue; 1095c742725SJiri Olsa strtabidx = sh.sh_link; 1105c742725SJiri Olsa symbols = elf_getdata(scn, 0); 1115c742725SJiri Olsa if (!symbols) { 1125c742725SJiri Olsa pr_warn("elf: failed to get symbols for symtab section in '%s': %s\n", 1135c742725SJiri Olsa binary_path, elf_errmsg(-1)); 1145c742725SJiri Olsa ret = -LIBBPF_ERRNO__FORMAT; 1155c742725SJiri Olsa goto out; 1165c742725SJiri Olsa } 1175c742725SJiri Olsa nr_syms = symbols->d_size / sh.sh_entsize; 1185c742725SJiri Olsa 1195c742725SJiri Olsa for (idx = 0; idx < nr_syms; idx++) { 1205c742725SJiri Olsa int curr_bind; 1215c742725SJiri Olsa GElf_Sym sym; 1225c742725SJiri Olsa Elf_Scn *sym_scn; 1235c742725SJiri Olsa GElf_Shdr sym_sh; 1245c742725SJiri Olsa 1255c742725SJiri Olsa if (!gelf_getsym(symbols, idx, &sym)) 1265c742725SJiri Olsa continue; 1275c742725SJiri Olsa 1285c742725SJiri Olsa if (GELF_ST_TYPE(sym.st_info) != STT_FUNC) 1295c742725SJiri Olsa continue; 1305c742725SJiri Olsa 1315c742725SJiri Olsa sname = elf_strptr(elf, strtabidx, sym.st_name); 1325c742725SJiri Olsa if (!sname) 1335c742725SJiri Olsa continue; 1345c742725SJiri Olsa 1355c742725SJiri Olsa curr_bind = GELF_ST_BIND(sym.st_info); 1365c742725SJiri Olsa 1375c742725SJiri Olsa /* User can specify func, func@@LIB or func@@LIB_VERSION. */ 1385c742725SJiri Olsa if (strncmp(sname, name, name_len) != 0) 1395c742725SJiri Olsa continue; 1405c742725SJiri Olsa /* ...but we don't want a search for "foo" to match 'foo2" also, so any 1415c742725SJiri Olsa * additional characters in sname should be of the form "@@LIB". 1425c742725SJiri Olsa */ 1435c742725SJiri Olsa if (!is_name_qualified && sname[name_len] != '\0' && sname[name_len] != '@') 1445c742725SJiri Olsa continue; 1455c742725SJiri Olsa 1465c742725SJiri Olsa if (ret >= 0) { 1475c742725SJiri Olsa /* handle multiple matches */ 1485c742725SJiri Olsa if (last_bind != STB_WEAK && curr_bind != STB_WEAK) { 1495c742725SJiri Olsa /* Only accept one non-weak bind. */ 1505c742725SJiri Olsa pr_warn("elf: ambiguous match for '%s', '%s' in '%s'\n", 1515c742725SJiri Olsa sname, name, binary_path); 1525c742725SJiri Olsa ret = -LIBBPF_ERRNO__FORMAT; 1535c742725SJiri Olsa goto out; 1545c742725SJiri Olsa } else if (curr_bind == STB_WEAK) { 1555c742725SJiri Olsa /* already have a non-weak bind, and 1565c742725SJiri Olsa * this is a weak bind, so ignore. 1575c742725SJiri Olsa */ 1585c742725SJiri Olsa continue; 1595c742725SJiri Olsa } 1605c742725SJiri Olsa } 1615c742725SJiri Olsa 1625c742725SJiri Olsa /* Transform symbol's virtual address (absolute for 1635c742725SJiri Olsa * binaries and relative for shared libs) into file 1645c742725SJiri Olsa * offset, which is what kernel is expecting for 1655c742725SJiri Olsa * uprobe/uretprobe attachment. 1665c742725SJiri Olsa * See Documentation/trace/uprobetracer.rst for more 1675c742725SJiri Olsa * details. 1685c742725SJiri Olsa * This is done by looking up symbol's containing 1695c742725SJiri Olsa * section's header and using it's virtual address 1705c742725SJiri Olsa * (sh_addr) and corresponding file offset (sh_offset) 1715c742725SJiri Olsa * to transform sym.st_value (virtual address) into 1725c742725SJiri Olsa * desired final file offset. 1735c742725SJiri Olsa */ 1745c742725SJiri Olsa sym_scn = elf_getscn(elf, sym.st_shndx); 1755c742725SJiri Olsa if (!sym_scn) 1765c742725SJiri Olsa continue; 1775c742725SJiri Olsa if (!gelf_getshdr(sym_scn, &sym_sh)) 1785c742725SJiri Olsa continue; 1795c742725SJiri Olsa 1805c742725SJiri Olsa ret = sym.st_value - sym_sh.sh_addr + sym_sh.sh_offset; 1815c742725SJiri Olsa last_bind = curr_bind; 1825c742725SJiri Olsa } 1835c742725SJiri Olsa if (ret > 0) 1845c742725SJiri Olsa break; 1855c742725SJiri Olsa } 1865c742725SJiri Olsa 1875c742725SJiri Olsa if (ret > 0) { 1885c742725SJiri Olsa pr_debug("elf: symbol address match for '%s' in '%s': 0x%lx\n", name, binary_path, 1895c742725SJiri Olsa ret); 1905c742725SJiri Olsa } else { 1915c742725SJiri Olsa if (ret == 0) { 1925c742725SJiri Olsa pr_warn("elf: '%s' is 0 in symtab for '%s': %s\n", name, binary_path, 1935c742725SJiri Olsa is_shared_lib ? "should not be 0 in a shared library" : 1945c742725SJiri Olsa "try using shared library path instead"); 1955c742725SJiri Olsa ret = -ENOENT; 1965c742725SJiri Olsa } else { 1975c742725SJiri Olsa pr_warn("elf: failed to find symbol '%s' in '%s'\n", name, binary_path); 1985c742725SJiri Olsa } 1995c742725SJiri Olsa } 2005c742725SJiri Olsa out: 2015c742725SJiri Olsa return ret; 2025c742725SJiri Olsa } 2035c742725SJiri Olsa 2045c742725SJiri Olsa /* Find offset of function name in ELF object specified by path. "name" matches 2055c742725SJiri Olsa * symbol name or name@@LIB for library functions. 2065c742725SJiri Olsa */ 2075c742725SJiri Olsa long elf_find_func_offset_from_file(const char *binary_path, const char *name) 2085c742725SJiri Olsa { 209*f90eb70dSJiri Olsa struct elf_fd elf_fd; 2105c742725SJiri Olsa long ret = -ENOENT; 2115c742725SJiri Olsa 212*f90eb70dSJiri Olsa ret = elf_open(binary_path, &elf_fd); 213*f90eb70dSJiri Olsa if (ret) 214*f90eb70dSJiri Olsa return ret; 215*f90eb70dSJiri Olsa ret = elf_find_func_offset(elf_fd.elf, binary_path, name); 216*f90eb70dSJiri Olsa elf_close(&elf_fd); 2175c742725SJiri Olsa return ret; 2185c742725SJiri Olsa } 219