xref: /openbmc/linux/tools/lib/bpf/elf.c (revision 5c742725)
1*5c742725SJiri Olsa // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
2*5c742725SJiri Olsa 
3*5c742725SJiri Olsa #include <libelf.h>
4*5c742725SJiri Olsa #include <gelf.h>
5*5c742725SJiri Olsa #include <fcntl.h>
6*5c742725SJiri Olsa #include <linux/kernel.h>
7*5c742725SJiri Olsa 
8*5c742725SJiri Olsa #include "libbpf_internal.h"
9*5c742725SJiri Olsa #include "str_error.h"
10*5c742725SJiri Olsa 
11*5c742725SJiri Olsa #define STRERR_BUFSIZE  128
12*5c742725SJiri Olsa 
13*5c742725SJiri Olsa /* Return next ELF section of sh_type after scn, or first of that type if scn is NULL. */
14*5c742725SJiri Olsa static Elf_Scn *elf_find_next_scn_by_type(Elf *elf, int sh_type, Elf_Scn *scn)
15*5c742725SJiri Olsa {
16*5c742725SJiri Olsa 	while ((scn = elf_nextscn(elf, scn)) != NULL) {
17*5c742725SJiri Olsa 		GElf_Shdr sh;
18*5c742725SJiri Olsa 
19*5c742725SJiri Olsa 		if (!gelf_getshdr(scn, &sh))
20*5c742725SJiri Olsa 			continue;
21*5c742725SJiri Olsa 		if (sh.sh_type == sh_type)
22*5c742725SJiri Olsa 			return scn;
23*5c742725SJiri Olsa 	}
24*5c742725SJiri Olsa 	return NULL;
25*5c742725SJiri Olsa }
26*5c742725SJiri Olsa 
27*5c742725SJiri Olsa /* Find offset of function name in the provided ELF object. "binary_path" is
28*5c742725SJiri Olsa  * the path to the ELF binary represented by "elf", and only used for error
29*5c742725SJiri Olsa  * reporting matters. "name" matches symbol name or name@@LIB for library
30*5c742725SJiri Olsa  * functions.
31*5c742725SJiri Olsa  */
32*5c742725SJiri Olsa long elf_find_func_offset(Elf *elf, const char *binary_path, const char *name)
33*5c742725SJiri Olsa {
34*5c742725SJiri Olsa 	int i, sh_types[2] = { SHT_DYNSYM, SHT_SYMTAB };
35*5c742725SJiri Olsa 	bool is_shared_lib, is_name_qualified;
36*5c742725SJiri Olsa 	long ret = -ENOENT;
37*5c742725SJiri Olsa 	size_t name_len;
38*5c742725SJiri Olsa 	GElf_Ehdr ehdr;
39*5c742725SJiri Olsa 
40*5c742725SJiri Olsa 	if (!gelf_getehdr(elf, &ehdr)) {
41*5c742725SJiri Olsa 		pr_warn("elf: failed to get ehdr from %s: %s\n", binary_path, elf_errmsg(-1));
42*5c742725SJiri Olsa 		ret = -LIBBPF_ERRNO__FORMAT;
43*5c742725SJiri Olsa 		goto out;
44*5c742725SJiri Olsa 	}
45*5c742725SJiri Olsa 	/* for shared lib case, we do not need to calculate relative offset */
46*5c742725SJiri Olsa 	is_shared_lib = ehdr.e_type == ET_DYN;
47*5c742725SJiri Olsa 
48*5c742725SJiri Olsa 	name_len = strlen(name);
49*5c742725SJiri Olsa 	/* Does name specify "@@LIB"? */
50*5c742725SJiri Olsa 	is_name_qualified = strstr(name, "@@") != NULL;
51*5c742725SJiri Olsa 
52*5c742725SJiri Olsa 	/* Search SHT_DYNSYM, SHT_SYMTAB for symbol. This search order is used because if
53*5c742725SJiri Olsa 	 * a binary is stripped, it may only have SHT_DYNSYM, and a fully-statically
54*5c742725SJiri Olsa 	 * linked binary may not have SHT_DYMSYM, so absence of a section should not be
55*5c742725SJiri Olsa 	 * reported as a warning/error.
56*5c742725SJiri Olsa 	 */
57*5c742725SJiri Olsa 	for (i = 0; i < ARRAY_SIZE(sh_types); i++) {
58*5c742725SJiri Olsa 		size_t nr_syms, strtabidx, idx;
59*5c742725SJiri Olsa 		Elf_Data *symbols = NULL;
60*5c742725SJiri Olsa 		Elf_Scn *scn = NULL;
61*5c742725SJiri Olsa 		int last_bind = -1;
62*5c742725SJiri Olsa 		const char *sname;
63*5c742725SJiri Olsa 		GElf_Shdr sh;
64*5c742725SJiri Olsa 
65*5c742725SJiri Olsa 		scn = elf_find_next_scn_by_type(elf, sh_types[i], NULL);
66*5c742725SJiri Olsa 		if (!scn) {
67*5c742725SJiri Olsa 			pr_debug("elf: failed to find symbol table ELF sections in '%s'\n",
68*5c742725SJiri Olsa 				 binary_path);
69*5c742725SJiri Olsa 			continue;
70*5c742725SJiri Olsa 		}
71*5c742725SJiri Olsa 		if (!gelf_getshdr(scn, &sh))
72*5c742725SJiri Olsa 			continue;
73*5c742725SJiri Olsa 		strtabidx = sh.sh_link;
74*5c742725SJiri Olsa 		symbols = elf_getdata(scn, 0);
75*5c742725SJiri Olsa 		if (!symbols) {
76*5c742725SJiri Olsa 			pr_warn("elf: failed to get symbols for symtab section in '%s': %s\n",
77*5c742725SJiri Olsa 				binary_path, elf_errmsg(-1));
78*5c742725SJiri Olsa 			ret = -LIBBPF_ERRNO__FORMAT;
79*5c742725SJiri Olsa 			goto out;
80*5c742725SJiri Olsa 		}
81*5c742725SJiri Olsa 		nr_syms = symbols->d_size / sh.sh_entsize;
82*5c742725SJiri Olsa 
83*5c742725SJiri Olsa 		for (idx = 0; idx < nr_syms; idx++) {
84*5c742725SJiri Olsa 			int curr_bind;
85*5c742725SJiri Olsa 			GElf_Sym sym;
86*5c742725SJiri Olsa 			Elf_Scn *sym_scn;
87*5c742725SJiri Olsa 			GElf_Shdr sym_sh;
88*5c742725SJiri Olsa 
89*5c742725SJiri Olsa 			if (!gelf_getsym(symbols, idx, &sym))
90*5c742725SJiri Olsa 				continue;
91*5c742725SJiri Olsa 
92*5c742725SJiri Olsa 			if (GELF_ST_TYPE(sym.st_info) != STT_FUNC)
93*5c742725SJiri Olsa 				continue;
94*5c742725SJiri Olsa 
95*5c742725SJiri Olsa 			sname = elf_strptr(elf, strtabidx, sym.st_name);
96*5c742725SJiri Olsa 			if (!sname)
97*5c742725SJiri Olsa 				continue;
98*5c742725SJiri Olsa 
99*5c742725SJiri Olsa 			curr_bind = GELF_ST_BIND(sym.st_info);
100*5c742725SJiri Olsa 
101*5c742725SJiri Olsa 			/* User can specify func, func@@LIB or func@@LIB_VERSION. */
102*5c742725SJiri Olsa 			if (strncmp(sname, name, name_len) != 0)
103*5c742725SJiri Olsa 				continue;
104*5c742725SJiri Olsa 			/* ...but we don't want a search for "foo" to match 'foo2" also, so any
105*5c742725SJiri Olsa 			 * additional characters in sname should be of the form "@@LIB".
106*5c742725SJiri Olsa 			 */
107*5c742725SJiri Olsa 			if (!is_name_qualified && sname[name_len] != '\0' && sname[name_len] != '@')
108*5c742725SJiri Olsa 				continue;
109*5c742725SJiri Olsa 
110*5c742725SJiri Olsa 			if (ret >= 0) {
111*5c742725SJiri Olsa 				/* handle multiple matches */
112*5c742725SJiri Olsa 				if (last_bind != STB_WEAK && curr_bind != STB_WEAK) {
113*5c742725SJiri Olsa 					/* Only accept one non-weak bind. */
114*5c742725SJiri Olsa 					pr_warn("elf: ambiguous match for '%s', '%s' in '%s'\n",
115*5c742725SJiri Olsa 						sname, name, binary_path);
116*5c742725SJiri Olsa 					ret = -LIBBPF_ERRNO__FORMAT;
117*5c742725SJiri Olsa 					goto out;
118*5c742725SJiri Olsa 				} else if (curr_bind == STB_WEAK) {
119*5c742725SJiri Olsa 					/* already have a non-weak bind, and
120*5c742725SJiri Olsa 					 * this is a weak bind, so ignore.
121*5c742725SJiri Olsa 					 */
122*5c742725SJiri Olsa 					continue;
123*5c742725SJiri Olsa 				}
124*5c742725SJiri Olsa 			}
125*5c742725SJiri Olsa 
126*5c742725SJiri Olsa 			/* Transform symbol's virtual address (absolute for
127*5c742725SJiri Olsa 			 * binaries and relative for shared libs) into file
128*5c742725SJiri Olsa 			 * offset, which is what kernel is expecting for
129*5c742725SJiri Olsa 			 * uprobe/uretprobe attachment.
130*5c742725SJiri Olsa 			 * See Documentation/trace/uprobetracer.rst for more
131*5c742725SJiri Olsa 			 * details.
132*5c742725SJiri Olsa 			 * This is done by looking up symbol's containing
133*5c742725SJiri Olsa 			 * section's header and using it's virtual address
134*5c742725SJiri Olsa 			 * (sh_addr) and corresponding file offset (sh_offset)
135*5c742725SJiri Olsa 			 * to transform sym.st_value (virtual address) into
136*5c742725SJiri Olsa 			 * desired final file offset.
137*5c742725SJiri Olsa 			 */
138*5c742725SJiri Olsa 			sym_scn = elf_getscn(elf, sym.st_shndx);
139*5c742725SJiri Olsa 			if (!sym_scn)
140*5c742725SJiri Olsa 				continue;
141*5c742725SJiri Olsa 			if (!gelf_getshdr(sym_scn, &sym_sh))
142*5c742725SJiri Olsa 				continue;
143*5c742725SJiri Olsa 
144*5c742725SJiri Olsa 			ret = sym.st_value - sym_sh.sh_addr + sym_sh.sh_offset;
145*5c742725SJiri Olsa 			last_bind = curr_bind;
146*5c742725SJiri Olsa 		}
147*5c742725SJiri Olsa 		if (ret > 0)
148*5c742725SJiri Olsa 			break;
149*5c742725SJiri Olsa 	}
150*5c742725SJiri Olsa 
151*5c742725SJiri Olsa 	if (ret > 0) {
152*5c742725SJiri Olsa 		pr_debug("elf: symbol address match for '%s' in '%s': 0x%lx\n", name, binary_path,
153*5c742725SJiri Olsa 			 ret);
154*5c742725SJiri Olsa 	} else {
155*5c742725SJiri Olsa 		if (ret == 0) {
156*5c742725SJiri Olsa 			pr_warn("elf: '%s' is 0 in symtab for '%s': %s\n", name, binary_path,
157*5c742725SJiri Olsa 				is_shared_lib ? "should not be 0 in a shared library" :
158*5c742725SJiri Olsa 						"try using shared library path instead");
159*5c742725SJiri Olsa 			ret = -ENOENT;
160*5c742725SJiri Olsa 		} else {
161*5c742725SJiri Olsa 			pr_warn("elf: failed to find symbol '%s' in '%s'\n", name, binary_path);
162*5c742725SJiri Olsa 		}
163*5c742725SJiri Olsa 	}
164*5c742725SJiri Olsa out:
165*5c742725SJiri Olsa 	return ret;
166*5c742725SJiri Olsa }
167*5c742725SJiri Olsa 
168*5c742725SJiri Olsa /* Find offset of function name in ELF object specified by path. "name" matches
169*5c742725SJiri Olsa  * symbol name or name@@LIB for library functions.
170*5c742725SJiri Olsa  */
171*5c742725SJiri Olsa long elf_find_func_offset_from_file(const char *binary_path, const char *name)
172*5c742725SJiri Olsa {
173*5c742725SJiri Olsa 	char errmsg[STRERR_BUFSIZE];
174*5c742725SJiri Olsa 	long ret = -ENOENT;
175*5c742725SJiri Olsa 	Elf *elf;
176*5c742725SJiri Olsa 	int fd;
177*5c742725SJiri Olsa 
178*5c742725SJiri Olsa 	fd = open(binary_path, O_RDONLY | O_CLOEXEC);
179*5c742725SJiri Olsa 	if (fd < 0) {
180*5c742725SJiri Olsa 		ret = -errno;
181*5c742725SJiri Olsa 		pr_warn("failed to open %s: %s\n", binary_path,
182*5c742725SJiri Olsa 			libbpf_strerror_r(ret, errmsg, sizeof(errmsg)));
183*5c742725SJiri Olsa 		return ret;
184*5c742725SJiri Olsa 	}
185*5c742725SJiri Olsa 	elf = elf_begin(fd, ELF_C_READ_MMAP, NULL);
186*5c742725SJiri Olsa 	if (!elf) {
187*5c742725SJiri Olsa 		pr_warn("elf: could not read elf from %s: %s\n", binary_path, elf_errmsg(-1));
188*5c742725SJiri Olsa 		close(fd);
189*5c742725SJiri Olsa 		return -LIBBPF_ERRNO__FORMAT;
190*5c742725SJiri Olsa 	}
191*5c742725SJiri Olsa 
192*5c742725SJiri Olsa 	ret = elf_find_func_offset(elf, binary_path, name);
193*5c742725SJiri Olsa 	elf_end(elf);
194*5c742725SJiri Olsa 	close(fd);
195*5c742725SJiri Olsa 	return ret;
196*5c742725SJiri Olsa }
197*5c742725SJiri Olsa 
198