diff options
Diffstat (limited to 'elfcopy/segments.c')
-rw-r--r-- | elfcopy/segments.c | 493 |
1 files changed, 493 insertions, 0 deletions
diff --git a/elfcopy/segments.c b/elfcopy/segments.c new file mode 100644 index 000000000000..c54cbfcbb07a --- /dev/null +++ b/elfcopy/segments.c @@ -0,0 +1,493 @@ +/*- + * Copyright (c) 2007-2010,2012 Kai Wang + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +#include <sys/queue.h> +#include <err.h> +#include <gelf.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "elfcopy.h" + +ELFTC_VCSID("$Id: segments.c 2542 2012-08-12 16:14:15Z kaiwang27 $"); + +static void insert_to_inseg_list(struct segment *seg, struct section *sec); + +/* + * elfcopy's segment handling is relatively simpler and less powerful than + * libbfd. Program headers are modified or copied from input to output objects, + * but never re-generated. As a result, if the input object has incorrect + * program headers, the output object's program headers will remain incorrect + * or become even worse. + */ + +/* + * Check whether a section is "loadable". If so, add it to the + * corresponding segment list(s) and return 1. + */ +int +add_to_inseg_list(struct elfcopy *ecp, struct section *s) +{ + struct segment *seg; + int loadable; + + if (ecp->ophnum == 0) + return (0); + + /* + * Segment is a different view of an ELF object. One segment can + * contain one or more sections, and one section can be included + * in one or more segments, or not included in any segment at all. + * We call those sections which can be found in one or more segments + * "loadable" sections, and call the rest "unloadable" sections. + * We keep track of "loadable" sections in their containing + * segment(s)' v_sec queue. These information are later used to + * recalculate the extents of segments, when sections are removed, + * for example. + */ + loadable = 0; + STAILQ_FOREACH(seg, &ecp->v_seg, seg_list) { + if (s->off < seg->off) + continue; + if (s->off + s->sz > seg->off + seg->fsz && + s->type != SHT_NOBITS) + continue; + if (s->off + s->sz > seg->off + seg->msz) + continue; + + insert_to_inseg_list(seg, s); + if (seg->type == PT_LOAD) + s->seg = seg; + s->lma = seg->addr + (s->off - seg->off); + loadable = 1; + } + + return (loadable); +} + +void +adjust_addr(struct elfcopy *ecp) +{ + struct section *s, *s0; + struct segment *seg; + struct sec_action *sac; + uint64_t dl, lma, old_vma, start, end; + int found, i; + + /* + * Apply VMA and global LMA changes in the first iteration. + */ + TAILQ_FOREACH(s, &ecp->v_sec, sec_list) { + + /* Only adjust loadable section's address. */ + if (!s->loadable || s->seg == NULL) + continue; + + /* Apply global LMA adjustment. */ + if (ecp->change_addr != 0) + s->lma += ecp->change_addr; + + if (!s->pseudo) { + old_vma = s->vma; + + /* Apply global VMA adjustment. */ + if (ecp->change_addr != 0) + s->vma += ecp->change_addr; + + /* Apply section VMA adjustment. */ + sac = lookup_sec_act(ecp, s->name, 0); + if (sac == NULL) + continue; + if (sac->setvma) + s->vma = sac->vma; + if (sac->vma_adjust != 0) + s->vma += sac->vma_adjust; + } + } + + /* + * Apply sections LMA change in the second iteration. + */ + TAILQ_FOREACH(s, &ecp->v_sec, sec_list) { + + /* Only adjust loadable section's LMA. */ + if (!s->loadable || s->seg == NULL) + continue; + + /* + * Check if there is a LMA change request for this + * section. + */ + sac = lookup_sec_act(ecp, s->name, 0); + if (sac == NULL) + continue; + if (!sac->setlma && sac->lma_adjust == 0) + continue; + lma = s->lma; + if (sac->setlma) + lma = sac->lma; + if (sac->lma_adjust != 0) + lma += sac->lma_adjust; + if (lma == s->lma) + continue; + + /* + * Check if the LMA change is viable. + * + * 1. Check if the new LMA is properly aligned accroding to + * section alignment. + * + * 2. Compute the new extent of segment that contains this + * section, make sure it doesn't overlap with other + * segments. + */ +#ifdef DEBUG + printf("LMA for section %s: %#jx\n", s->name, lma); +#endif + + if (lma % s->align != 0) + errx(EXIT_FAILURE, "The load address %#jx for " + "section %s is not aligned to %ju", + (uintmax_t) lma, s->name, s->align); + + if (lma < s->lma) { + /* Move section to lower address. */ + if (lma < s->lma - s->seg->addr) + errx(EXIT_FAILURE, "Not enough space to move " + "section %s load address to %#jx", s->name, + (uintmax_t) lma); + start = lma - (s->lma - s->seg->addr); + if (s == s->seg->v_sec[s->seg->nsec - 1]) + end = start + s->seg->msz; + else + end = s->seg->addr + s->seg->msz; + + } else { + /* Move section to upper address. */ + if (s == s->seg->v_sec[0]) + start = lma; + else + start = s->seg->addr; + end = lma + (s->seg->addr + s->seg->msz - s->lma); + if (end < start) + errx(EXIT_FAILURE, "Not enough space to move " + "section %s load address to %#jx", s->name, + (uintmax_t) lma); + } + +#ifdef DEBUG + printf("new extent for segment containing %s: (%#jx,%#jx)\n", + s->name, start, end); +#endif + + STAILQ_FOREACH(seg, &ecp->v_seg, seg_list) { + if (seg == s->seg || seg->type != PT_LOAD) + continue; + if (start > seg->addr + seg->msz) + continue; + if (end < seg->addr) + continue; + errx(EXIT_FAILURE, "The extent of segment containing " + "section %s overlaps with segment(%#jx,%#jx)", + s->name, seg->addr, seg->addr + seg->msz); + } + + /* + * Update section LMA and file offset. + */ + + if (lma < s->lma) { + /* + * To move a section to lower load address, we decrease + * the load addresses of the section and all the + * sections that are before it, and we increase the + * file offsets of all the sections that are after it. + */ + dl = s->lma - lma; + for (i = 0; i < s->seg->nsec; i++) { + s0 = s->seg->v_sec[i]; + s0->lma -= dl; +#ifdef DEBUG + printf("section %s LMA set to %#jx\n", + s0->name, (uintmax_t) s0->lma); +#endif + if (s0 == s) + break; + } + for (i = i + 1; i < s->seg->nsec; i++) { + s0 = s->seg->v_sec[i]; + s0->off += dl; +#ifdef DEBUG + printf("section %s offset set to %#jx\n", + s0->name, (uintmax_t) s0->off); +#endif + } + } else { + /* + * To move a section to upper load address, we increase + * the load addresses of the section and all the + * sections that are after it, and we increase the + * their file offsets too unless the section in question + * is the first in its containing segment. + */ + dl = lma - s->lma; + for (i = 0; i < s->seg->nsec; i++) + if (s->seg->v_sec[i] == s) + break; + if (i >= s->seg->nsec) + errx(EXIT_FAILURE, "Internal: section `%s' not" + " found in its containing segement", + s->name); + for (; i < s->seg->nsec; i++) { + s0 = s->seg->v_sec[i]; + s0->lma += dl; +#ifdef DEBUG + printf("section %s LMA set to %#jx\n", + s0->name, (uintmax_t) s0->lma); +#endif + if (s != s->seg->v_sec[0]) { + s0->off += dl; +#ifdef DEBUG + printf("section %s offset set to %#jx\n", + s0->name, (uintmax_t) s0->off); +#endif + } + } + } + } + + /* + * Apply load address padding. + */ + + if (ecp->pad_to != 0) { + + /* + * Find the section with highest load address. + */ + + s = NULL; + STAILQ_FOREACH(seg, &ecp->v_seg, seg_list) { + if (seg->type != PT_LOAD) + continue; + for (i = seg->nsec - 1; i >= 0; i--) + if (seg->v_sec[i]->type != SHT_NOBITS) + break; + if (i < 0) + continue; + if (s == NULL) + s = seg->v_sec[i]; + else { + s0 = seg->v_sec[i]; + if (s0->lma > s->lma) + s = s0; + } + } + + if (s == NULL) + goto issue_warn; + + /* No need to pad if the pad_to address is lower. */ + if (ecp->pad_to <= s->lma + s->sz) + goto issue_warn; + + s->pad_sz = ecp->pad_to - (s->lma + s->sz); +#ifdef DEBUG + printf("pad section %s load to address %#jx by %#jx\n", s->name, + (uintmax_t) ecp->pad_to, (uintmax_t) s->pad_sz); +#endif + } + +issue_warn: + + /* + * Issue a warning if there are VMA/LMA adjust requests for + * some nonexistent sections. + */ + if ((ecp->flags & NO_CHANGE_WARN) == 0) { + STAILQ_FOREACH(sac, &ecp->v_sac, sac_list) { + if (!sac->setvma && !sac->setlma && + !sac->vma_adjust && !sac->lma_adjust) + continue; + found = 0; + TAILQ_FOREACH(s, &ecp->v_sec, sec_list) { + if (s->pseudo || s->name == NULL) + continue; + if (!strcmp(s->name, sac->name)) { + found = 1; + break; + } + } + if (!found) + warnx("cannot find section `%s'", sac->name); + } + } +} + +static void +insert_to_inseg_list(struct segment *seg, struct section *sec) +{ + struct section *s; + int i; + + seg->nsec++; + seg->v_sec = realloc(seg->v_sec, seg->nsec * sizeof(*seg->v_sec)); + if (seg->v_sec == NULL) + err(EXIT_FAILURE, "realloc failed"); + + /* + * Sort the section in order of offset. + */ + + for (i = seg->nsec - 1; i > 0; i--) { + s = seg->v_sec[i - 1]; + if (sec->off >= s->off) { + seg->v_sec[i] = sec; + break; + } else + seg->v_sec[i] = s; + } + if (i == 0) + seg->v_sec[0] = sec; +} + +void +setup_phdr(struct elfcopy *ecp) +{ + struct segment *seg; + GElf_Phdr iphdr; + size_t iphnum; + int i; + + if (elf_getphnum(ecp->ein, &iphnum) == 0) + errx(EXIT_FAILURE, "elf_getphnum failed: %s", + elf_errmsg(-1)); + + ecp->ophnum = ecp->iphnum = iphnum; + if (iphnum == 0) + return; + + /* If --only-keep-debug is specified, discard all program headers. */ + if (ecp->strip == STRIP_NONDEBUG) { + ecp->ophnum = 0; + return; + } + + for (i = 0; (size_t)i < iphnum; i++) { + if (gelf_getphdr(ecp->ein, i, &iphdr) != &iphdr) + errx(EXIT_FAILURE, "gelf_getphdr failed: %s", + elf_errmsg(-1)); + if ((seg = calloc(1, sizeof(*seg))) == NULL) + err(EXIT_FAILURE, "calloc failed"); + seg->addr = iphdr.p_vaddr; + seg->off = iphdr.p_offset; + seg->fsz = iphdr.p_filesz; + seg->msz = iphdr.p_memsz; + seg->type = iphdr.p_type; + STAILQ_INSERT_TAIL(&ecp->v_seg, seg, seg_list); + } +} + +void +copy_phdr(struct elfcopy *ecp) +{ + struct segment *seg; + struct section *s; + GElf_Phdr iphdr, ophdr; + int i; + + STAILQ_FOREACH(seg, &ecp->v_seg, seg_list) { + if (seg->type == PT_PHDR) { + if (!TAILQ_EMPTY(&ecp->v_sec)) { + s = TAILQ_FIRST(&ecp->v_sec); + if (s->pseudo) + seg->addr = s->lma + + gelf_fsize(ecp->eout, ELF_T_EHDR, + 1, EV_CURRENT); + } + seg->fsz = seg->msz = gelf_fsize(ecp->eout, ELF_T_PHDR, + ecp->ophnum, EV_CURRENT); + continue; + } + + seg->fsz = seg->msz = 0; + for (i = 0; i < seg->nsec; i++) { + s = seg->v_sec[i]; + seg->msz = s->off + s->sz - seg->off; + if (s->type != SHT_NOBITS) + seg->fsz = seg->msz; + } + } + + /* + * Allocate space for program headers, note that libelf keep + * track of the number in internal variable, and a call to + * elf_update is needed to update e_phnum of ehdr. + */ + if (gelf_newphdr(ecp->eout, ecp->ophnum) == NULL) + errx(EXIT_FAILURE, "gelf_newphdr() failed: %s", + elf_errmsg(-1)); + + /* + * This elf_update() call is to update the e_phnum field in + * ehdr. It's necessary because later we will call gelf_getphdr(), + * which does sanity check by comparing ndx argument with e_phnum. + */ + if (elf_update(ecp->eout, ELF_C_NULL) < 0) + errx(EXIT_FAILURE, "elf_update() failed: %s", elf_errmsg(-1)); + + /* + * iphnum == ophnum, since we don't remove program headers even if + * they no longer contain sections. + */ + i = 0; + STAILQ_FOREACH(seg, &ecp->v_seg, seg_list) { + if (i >= ecp->iphnum) + break; + if (gelf_getphdr(ecp->ein, i, &iphdr) != &iphdr) + errx(EXIT_FAILURE, "gelf_getphdr failed: %s", + elf_errmsg(-1)); + if (gelf_getphdr(ecp->eout, i, &ophdr) != &ophdr) + errx(EXIT_FAILURE, "gelf_getphdr failed: %s", + elf_errmsg(-1)); + + ophdr.p_type = iphdr.p_type; + ophdr.p_vaddr = seg->addr; + ophdr.p_paddr = seg->addr; + ophdr.p_flags = iphdr.p_flags; + ophdr.p_align = iphdr.p_align; + ophdr.p_offset = seg->off; + ophdr.p_filesz = seg->fsz; + ophdr.p_memsz = seg->msz; + if (!gelf_update_phdr(ecp->eout, i, &ophdr)) + err(EXIT_FAILURE, "gelf_update_phdr failed :%s", + elf_errmsg(-1)); + + i++; + } +} |