xref: /freebsd/contrib/elftoolchain/elfcopy/segments.c (revision e8d8bef961a50d4dc22501cde4fb9fb0be1b2532)
1 /*-
2  * Copyright (c) 2007-2010,2012 Kai Wang
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26 
27 #include <sys/queue.h>
28 #include <err.h>
29 #include <gelf.h>
30 #include <stdint.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 
35 #include "elfcopy.h"
36 
37 ELFTC_VCSID("$Id: segments.c 3615 2018-05-17 04:12:24Z kaiwang27 $");
38 
39 static void	insert_to_inseg_list(struct segment *seg, struct section *sec);
40 
41 /*
42  * elfcopy's segment handling is relatively simpler and less powerful than
43  * libbfd. Program headers are modified or copied from input to output objects,
44  * but never re-generated. As a result, if the input object has incorrect
45  * program headers, the output object's program headers will remain incorrect
46  * or become even worse.
47  */
48 
49 /*
50  * Check whether a section is "loadable". If so, add it to the
51  * corresponding segment list(s) and return 1.
52  */
53 int
54 add_to_inseg_list(struct elfcopy *ecp, struct section *s)
55 {
56 	struct segment	*seg;
57 	int		 loadable;
58 
59 	if (ecp->ophnum == 0)
60 		return (0);
61 
62 	/*
63 	 * Segment is a different view of an ELF object. One segment can
64 	 * contain one or more sections, and one section can be included
65 	 * in one or more segments, or not included in any segment at all.
66 	 * We call those sections which can be found in one or more segments
67 	 * "loadable" sections, and call the rest "unloadable" sections.
68 	 * We keep track of "loadable" sections in their containing
69 	 * segment(s)' v_sec queue. These information are later used to
70 	 * recalculate the extents of segments, when sections are removed,
71 	 * for example.
72 	 */
73 	loadable = 0;
74 	STAILQ_FOREACH(seg, &ecp->v_seg, seg_list) {
75 		if (s->off < seg->off || (s->vma < seg->vaddr && !s->pseudo))
76 			continue;
77 		if (s->off + s->sz > seg->off + seg->fsz &&
78 		    s->type != SHT_NOBITS)
79 			continue;
80 		if (s->vma + s->sz > seg->vaddr + seg->msz)
81 			continue;
82 		if (seg->type == PT_TLS && ((s->flags & SHF_TLS) == 0))
83 			continue;
84 
85 		insert_to_inseg_list(seg, s);
86 		if (seg->type == PT_LOAD)
87 			s->seg = seg;
88 		else if (seg->type == PT_TLS)
89 			s->seg_tls = seg;
90 		if (s->pseudo)
91 			s->vma = seg->vaddr + (s->off - seg->off);
92 		if (seg->paddr > 0)
93 			s->lma = seg->paddr + (s->off - seg->off);
94 		else
95 			s->lma = 0;
96 		loadable = 1;
97 	}
98 
99 	return (loadable);
100 }
101 
102 void
103 adjust_addr(struct elfcopy *ecp)
104 {
105 	struct section *s, *s0;
106 	struct segment *seg;
107 	struct sec_action *sac;
108 	uint64_t dl, vma, lma, start, end;
109 	int found, i;
110 
111 	/*
112 	 * Apply VMA and global LMA changes in the first iteration.
113 	 */
114 	TAILQ_FOREACH(s, &ecp->v_sec, sec_list) {
115 
116 		/* Only adjust loadable section's address. */
117 		if (!s->loadable)
118 			continue;
119 
120 		/* Apply global VMA adjustment. */
121 		if (ecp->change_addr != 0)
122 			s->vma += ecp->change_addr;
123 
124 		/* Apply global LMA adjustment. */
125 		if (ecp->change_addr != 0 && s->seg != NULL &&
126 		    s->seg->paddr > 0)
127 			s->lma += ecp->change_addr;
128 	}
129 
130 	/*
131 	 * Apply sections VMA change in the second iteration.
132 	 */
133 	TAILQ_FOREACH(s, &ecp->v_sec, sec_list) {
134 
135 		if (!s->loadable)
136 			continue;
137 
138 		/*
139 		 * Check if there is a VMA change request for this
140 		 * section.
141 		 */
142 		sac = lookup_sec_act(ecp, s->name, 0);
143 		if (sac == NULL)
144 			continue;
145 		vma = s->vma;
146 		if (sac->setvma)
147 			vma = sac->vma;
148 		if (sac->vma_adjust != 0)
149 			vma += sac->vma_adjust;
150 		if (vma == s->vma)
151 			continue;
152 
153 		/*
154 		 * No need to make segment adjustment if the section doesn't
155 		 * belong to any segment.
156 		 */
157 		if (s->seg == NULL) {
158 			s->vma = vma;
159 			continue;
160 		}
161 
162 		/*
163 		 * Check if the VMA change is viable.
164 		 *
165 		 * 1. Check if the new VMA is properly aligned accroding to
166 		 *    section alignment.
167 		 *
168 		 * 2. Compute the new extent of segment that contains this
169 		 *    section, make sure it doesn't overlap with other
170 		 *    segments.
171 		 */
172 #ifdef	DEBUG
173 		printf("VMA for section %s: %#jx\n", s->name, vma);
174 #endif
175 
176 		if (vma % s->align != 0)
177 			errx(EXIT_FAILURE, "The VMA %#jx for "
178 			    "section %s is not aligned to %ju",
179 			    (uintmax_t) vma, s->name, (uintmax_t) s->align);
180 
181 		if (vma < s->vma) {
182 			/* Move section to lower address. */
183 			if (vma < s->vma - s->seg->vaddr)
184 				errx(EXIT_FAILURE, "Not enough space to move "
185 				    "section %s VMA to %#jx", s->name,
186 				    (uintmax_t) vma);
187 			start = vma - (s->vma - s->seg->vaddr);
188 			if (s == s->seg->v_sec[s->seg->nsec - 1])
189 				end = start + s->seg->msz;
190 			else
191 				end = s->seg->vaddr + s->seg->msz;
192 		} else {
193 			/* Move section to upper address. */
194 			if (s == s->seg->v_sec[0])
195 				start = vma;
196 			else
197 				start = s->seg->vaddr;
198 			end = vma + (s->seg->vaddr + s->seg->msz - s->vma);
199 			if (end < start)
200 				errx(EXIT_FAILURE, "Not enough space to move "
201 				    "section %s VMA to %#jx", s->name,
202 				    (uintmax_t) vma);
203 		}
204 
205 #ifdef	DEBUG
206 		printf("new extent for segment containing %s: (%#jx,%#jx)\n",
207 		    s->name, start, end);
208 #endif
209 
210 		STAILQ_FOREACH(seg, &ecp->v_seg, seg_list) {
211 			if (seg == s->seg || seg->type != PT_LOAD)
212 				continue;
213 			if (start > seg->vaddr + seg->msz)
214 				continue;
215 			if (end < seg->vaddr)
216 				continue;
217 			errx(EXIT_FAILURE, "The extent of segment containing "
218 			    "section %s overlaps with segment(%#jx,%#jx)",
219 			    s->name, (uintmax_t) seg->vaddr,
220 			    (uintmax_t) (seg->vaddr + seg->msz));
221 		}
222 
223 		/*
224 		 * Update section VMA and file offset.
225 		 */
226 
227 		if (vma < s->vma) {
228 			/*
229 			 * To move a section to lower VMA, we decrease
230 			 * the VMA of the section and all the sections that
231 			 * are before it, and we increase the file offsets
232 			 * of all the sections that are after it.
233 			 */
234 			dl = s->vma - vma;
235 			for (i = 0; i < s->seg->nsec; i++) {
236 				s0 = s->seg->v_sec[i];
237 				s0->vma -= dl;
238 #ifdef	DEBUG
239 				printf("section %s VMA set to %#jx\n",
240 				    s0->name, (uintmax_t) s0->vma);
241 #endif
242 				if (s0 == s)
243 					break;
244 			}
245 			for (i = i + 1; i < s->seg->nsec; i++) {
246 				s0 = s->seg->v_sec[i];
247 				s0->off += dl;
248 #ifdef	DEBUG
249 				printf("section %s offset set to %#jx\n",
250 				    s0->name, (uintmax_t) s0->off);
251 #endif
252 			}
253 		} else {
254 			/*
255 			 * To move a section to upper VMA, we increase
256 			 * the VMA of the section and all the sections that
257 			 * are after it, and we increase the their file
258 			 * offsets too unless the section in question
259 			 * is the first in its containing segment.
260 			 */
261 			dl = vma - s->vma;
262 			for (i = 0; i < s->seg->nsec; i++)
263 				if (s->seg->v_sec[i] == s)
264 					break;
265 			if (i >= s->seg->nsec)
266 				errx(EXIT_FAILURE, "Internal: section `%s' not"
267 				    " found in its containing segement",
268 				    s->name);
269 			for (; i < s->seg->nsec; i++) {
270 				s0 = s->seg->v_sec[i];
271 				s0->vma += dl;
272 #ifdef	DEBUG
273 				printf("section %s VMA set to %#jx\n",
274 				    s0->name, (uintmax_t) s0->lma);
275 #endif
276 				if (s != s->seg->v_sec[0]) {
277 					s0->off += dl;
278 #ifdef	DEBUG
279 					printf("section %s offset set to %#jx\n",
280 					    s0->name, (uintmax_t) s0->off);
281 #endif
282 				}
283 			}
284 		}
285 	}
286 
287 	/*
288 	 * Apply load address padding.
289 	 */
290 
291 	if (ecp->pad_to != 0) {
292 
293 		/*
294 		 * Find the section with highest VMA.
295 		 */
296 		s = NULL;
297 		STAILQ_FOREACH(seg, &ecp->v_seg, seg_list) {
298 			if (seg->type != PT_LOAD)
299 				continue;
300 			for (i = seg->nsec - 1; i >= 0; i--)
301 				if (seg->v_sec[i]->type != SHT_NOBITS)
302 					break;
303 			if (i < 0)
304 				continue;
305 			if (s == NULL)
306 				s = seg->v_sec[i];
307 			else {
308 				s0 = seg->v_sec[i];
309 				if (s0->vma > s->vma)
310 					s = s0;
311 			}
312 		}
313 
314 		if (s == NULL)
315 			goto adjust_lma;
316 
317 		/* No need to pad if the pad_to address is lower. */
318 		if (ecp->pad_to <= s->vma + s->sz)
319 			goto adjust_lma;
320 
321 		s->pad_sz = ecp->pad_to - (s->vma + s->sz);
322 #ifdef	DEBUG
323 		printf("pad section %s VMA to address %#jx by %#jx\n", s->name,
324 		    (uintmax_t) ecp->pad_to, (uintmax_t) s->pad_sz);
325 #endif
326 	}
327 
328 
329 adjust_lma:
330 
331 	/*
332 	 * Apply sections LMA change in the third iteration.
333 	 */
334 	TAILQ_FOREACH(s, &ecp->v_sec, sec_list) {
335 
336 		/*
337 		 * Only loadable section that's inside a segment can have
338 		 * LMA adjusted. Also, if LMA of the containing segment is
339 		 * set to 0, it probably means we should ignore the LMA.
340 		 */
341 		if (!s->loadable || s->seg == NULL || s->seg->paddr == 0)
342 			continue;
343 
344 		/*
345 		 * Check if there is a LMA change request for this
346 		 * section.
347 		 */
348 		sac = lookup_sec_act(ecp, s->name, 0);
349 		if (sac == NULL)
350 			continue;
351 		if (!sac->setlma && sac->lma_adjust == 0)
352 			continue;
353 		lma = s->lma;
354 		if (sac->setlma)
355 			lma = sac->lma;
356 		if (sac->lma_adjust != 0)
357 			lma += sac->lma_adjust;
358 		if (lma == s->lma)
359 			continue;
360 
361 #ifdef	DEBUG
362 		printf("LMA for section %s: %#jx\n", s->name, lma);
363 #endif
364 
365 		/* Check alignment. */
366 		if (lma % s->align != 0)
367 			errx(EXIT_FAILURE, "The LMA %#jx for "
368 			    "section %s is not aligned to %ju",
369 			    (uintmax_t) lma, s->name, (uintmax_t) s->align);
370 
371 		/*
372 		 * Update section LMA.
373 		 */
374 
375 		if (lma < s->lma) {
376 			/*
377 			 * To move a section to lower LMA, we decrease
378 			 * the LMA of the section and all the sections that
379 			 * are before it.
380 			 */
381 			dl = s->lma - lma;
382 			for (i = 0; i < s->seg->nsec; i++) {
383 				s0 = s->seg->v_sec[i];
384 				s0->lma -= dl;
385 #ifdef	DEBUG
386 				printf("section %s LMA set to %#jx\n",
387 				    s0->name, (uintmax_t) s0->lma);
388 #endif
389 				if (s0 == s)
390 					break;
391 			}
392 		} else {
393 			/*
394 			 * To move a section to upper LMA, we increase
395 			 * the LMA of the section and all the sections that
396 			 * are after it.
397 			 */
398 			dl = lma - s->lma;
399 			for (i = 0; i < s->seg->nsec; i++)
400 				if (s->seg->v_sec[i] == s)
401 					break;
402 			if (i >= s->seg->nsec)
403 				errx(EXIT_FAILURE, "Internal: section `%s' not"
404 				    " found in its containing segement",
405 				    s->name);
406 			for (; i < s->seg->nsec; i++) {
407 				s0 = s->seg->v_sec[i];
408 				s0->lma += dl;
409 #ifdef	DEBUG
410 				printf("section %s LMA set to %#jx\n",
411 				    s0->name, (uintmax_t) s0->lma);
412 #endif
413 			}
414 		}
415 	}
416 
417 	/*
418 	 * Issue a warning if there are VMA/LMA adjust requests for
419 	 * some nonexistent sections.
420 	 */
421 	if ((ecp->flags & NO_CHANGE_WARN) == 0) {
422 		STAILQ_FOREACH(sac, &ecp->v_sac, sac_list) {
423 			if (!sac->setvma && !sac->setlma &&
424 			    !sac->vma_adjust && !sac->lma_adjust)
425 				continue;
426 			found = 0;
427 			TAILQ_FOREACH(s, &ecp->v_sec, sec_list) {
428 				if (s->pseudo || s->name == NULL)
429 					continue;
430 				if (!strcmp(s->name, sac->name)) {
431 					found = 1;
432 					break;
433 				}
434 			}
435 			if (!found)
436 				warnx("cannot find section `%s'", sac->name);
437 		}
438 	}
439 }
440 
441 static void
442 insert_to_inseg_list(struct segment *seg, struct section *sec)
443 {
444 	struct section *s;
445 	int i;
446 
447 	seg->nsec++;
448 	seg->v_sec = realloc(seg->v_sec, seg->nsec * sizeof(*seg->v_sec));
449 	if (seg->v_sec == NULL)
450 		err(EXIT_FAILURE, "realloc failed");
451 
452 	/*
453 	 * Sort the section in order of offset.
454 	 */
455 
456 	for (i = seg->nsec - 1; i > 0; i--) {
457 		s = seg->v_sec[i - 1];
458 		if (sec->off >= s->off) {
459 			seg->v_sec[i] = sec;
460 			break;
461 		} else
462 			seg->v_sec[i] = s;
463 	}
464 	if (i == 0)
465 		seg->v_sec[0] = sec;
466 }
467 
468 void
469 setup_phdr(struct elfcopy *ecp)
470 {
471 	struct segment	*seg;
472 	GElf_Phdr	 iphdr;
473 	size_t		 iphnum, i;
474 
475 	if (elf_getphnum(ecp->ein, &iphnum) == 0)
476 		errx(EXIT_FAILURE, "elf_getphnum failed: %s",
477 		    elf_errmsg(-1));
478 
479 	ecp->ophnum = ecp->iphnum = iphnum;
480 	if (iphnum == 0)
481 		return;
482 
483 	/* If --only-keep-debug is specified, discard all program headers. */
484 	if (ecp->strip == STRIP_NONDEBUG) {
485 		ecp->ophnum = 0;
486 		return;
487 	}
488 
489 	for (i = 0; i < iphnum; i++) {
490 		if (gelf_getphdr(ecp->ein, i, &iphdr) != &iphdr)
491 			errx(EXIT_FAILURE, "gelf_getphdr failed: %s",
492 			    elf_errmsg(-1));
493 		if ((seg = calloc(1, sizeof(*seg))) == NULL)
494 			err(EXIT_FAILURE, "calloc failed");
495 		seg->vaddr	= iphdr.p_vaddr;
496 		seg->paddr	= iphdr.p_paddr;
497 		seg->off	= iphdr.p_offset;
498 		seg->fsz	= iphdr.p_filesz;
499 		seg->msz	= iphdr.p_memsz;
500 		seg->type	= iphdr.p_type;
501 		STAILQ_INSERT_TAIL(&ecp->v_seg, seg, seg_list);
502 	}
503 }
504 
505 void
506 copy_phdr(struct elfcopy *ecp)
507 {
508 	struct segment	*seg;
509 	struct section	*s;
510 	GElf_Phdr	 iphdr, ophdr;
511 	int		 i;
512 
513 	STAILQ_FOREACH(seg, &ecp->v_seg, seg_list) {
514 		if (seg->type == PT_PHDR) {
515 			if (!TAILQ_EMPTY(&ecp->v_sec)) {
516 				s = TAILQ_FIRST(&ecp->v_sec);
517 				if (s->pseudo) {
518 					seg->vaddr = s->vma +
519 					    gelf_fsize(ecp->eout, ELF_T_EHDR,
520 						1, EV_CURRENT);
521 					seg->paddr = s->lma +
522 					    gelf_fsize(ecp->eout, ELF_T_EHDR,
523 						1, EV_CURRENT);
524 				}
525 			}
526 			seg->fsz = seg->msz = gelf_fsize(ecp->eout, ELF_T_PHDR,
527 			    ecp->ophnum, EV_CURRENT);
528 			continue;
529 		}
530 
531 		if (seg->nsec > 0) {
532 			s = seg->v_sec[0];
533 			seg->vaddr = s->vma;
534 			seg->paddr = s->lma;
535 		}
536 
537 		seg->fsz = seg->msz = 0;
538 		for (i = 0; i < seg->nsec; i++) {
539 			s = seg->v_sec[i];
540 			seg->msz = s->vma + s->sz - seg->vaddr;
541 			if (s->type != SHT_NOBITS)
542 				seg->fsz = s->off + s->sz - seg->off;
543 		}
544 	}
545 
546 	/*
547 	 * Allocate space for program headers, note that libelf keep
548 	 * track of the number in internal variable, and a call to
549 	 * elf_update is needed to update e_phnum of ehdr.
550 	 */
551 	if (gelf_newphdr(ecp->eout, ecp->ophnum) == NULL)
552 		errx(EXIT_FAILURE, "gelf_newphdr() failed: %s",
553 		    elf_errmsg(-1));
554 
555 	/*
556 	 * This elf_update() call is to update the e_phnum field in
557 	 * ehdr. It's necessary because later we will call gelf_getphdr(),
558 	 * which does sanity check by comparing ndx argument with e_phnum.
559 	 */
560 	if (elf_update(ecp->eout, ELF_C_NULL) < 0)
561 		errx(EXIT_FAILURE, "elf_update() failed: %s", elf_errmsg(-1));
562 
563 	/*
564 	 * iphnum == ophnum, since we don't remove program headers even if
565 	 * they no longer contain sections.
566 	 */
567 	i = 0;
568 	STAILQ_FOREACH(seg, &ecp->v_seg, seg_list) {
569 		if (i >= ecp->iphnum)
570 			break;
571 		if (gelf_getphdr(ecp->ein, i, &iphdr) != &iphdr)
572 			errx(EXIT_FAILURE, "gelf_getphdr failed: %s",
573 			    elf_errmsg(-1));
574 		if (gelf_getphdr(ecp->eout, i, &ophdr) != &ophdr)
575 			errx(EXIT_FAILURE, "gelf_getphdr failed: %s",
576 			    elf_errmsg(-1));
577 
578 		ophdr.p_type = iphdr.p_type;
579 		ophdr.p_vaddr = seg->vaddr;
580 		ophdr.p_paddr = seg->paddr;
581 		ophdr.p_flags = iphdr.p_flags;
582 		ophdr.p_align = iphdr.p_align;
583 		ophdr.p_offset = seg->off;
584 		ophdr.p_filesz = seg->fsz;
585 		ophdr.p_memsz = seg->msz;
586 		if (!gelf_update_phdr(ecp->eout, i, &ophdr))
587 			errx(EXIT_FAILURE, "gelf_update_phdr failed: %s",
588 			    elf_errmsg(-1));
589 
590 		i++;
591 	}
592 }
593