xref: /freebsd/usr.sbin/kldxref/ef.c (revision e0c4386e)
1 /*-
2  * SPDX-License-Identifier: BSD-4-Clause
3  *
4  * Copyright (c) 2000, Boris Popov
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. All advertising materials mentioning features or use of this software
16  *    must display the following acknowledgement:
17  *    This product includes software developed by Boris Popov.
18  * 4. Neither the name of the author nor the names of any co-contributors
19  *    may be used to endorse or promote products derived from this software
20  *    without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 
35 #include <sys/param.h>
36 
37 #include <err.h>
38 #include <errno.h>
39 #include <gelf.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43 
44 #include "ef.h"
45 
46 #define	MAXSEGS 16
47 struct ef_file {
48 	char		*ef_name;
49 	struct elf_file *ef_efile;
50 	GElf_Phdr	*ef_ph;
51 	void		*ef_fpage;		/* First block of the file */
52 	int		ef_fplen;		/* length of first block */
53 	GElf_Hashelt	ef_nbuckets;
54 	GElf_Hashelt	ef_nchains;
55 	GElf_Hashelt	*ef_buckets;
56 	GElf_Hashelt	*ef_chains;
57 	GElf_Hashelt	*ef_hashtab;
58 	caddr_t		ef_strtab;
59 	long		ef_strsz;
60 	GElf_Sym	*ef_symtab;
61 	int		ef_nsegs;
62 	GElf_Phdr	*ef_segs[MAXSEGS];
63 	int		ef_verbose;
64 	GElf_Rel	*ef_rel;		/* relocation table */
65 	long		ef_relsz;		/* number of entries */
66 	GElf_Rela	*ef_rela;		/* relocation table */
67 	long		ef_relasz;		/* number of entries */
68 };
69 
70 static void	ef_print_phdr(GElf_Phdr *);
71 static GElf_Off	ef_get_offset(elf_file_t, GElf_Addr);
72 
73 static void	ef_close(elf_file_t ef);
74 
75 static int	ef_seg_read_rel(elf_file_t ef, GElf_Addr address, size_t len,
76 		    void *dest);
77 static int	ef_seg_read_string(elf_file_t ef, GElf_Addr address, size_t len,
78 		    char *dest);
79 
80 static GElf_Addr ef_symaddr(elf_file_t ef, GElf_Size symidx);
81 static int	ef_lookup_set(elf_file_t ef, const char *name,
82 		    GElf_Addr *startp, GElf_Addr *stopp, long *countp);
83 static int	ef_lookup_symbol(elf_file_t ef, const char *name,
84 		    GElf_Sym **sym);
85 
86 static struct elf_file_ops ef_file_ops = {
87 	.close			= ef_close,
88 	.seg_read_rel		= ef_seg_read_rel,
89 	.seg_read_string	= ef_seg_read_string,
90 	.symaddr		= ef_symaddr,
91 	.lookup_set		= ef_lookup_set,
92 };
93 
94 static void
95 ef_print_phdr(GElf_Phdr *phdr)
96 {
97 
98 	if ((phdr->p_flags & PF_W) == 0) {
99 		printf("text=0x%jx ", (uintmax_t)phdr->p_filesz);
100 	} else {
101 		printf("data=0x%jx", (uintmax_t)phdr->p_filesz);
102 		if (phdr->p_filesz < phdr->p_memsz)
103 			printf("+0x%jx",
104 			    (uintmax_t)(phdr->p_memsz - phdr->p_filesz));
105 		printf(" ");
106 	}
107 }
108 
109 static GElf_Off
110 ef_get_offset(elf_file_t ef, GElf_Addr addr)
111 {
112 	GElf_Phdr *ph;
113 	int i;
114 
115 	for (i = 0; i < ef->ef_nsegs; i++) {
116 		ph = ef->ef_segs[i];
117 		if (addr >= ph->p_vaddr && addr < ph->p_vaddr + ph->p_memsz) {
118 			return (ph->p_offset + (addr - ph->p_vaddr));
119 		}
120 	}
121 	return (0);
122 }
123 
124 /*
125  * next two functions copied from link_elf.c
126  */
127 static int
128 ef_lookup_symbol(elf_file_t ef, const char *name, GElf_Sym **sym)
129 {
130 	unsigned long hash, symnum;
131 	GElf_Sym *symp;
132 	char *strp;
133 
134 	/* First, search hashed global symbols */
135 	hash = elf_hash(name);
136 	symnum = ef->ef_buckets[hash % ef->ef_nbuckets];
137 
138 	while (symnum != STN_UNDEF) {
139 		if (symnum >= ef->ef_nchains) {
140 			warnx("ef_lookup_symbol: file %s have corrupted symbol table\n",
141 			    ef->ef_name);
142 			return (ENOENT);
143 		}
144 
145 		symp = ef->ef_symtab + symnum;
146 		if (symp->st_name == 0) {
147 			warnx("ef_lookup_symbol: file %s have corrupted symbol table\n",
148 			    ef->ef_name);
149 			return (ENOENT);
150 		}
151 
152 		strp = ef->ef_strtab + symp->st_name;
153 
154 		if (strcmp(name, strp) == 0) {
155 			if (symp->st_shndx != SHN_UNDEF ||
156 			    (symp->st_value != 0 &&
157 				GELF_ST_TYPE(symp->st_info) == STT_FUNC)) {
158 				*sym = symp;
159 				return (0);
160 			} else
161 				return (ENOENT);
162 		}
163 
164 		symnum = ef->ef_chains[symnum];
165 	}
166 
167 	return (ENOENT);
168 }
169 
170 static int
171 ef_lookup_set(elf_file_t ef, const char *name, GElf_Addr *startp,
172     GElf_Addr *stopp, long *countp)
173 {
174 	GElf_Sym *sym;
175 	char *setsym;
176 	int error, len;
177 
178 	len = strlen(name) + sizeof("__start_set_"); /* sizeof includes \0 */
179 	setsym = malloc(len);
180 	if (setsym == NULL)
181 		return (errno);
182 
183 	/* get address of first entry */
184 	snprintf(setsym, len, "%s%s", "__start_set_", name);
185 	error = ef_lookup_symbol(ef, setsym, &sym);
186 	if (error != 0)
187 		goto out;
188 	*startp = sym->st_value;
189 
190 	/* get address of last entry */
191 	snprintf(setsym, len, "%s%s", "__stop_set_", name);
192 	error = ef_lookup_symbol(ef, setsym, &sym);
193 	if (error != 0)
194 		goto out;
195 	*stopp = sym->st_value;
196 
197 	/* and the number of entries */
198 	*countp = (*stopp - *startp) / elf_pointer_size(ef->ef_efile);
199 
200 out:
201 	free(setsym);
202 	return (error);
203 }
204 
205 static GElf_Addr
206 ef_symaddr(elf_file_t ef, GElf_Size symidx)
207 {
208 	const GElf_Sym *sym;
209 
210 	if (symidx >= ef->ef_nchains)
211 		return (0);
212 	sym = ef->ef_symtab + symidx;
213 
214 	if (GELF_ST_BIND(sym->st_info) == STB_LOCAL &&
215 	    sym->st_shndx != SHN_UNDEF && sym->st_value != 0)
216 		return (sym->st_value);
217 	return (0);
218 }
219 
220 static int
221 ef_parse_dynamic(elf_file_t ef, const GElf_Phdr *phdyn)
222 {
223 	GElf_Shdr *shdr;
224 	GElf_Dyn *dyn, *dp;
225 	size_t i, ndyn, nshdr, nsym;
226 	int error;
227 	GElf_Off hash_off, sym_off, str_off;
228 	GElf_Off rel_off;
229 	GElf_Off rela_off;
230 	int rel_sz;
231 	int rela_sz;
232 	int dynamic_idx;
233 
234 	/*
235 	 * The kernel linker parses the PT_DYNAMIC segment to find
236 	 * various important tables.  The gelf API of libelf is
237 	 * section-oriented and requires extracting data from sections
238 	 * instead of segments (program headers).  As a result,
239 	 * iterate over section headers to read various tables after
240 	 * parsing values from PT_DYNAMIC.
241 	 */
242 	error = elf_read_shdrs(ef->ef_efile, &nshdr, &shdr);
243 	if (error != 0)
244 		return (EFTYPE);
245 	dyn = NULL;
246 
247 	/* Find section for .dynamic. */
248 	dynamic_idx = -1;
249 	for (i = 0; i < nshdr; i++) {
250 		if (shdr[i].sh_type == SHT_DYNAMIC) {
251 			/*
252 			 * PowerPC kernels contain additional sections
253 			 * beyond .dynamic in PT_DYNAMIC due to a linker
254 			 * script bug.  Permit a section with a smaller
255 			 * size as a workaround.
256 			 */
257 			if (shdr[i].sh_offset != phdyn->p_offset ||
258 			    ((elf_machine(ef->ef_efile) == EM_PPC ||
259 			    elf_machine(ef->ef_efile) == EM_PPC64) ?
260 			    shdr[i].sh_size > phdyn->p_filesz :
261 			    shdr[i].sh_size != phdyn->p_filesz)) {
262 				warnx(".dynamic section doesn't match phdr");
263 				error = EFTYPE;
264 				goto out;
265 			}
266 			if (dynamic_idx != -1) {
267 				warnx("multiple SHT_DYNAMIC sections");
268 				error = EFTYPE;
269 				goto out;
270 			}
271 			dynamic_idx = i;
272 		}
273 	}
274 
275 	error = elf_read_dynamic(ef->ef_efile, dynamic_idx, &ndyn, &dyn);
276 	if (error != 0)
277 		goto out;
278 
279 	hash_off = rel_off = rela_off = sym_off = str_off = 0;
280 	rel_sz = rela_sz = 0;
281 	for (i = 0; i < ndyn; i++) {
282 		dp = &dyn[i];
283 		if (dp->d_tag == DT_NULL)
284 			break;
285 
286 		switch (dp->d_tag) {
287 		case DT_HASH:
288 			if (hash_off != 0)
289 				warnx("second DT_HASH entry ignored");
290 			else
291 				hash_off = ef_get_offset(ef, dp->d_un.d_ptr);
292 			break;
293 		case DT_STRTAB:
294 			if (str_off != 0)
295 				warnx("second DT_STRTAB entry ignored");
296 			else
297 				str_off = ef_get_offset(ef, dp->d_un.d_ptr);
298 			break;
299 		case DT_SYMTAB:
300 			if (sym_off != 0)
301 				warnx("second DT_SYMTAB entry ignored");
302 			else
303 				sym_off = ef_get_offset(ef, dp->d_un.d_ptr);
304 			break;
305 		case DT_SYMENT:
306 			if (dp->d_un.d_val != elf_object_size(ef->ef_efile,
307 			    ELF_T_SYM)) {
308 				error = EFTYPE;
309 				goto out;
310 			}
311 			break;
312 		case DT_REL:
313 			if (rel_off != 0)
314 				warnx("second DT_REL entry ignored");
315 			else
316 				rel_off = ef_get_offset(ef, dp->d_un.d_ptr);
317 			break;
318 		case DT_RELSZ:
319 			if (rel_sz != 0)
320 				warnx("second DT_RELSZ entry ignored");
321 			else
322 				rel_sz = dp->d_un.d_val;
323 			break;
324 		case DT_RELENT:
325 			if (dp->d_un.d_val != elf_object_size(ef->ef_efile,
326 			    ELF_T_REL)) {
327 				error = EFTYPE;
328 				goto out;
329 			}
330 			break;
331 		case DT_RELA:
332 			if (rela_off != 0)
333 				warnx("second DT_RELA entry ignored");
334 			else
335 				rela_off = ef_get_offset(ef, dp->d_un.d_ptr);
336 			break;
337 		case DT_RELASZ:
338 			if (rela_sz != 0)
339 				warnx("second DT_RELSZ entry ignored");
340 			else
341 				rela_sz = dp->d_un.d_val;
342 			break;
343 		case DT_RELAENT:
344 			if (dp->d_un.d_val != elf_object_size(ef->ef_efile,
345 			    ELF_T_RELA)) {
346 				error = EFTYPE;
347 				goto out;
348 			}
349 			break;
350 		}
351 	}
352 	if (hash_off == 0) {
353 		warnx("%s: no .hash section found\n", ef->ef_name);
354 		error = EFTYPE;
355 		goto out;
356 	}
357 	if (sym_off == 0) {
358 		warnx("%s: no .dynsym section found\n", ef->ef_name);
359 		error = EFTYPE;
360 		goto out;
361 	}
362 	if (str_off == 0) {
363 		warnx("%s: no .dynstr section found\n", ef->ef_name);
364 		error = EFTYPE;
365 		goto out;
366 	}
367 	if (rel_off == 0 && rela_off == 0) {
368 		warnx("%s: no ELF relocation table found\n", ef->ef_name);
369 		error = EFTYPE;
370 		goto out;
371 	}
372 
373 	nsym = 0;
374 	for (i = 0; i < nshdr; i++) {
375 		switch (shdr[i].sh_type) {
376 		case SHT_HASH:
377 			if (shdr[i].sh_offset != hash_off) {
378 				warnx("%s: ignoring SHT_HASH at different offset from DT_HASH",
379 				    ef->ef_name);
380 				break;
381 			}
382 
383 			/*
384 			 * libelf(3) mentions ELF_T_HASH, but it is
385 			 * not defined.
386 			 */
387 			if (shdr[i].sh_size < sizeof(*ef->ef_hashtab) * 2) {
388 				warnx("hash section too small");
389 				error = EFTYPE;
390 				goto out;
391 			}
392 			error = elf_read_data(ef->ef_efile, ELF_T_WORD,
393 			    shdr[i].sh_offset, shdr[i].sh_size,
394 			    (void **)&ef->ef_hashtab);
395 			if (error != 0) {
396 				warnc(error, "can't read hash table");
397 				goto out;
398 			}
399 			ef->ef_nbuckets = ef->ef_hashtab[0];
400 			ef->ef_nchains = ef->ef_hashtab[1];
401 			if ((2 + ef->ef_nbuckets + ef->ef_nchains) *
402 			    sizeof(*ef->ef_hashtab) != shdr[i].sh_size) {
403 				warnx("inconsistent hash section size");
404 				error = EFTYPE;
405 				goto out;
406 			}
407 
408 			ef->ef_buckets = ef->ef_hashtab + 2;
409 			ef->ef_chains = ef->ef_buckets + ef->ef_nbuckets;
410 			break;
411 		case SHT_DYNSYM:
412 			if (shdr[i].sh_offset != sym_off) {
413 				warnx("%s: ignoring SHT_DYNSYM at different offset from DT_SYMTAB",
414 				    ef->ef_name);
415 				break;
416 			}
417 			error = elf_read_symbols(ef->ef_efile, i, &nsym,
418 			    &ef->ef_symtab);
419 			if (error != 0) {
420 				if (ef->ef_verbose)
421 					warnx("%s: can't load .dynsym section (0x%jx)",
422 					    ef->ef_name, (uintmax_t)sym_off);
423 				goto out;
424 			}
425 			break;
426 		case SHT_STRTAB:
427 			if (shdr[i].sh_offset != str_off)
428 				break;
429 			error = elf_read_string_table(ef->ef_efile,
430 			    &shdr[i], &ef->ef_strsz, &ef->ef_strtab);
431 			if (error != 0) {
432 				warnx("can't load .dynstr section");
433 				error = EIO;
434 				goto out;
435 			}
436 			break;
437 		case SHT_REL:
438 			if (shdr[i].sh_offset != rel_off)
439 				break;
440 			if (shdr[i].sh_size != rel_sz) {
441 				warnx("%s: size mismatch for DT_REL section",
442 				    ef->ef_name);
443 				error = EFTYPE;
444 				goto out;
445 			}
446 			error = elf_read_rel(ef->ef_efile, i, &ef->ef_relsz,
447 			    &ef->ef_rel);
448 			if (error != 0) {
449 				warnx("%s: cannot load DT_REL section",
450 				    ef->ef_name);
451 				goto out;
452 			}
453 			break;
454 		case SHT_RELA:
455 			if (shdr[i].sh_offset != rela_off)
456 				break;
457 			if (shdr[i].sh_size != rela_sz) {
458 				warnx("%s: size mismatch for DT_RELA section",
459 				    ef->ef_name);
460 				error = EFTYPE;
461 				goto out;
462 			}
463 			error = elf_read_rela(ef->ef_efile, i, &ef->ef_relasz,
464 			    &ef->ef_rela);
465 			if (error != 0) {
466 				warnx("%s: cannot load DT_RELA section",
467 				    ef->ef_name);
468 				goto out;
469 			}
470 			break;
471 		}
472 	}
473 
474 	if (ef->ef_hashtab == NULL) {
475 		warnx("%s: did not find a symbol hash table", ef->ef_name);
476 		error = EFTYPE;
477 		goto out;
478 	}
479 	if (ef->ef_symtab == NULL) {
480 		warnx("%s: did not find a dynamic symbol table", ef->ef_name);
481 		error = EFTYPE;
482 		goto out;
483 	}
484 	if (nsym != ef->ef_nchains) {
485 		warnx("%s: symbol count mismatch", ef->ef_name);
486 		error = EFTYPE;
487 		goto out;
488 	}
489 	if (ef->ef_strtab == NULL) {
490 		warnx("%s: did not find a dynamic string table", ef->ef_name);
491 		error = EFTYPE;
492 		goto out;
493 	}
494 	if (rel_off != 0 && ef->ef_rel == NULL) {
495 		warnx("%s: did not find a DT_REL relocation table",
496 		    ef->ef_name);
497 		error = EFTYPE;
498 		goto out;
499 	}
500 	if (rela_off != 0 && ef->ef_rela == NULL) {
501 		warnx("%s: did not find a DT_RELA relocation table",
502 		    ef->ef_name);
503 		error = EFTYPE;
504 		goto out;
505 	}
506 
507 	error = 0;
508 out:
509 	free(dyn);
510 	free(shdr);
511 	return (error);
512 }
513 
514 static int
515 ef_seg_read_rel(elf_file_t ef, GElf_Addr address, size_t len, void *dest)
516 {
517 	GElf_Off ofs;
518 	const GElf_Rela *a;
519 	const GElf_Rel *r;
520 	int error;
521 
522 	ofs = ef_get_offset(ef, address);
523 	if (ofs == 0) {
524 		if (ef->ef_verbose)
525 			warnx("ef_seg_read_rel(%s): bad address (%jx)",
526 			    ef->ef_name, (uintmax_t)address);
527 		return (EFAULT);
528 	}
529 	error = elf_read_raw_data(ef->ef_efile, ofs, dest, len);
530 	if (error != 0)
531 		return (error);
532 
533 	for (r = ef->ef_rel; r < &ef->ef_rel[ef->ef_relsz]; r++) {
534 		error = elf_reloc(ef->ef_efile, r, ELF_T_REL, 0, address,
535 		    len, dest);
536 		if (error != 0)
537 			return (error);
538 	}
539 	for (a = ef->ef_rela; a < &ef->ef_rela[ef->ef_relasz]; a++) {
540 		error = elf_reloc(ef->ef_efile, a, ELF_T_RELA, 0, address,
541 		    len, dest);
542 		if (error != 0)
543 			return (error);
544 	}
545 	return (0);
546 }
547 
548 static int
549 ef_seg_read_string(elf_file_t ef, GElf_Addr address, size_t len, char *dest)
550 {
551 	GElf_Off ofs;
552 	int error;
553 
554 	ofs = ef_get_offset(ef, address);
555 	if (ofs == 0) {
556 		if (ef->ef_verbose)
557 			warnx("ef_seg_read_string(%s): bad offset (%jx:%ju)",
558 			    ef->ef_name, (uintmax_t)address, (uintmax_t)ofs);
559 		return (EFAULT);
560 	}
561 
562 	error = elf_read_raw_data(ef->ef_efile, ofs, dest, len);
563 	if (error != 0)
564 		return (error);
565 	if (strnlen(dest, len) == len)
566 		return (EFAULT);
567 
568 	return (0);
569 }
570 
571 int
572 ef_open(struct elf_file *efile, int verbose)
573 {
574 	elf_file_t ef;
575 	GElf_Ehdr *hdr;
576 	size_t i, nphdr, nsegs;
577 	int error;
578 	GElf_Phdr *phdr, *phdyn;
579 
580 	hdr = &efile->ef_hdr;
581 	if (hdr->e_phnum == 0 ||
582 	    hdr->e_phentsize != elf_object_size(efile, ELF_T_PHDR) ||
583 	    hdr->e_shnum == 0 || hdr->e_shoff == 0 ||
584 	    hdr->e_shentsize != elf_object_size(efile, ELF_T_SHDR))
585 		return (EFTYPE);
586 
587 	ef = malloc(sizeof(*ef));
588 	if (ef == NULL)
589 		return (errno);
590 
591 	efile->ef_ef = ef;
592 	efile->ef_ops = &ef_file_ops;
593 
594 	bzero(ef, sizeof(*ef));
595 	ef->ef_verbose = verbose;
596 	ef->ef_name = strdup(efile->ef_filename);
597 	ef->ef_efile = efile;
598 
599 	error = elf_read_phdrs(efile, &nphdr, &ef->ef_ph);
600 	if (error != 0) {
601 		phdr = NULL;
602 		goto out;
603 	}
604 
605 	error = EFTYPE;
606 	nsegs = 0;
607 	phdyn = NULL;
608 	phdr = ef->ef_ph;
609 	for (i = 0; i < nphdr; i++, phdr++) {
610 		if (verbose > 1)
611 			ef_print_phdr(phdr);
612 		switch (phdr->p_type) {
613 		case PT_LOAD:
614 			if (nsegs < MAXSEGS)
615 				ef->ef_segs[nsegs] = phdr;
616 			nsegs++;
617 			break;
618 		case PT_PHDR:
619 			break;
620 		case PT_DYNAMIC:
621 			phdyn = phdr;
622 			break;
623 		}
624 	}
625 	if (verbose > 1)
626 		printf("\n");
627 	if (phdyn == NULL) {
628 		warnx("Skipping %s: not dynamically-linked",
629 		    ef->ef_name);
630 		goto out;
631 	}
632 
633 	if (nsegs > MAXSEGS) {
634 		warnx("%s: too many segments", ef->ef_name);
635 		goto out;
636 	}
637 	ef->ef_nsegs = nsegs;
638 
639 	error = ef_parse_dynamic(ef, phdyn);
640 out:
641 	if (error != 0)
642 		ef_close(ef);
643 	return (error);
644 }
645 
646 static void
647 ef_close(elf_file_t ef)
648 {
649 	free(ef->ef_rela);
650 	free(ef->ef_rel);
651 	free(ef->ef_strtab);
652 	free(ef->ef_symtab);
653 	free(ef->ef_hashtab);
654 	free(ef->ef_ph);
655 	if (ef->ef_name)
656 		free(ef->ef_name);
657 	ef->ef_efile->ef_ops = NULL;
658 	ef->ef_efile->ef_ef = NULL;
659 	free(ef);
660 }
661