summaryrefslogtreecommitdiff
path: root/ld/ld_output.c
diff options
context:
space:
mode:
Diffstat (limited to 'ld/ld_output.c')
-rw-r--r--ld/ld_output.c1154
1 files changed, 1154 insertions, 0 deletions
diff --git a/ld/ld_output.c b/ld/ld_output.c
new file mode 100644
index 0000000000000..43ea818da2c8f
--- /dev/null
+++ b/ld/ld_output.c
@@ -0,0 +1,1154 @@
+/*-
+ * Copyright (c) 2011-2013 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 "ld.h"
+#include "ld_arch.h"
+#include "ld_dynamic.h"
+#include "ld_ehframe.h"
+#include "ld_input.h"
+#include "ld_output.h"
+#include "ld_layout.h"
+#include "ld_reloc.h"
+#include "ld_script.h"
+#include "ld_strtab.h"
+#include "ld_symbols.h"
+
+ELFTC_VCSID("$Id: ld_output.c 2965 2013-09-10 02:46:29Z kaiwang27 $");
+
+static void _alloc_input_section_data(struct ld *ld, Elf_Scn *scn,
+ struct ld_input_section *is);
+static void _alloc_section_data_from_buffer(struct ld *ld, Elf_Scn *scn,
+ struct ld_output_data_buffer *odb);
+static void _alloc_section_data_for_symtab(struct ld *ld,
+ struct ld_output_section *os, Elf_Scn *scn,
+ struct ld_symbol_table *symtab);
+static void _alloc_section_data_for_strtab(struct ld *ld, Elf_Scn *scn,
+ struct ld_strtab *strtab);
+static void _add_to_shstrtab(struct ld *ld, const char *name);
+static void _copy_and_reloc_input_sections(struct ld *ld);
+static Elf_Scn *_create_elf_scn(struct ld *ld, struct ld_output *lo,
+ struct ld_output_section *os);
+static void _create_elf_section(struct ld *ld, struct ld_output_section *os);
+static void _create_phdr(struct ld *ld);
+static void _create_symbol_table(struct ld *ld);
+static uint64_t _find_entry_point(struct ld *ld);
+static void _produce_reloc_sections(struct ld *ld, struct ld_output *lo);
+static void _join_and_finalize_dynamic_reloc_sections(struct ld *ld,
+ struct ld_output *lo);
+static void _join_normal_reloc_sections(struct ld *ld, struct ld_output *lo);
+static void _update_section_header(struct ld *ld);
+
+void
+ld_output_early_init(struct ld *ld)
+{
+ struct ld_output *lo;
+
+ if (ld->ld_output == NULL) {
+ if ((lo = calloc(1, sizeof(*lo))) == NULL)
+ ld_fatal_std(ld, "calloc");
+
+ STAILQ_INIT(&lo->lo_oelist);
+ STAILQ_INIT(&lo->lo_oslist);
+ ld->ld_output = lo;
+ } else
+ lo = ld->ld_output;
+
+ assert(ld->ld_otgt != NULL);
+ lo->lo_ec = elftc_bfd_target_class(ld->ld_otgt);
+ lo->lo_endian = elftc_bfd_target_byteorder(ld->ld_otgt);
+}
+
+void
+ld_output_init(struct ld *ld)
+{
+ struct ld_output *lo;
+ const char *fn;
+ GElf_Ehdr eh;
+
+ lo = ld->ld_output;
+ assert(lo != NULL);
+
+ if (ld->ld_output_file == NULL)
+ fn = "a.out";
+ else
+ fn = ld->ld_output_file;
+
+ lo->lo_fd = open(fn, O_WRONLY | O_CREAT, S_IRWXU | S_IRWXG | S_IRWXO);
+ if (lo->lo_fd < 0)
+ ld_fatal_std(ld, "can not create output file: open %s", fn);
+
+ if ((lo->lo_elf = elf_begin(lo->lo_fd, ELF_C_WRITE, NULL)) == NULL)
+ ld_fatal(ld, "elf_begin failed: %s", elf_errmsg(-1));
+
+ elf_flagelf(lo->lo_elf, ELF_C_SET, ELF_F_LAYOUT);
+
+ if (gelf_newehdr(lo->lo_elf, lo->lo_ec) == NULL)
+ ld_fatal(ld, "gelf_newehdr failed: %s", elf_errmsg(-1));
+
+ if (gelf_getehdr(lo->lo_elf, &eh) == NULL)
+ ld_fatal(ld, "gelf_getehdr failed: %s", elf_errmsg(-1));
+
+ eh.e_ident[EI_CLASS] = lo->lo_ec;
+ eh.e_ident[EI_DATA] = lo->lo_endian;
+ eh.e_flags = 0; /* TODO */
+ eh.e_machine = elftc_bfd_target_machine(ld->ld_otgt);
+ if (ld->ld_dso || ld->ld_pie)
+ eh.e_type = ET_DYN;
+ else if (ld->ld_reloc)
+ eh.e_type = ET_REL;
+ else
+ eh.e_type = ET_EXEC;
+ eh.e_version = EV_CURRENT;
+
+ /* Save updated ELF header. */
+ if (gelf_update_ehdr(lo->lo_elf, &eh) == 0)
+ ld_fatal(ld, "gelf_update_ehdr failed: %s", elf_errmsg(-1));
+}
+
+void
+ld_output_format(struct ld *ld, char *def, char *be, char *le)
+{
+
+ ld->ld_otgt_name = def;
+ if ((ld->ld_otgt = elftc_bfd_find_target(def)) == NULL)
+ ld_fatal(ld, "invalid BFD format %s", def);
+
+ ld->ld_otgt_be_name = be;
+ if ((ld->ld_otgt_be = elftc_bfd_find_target(be)) == NULL)
+ ld_fatal(ld, "invalid BFD format %s", be);
+
+ ld->ld_otgt_le_name = le;
+ if ((ld->ld_otgt_le = elftc_bfd_find_target(le)) == NULL)
+ ld_fatal(ld, "invalid BFD format %s", le);
+
+ ld_arch_set_from_target(ld);
+}
+
+struct ld_output_element *
+ld_output_create_element(struct ld *ld, struct ld_output_element_head *head,
+ enum ld_output_element_type type, void *entry,
+ struct ld_output_element *after)
+{
+ struct ld_output_element *oe;
+
+ if ((oe = calloc(1, sizeof(*oe))) == NULL)
+ ld_fatal_std(ld, "calloc");
+
+ oe->oe_type = type;
+ oe->oe_entry = entry;
+
+ if (after == NULL)
+ STAILQ_INSERT_TAIL(head, oe, oe_next);
+ else
+ STAILQ_INSERT_AFTER(head, after, oe, oe_next);
+
+ return (oe);
+}
+
+struct ld_output_element *
+ld_output_create_section_element(struct ld *ld, struct ld_output_section *os,
+ enum ld_output_element_type type, void *entry,
+ struct ld_output_element *after)
+{
+ struct ld_output_element *oe;
+
+ oe = ld_output_create_element(ld, &os->os_e, type, entry, after);
+
+ switch (type) {
+ case OET_DATA:
+ case OET_DATA_BUFFER:
+ case OET_SYMTAB:
+ case OET_STRTAB:
+ os->os_empty = 0;
+ break;
+ default:
+ break;
+ }
+
+ return (oe);
+}
+
+struct ld_output_section *
+ld_output_alloc_section(struct ld *ld, const char *name,
+ struct ld_output_section *after, struct ld_output_section *ros)
+{
+ struct ld_output *lo;
+ struct ld_output_section *os;
+
+ lo = ld->ld_output;
+
+ if ((os = calloc(1, sizeof(*os))) == NULL)
+ ld_fatal_std(ld, "calloc");
+
+ if ((os->os_name = strdup(name)) == NULL)
+ ld_fatal_std(ld, "strdup");
+
+ os->os_align = 1;
+ os->os_empty = 1;
+
+ STAILQ_INIT(&os->os_e);
+
+ HASH_ADD_KEYPTR(hh, lo->lo_ostbl, os->os_name, strlen(os->os_name), os);
+
+ if (after == NULL) {
+ STAILQ_INSERT_TAIL(&lo->lo_oslist, os, os_next);
+ os->os_pe = ld_output_create_element(ld, &lo->lo_oelist,
+ OET_OUTPUT_SECTION, os, NULL);
+ } else {
+ STAILQ_INSERT_AFTER(&lo->lo_oslist, after, os, os_next);
+ os->os_pe = ld_output_create_element(ld, &lo->lo_oelist,
+ OET_OUTPUT_SECTION, os, after->os_pe);
+ }
+
+ if (ros != NULL)
+ ros->os_r = os;
+
+ return (os);
+}
+
+static Elf_Scn *
+_create_elf_scn(struct ld *ld, struct ld_output *lo,
+ struct ld_output_section *os)
+{
+ Elf_Scn *scn;
+
+ assert(lo->lo_elf != NULL);
+
+ if ((scn = elf_newscn(lo->lo_elf)) == NULL)
+ ld_fatal(ld, "elf_newscn failed: %s", elf_errmsg(-1));
+
+ if (os != NULL)
+ os->os_scn = scn;
+
+ return (scn);
+}
+
+static void
+_create_elf_section(struct ld *ld, struct ld_output_section *os)
+{
+ struct ld_output *lo;
+ struct ld_output_element *oe;
+ struct ld_input_section *is;
+ struct ld_input_section_head *islist;
+ Elf_Data *d;
+ Elf_Scn *scn;
+
+ lo = ld->ld_output;
+ assert(lo->lo_elf != NULL);
+
+ /* Create section data. */
+ scn = NULL;
+ STAILQ_FOREACH(oe, &os->os_e, oe_next) {
+ switch (oe->oe_type) {
+ case OET_DATA:
+ if (scn == NULL)
+ scn = _create_elf_scn(ld, lo, os);
+ /* TODO */
+ break;
+ case OET_DATA_BUFFER:
+ if (scn == NULL)
+ scn = _create_elf_scn(ld, lo, os);
+ _alloc_section_data_from_buffer(ld, scn, oe->oe_entry);
+ break;
+ case OET_INPUT_SECTION_LIST:
+ islist = oe->oe_islist;
+ STAILQ_FOREACH(is, islist, is_next) {
+ if (scn == NULL)
+ scn = _create_elf_scn(ld, lo, os);
+ if (os->os_type != SHT_NOBITS &&
+ !os->os_dynrel)
+ _alloc_input_section_data(ld, scn, is);
+ }
+ if ((ld->ld_reloc || ld->ld_emit_reloc) &&
+ os->os_r != NULL) {
+ /* Create Scn for relocation section. */
+ if (os->os_r->os_scn == NULL) {
+ os->os_r->os_scn = _create_elf_scn(ld,
+ lo, os->os_r);
+ _add_to_shstrtab(ld,
+ os->os_r->os_name);
+ }
+ }
+ break;
+ case OET_KEYWORD:
+ if (scn == NULL)
+ scn = _create_elf_scn(ld, lo, os);
+ /* TODO */
+ break;
+ case OET_SYMTAB:
+ /* TODO: Check symtab size. */
+ if (scn == NULL)
+ scn = _create_elf_scn(ld, lo, os);
+ break;
+ case OET_STRTAB:
+ /* TODO: Check strtab size. */
+ if (scn == NULL)
+ scn = _create_elf_scn(ld, lo, os);
+ _alloc_section_data_for_strtab(ld, scn, oe->oe_entry);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (scn == NULL)
+ return;
+
+ if (os->os_type == SHT_NOBITS) {
+ if ((d = elf_newdata(scn)) == NULL)
+ ld_fatal(ld, "elf_newdata failed: %s", elf_errmsg(-1));
+
+ d->d_align = os->os_align;
+ d->d_off = 0;
+ d->d_type = ELF_T_BYTE;
+ d->d_size = os->os_size;
+ d->d_version = EV_CURRENT;
+ d->d_buf = NULL;
+ }
+
+ _add_to_shstrtab(ld, os->os_name);
+}
+
+static void
+_alloc_input_section_data(struct ld *ld, Elf_Scn *scn,
+ struct ld_input_section *is)
+{
+ Elf_Data *d;
+
+ if (is->is_type == SHT_NOBITS || is->is_size == 0)
+ return;
+
+ if ((d = elf_newdata(scn)) == NULL)
+ ld_fatal(ld, "elf_newdata failed: %s", elf_errmsg(-1));
+
+ is->is_data = d;
+}
+
+static void
+_alloc_section_data_from_buffer(struct ld *ld, Elf_Scn *scn,
+ struct ld_output_data_buffer *odb)
+{
+ Elf_Data *d;
+
+ if ((d = elf_newdata(scn)) == NULL)
+ ld_fatal(ld, "elf_newdata failed: %s", elf_errmsg(-1));
+
+ d->d_align = odb->odb_align;
+ d->d_off = odb->odb_off;
+ d->d_type = odb->odb_type;
+ d->d_size = odb->odb_size;
+ d->d_version = EV_CURRENT;
+ d->d_buf = odb->odb_buf;
+}
+
+static void
+_alloc_section_data_from_reloc_buffer(struct ld *ld, Elf_Scn *scn,
+ void *buf, size_t sz)
+{
+ Elf_Data *d;
+
+ if ((d = elf_newdata(scn)) == NULL)
+ ld_fatal(ld, "elf_newdata failed: %s", elf_errmsg(-1));
+
+ d->d_align = ld->ld_arch->reloc_is_64bit ? 8 : 4;
+ d->d_off = 0; /* has to be the only data descriptor */
+ d->d_type = ld->ld_arch->reloc_is_rela ? ELF_T_RELA : ELF_T_REL;
+ d->d_size = sz;
+ d->d_version = EV_CURRENT;
+ d->d_buf = buf;
+}
+
+static void
+_alloc_section_data_for_symtab(struct ld *ld, struct ld_output_section *os,
+ Elf_Scn *scn, struct ld_symbol_table *symtab)
+{
+ Elf_Data *d;
+
+ if (symtab->sy_buf == NULL || symtab->sy_size == 0)
+ return;
+
+ if ((d = elf_newdata(scn)) == NULL)
+ ld_fatal(ld, "elf_newdata failed: %s", elf_errmsg(-1));
+
+ d->d_align = os->os_align;
+ d->d_off = 0;
+ d->d_type = ELF_T_SYM;
+ d->d_size = os->os_entsize * symtab->sy_size;
+ d->d_version = EV_CURRENT;
+ d->d_buf = symtab->sy_buf;
+}
+
+static void
+_alloc_section_data_for_strtab(struct ld *ld, Elf_Scn *scn,
+ struct ld_strtab *strtab)
+{
+ Elf_Data *d;
+ void *buf;
+ size_t sz;
+
+ buf = ld_strtab_getbuf(ld, strtab);
+ sz = ld_strtab_getsize(strtab);
+ if (buf == NULL || sz == 0)
+ return;
+
+ if ((d = elf_newdata(scn)) == NULL)
+ ld_fatal(ld, "elf_newdata failed: %s", elf_errmsg(-1));
+
+ d->d_align = 1;
+ d->d_off = 0;
+ d->d_type = ELF_T_BYTE;
+ d->d_size = sz;
+ d->d_version = EV_CURRENT;
+ d->d_buf = buf;
+}
+
+static void
+_copy_and_reloc_input_sections(struct ld *ld)
+{
+ struct ld_input *li;
+ struct ld_input_section *is;
+ Elf_Data *d;
+ int i;
+
+ STAILQ_FOREACH(li, &ld->ld_lilist, li_next) {
+ ld_input_load(ld, li);
+ for (i = 0; (uint64_t) i < li->li_shnum; i++) {
+ is = &li->li_is[i];
+
+ if (is->is_discard || !is->is_need_reloc)
+ continue;
+
+ d = is->is_data;
+
+ d->d_align = is->is_align;
+ d->d_off = is->is_reloff;
+ d->d_type = ELF_T_BYTE;
+ d->d_size = is->is_size;
+ d->d_version = EV_CURRENT;
+
+ /*
+ * Take different actions depending on different types
+ * of input sections:
+ *
+ * For internal input sections, assign the internal
+ * buffer directly to the data descriptor.
+ * For relocation sections, they should be ignored
+ * since they are handled elsewhere.
+ * For other input sections, load the raw data from
+ * input object and preform relocation.
+ */
+ if (is->is_ibuf != NULL) {
+ d->d_buf = is->is_ibuf;
+ /* .eh_frame section needs relocation */
+ if (strcmp(is->is_name, ".eh_frame") == 0)
+ ld_reloc_process_input_section(ld, is,
+ d->d_buf);
+ } else if (is->is_reloc == NULL) {
+ d->d_buf = ld_input_get_section_rawdata(ld,
+ is);
+ ld_reloc_process_input_section(ld, is,
+ d->d_buf);
+ }
+ }
+ ld_input_unload(ld, li);
+ }
+}
+
+static void
+_produce_reloc_sections(struct ld *ld, struct ld_output *lo)
+{
+ struct ld_output_section *os;
+ void *buf;
+ size_t sz;
+
+ STAILQ_FOREACH(os, &lo->lo_oslist, os_next) {
+ if (os->os_reloc != NULL) {
+ /* Serialize relocation records. */
+ buf = ld_reloc_serialize(ld, os, &sz);
+ _alloc_section_data_from_reloc_buffer(ld, os->os_scn,
+ buf, sz);
+
+ /*
+ * Link dynamic relocation sections to .dynsym
+ * section.
+ */
+ if (os->os_dynrel) {
+ if ((os->os_link = strdup(".dynsym")) == NULL)
+ ld_fatal_std(ld, "strdup");
+ }
+ }
+ }
+}
+
+static void
+_join_and_finalize_dynamic_reloc_sections(struct ld *ld, struct ld_output *lo)
+{
+ struct ld_output_section *os;
+ struct ld_output_element *oe;
+ struct ld_input_section *is;
+ struct ld_input_section_head *islist;
+
+ STAILQ_FOREACH(os, &lo->lo_oslist, os_next) {
+
+ if (!os->os_dynrel)
+ continue;
+
+ STAILQ_FOREACH(oe, &os->os_e, oe_next) {
+ switch (oe->oe_type) {
+ case OET_INPUT_SECTION_LIST:
+ islist = oe->oe_islist;
+ STAILQ_FOREACH(is, islist, is_next)
+ ld_reloc_join(ld, os, is);
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* Sort dynamic relocations for the runtime linker. */
+ if (os->os_reloc != NULL && os->os_dynrel && !os->os_pltrel)
+ ld_reloc_sort(ld, os);
+
+ /* Finalize relocations. */
+ ld_reloc_finalize_dynamic(ld, lo, os);
+ }
+}
+
+static void
+_join_normal_reloc_sections(struct ld *ld, struct ld_output *lo)
+{
+ struct ld_output_section *os;
+ struct ld_output_element *oe;
+ struct ld_input_section *is;
+ struct ld_input_section_head *islist;
+
+ STAILQ_FOREACH(os, &lo->lo_oslist, os_next) {
+
+ if (os->os_r == NULL)
+ continue;
+
+ STAILQ_FOREACH(oe, &os->os_e, oe_next) {
+ switch (oe->oe_type) {
+ case OET_INPUT_SECTION_LIST:
+ islist = oe->oe_islist;
+ STAILQ_FOREACH(is, islist, is_next) {
+ if (is->is_ris == NULL)
+ continue;
+ ld_reloc_join(ld, os->os_r,
+ is->is_ris);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
+
+void
+ld_output_create_elf_sections(struct ld *ld)
+{
+ struct ld_output *lo;
+ struct ld_output_element *oe;
+
+ lo = ld->ld_output;
+ assert(lo->lo_elf != NULL);
+
+ STAILQ_FOREACH(oe, &lo->lo_oelist, oe_next) {
+ switch (oe->oe_type) {
+ case OET_OUTPUT_SECTION:
+ _create_elf_section(ld, oe->oe_entry);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void
+ld_output_emit_gnu_stack_section(struct ld *ld)
+{
+ struct ld_state *ls;
+ struct ld_output *lo;
+ struct ld_output_section *os;
+
+ ls = &ld->ld_state;
+ lo = ld->ld_output;
+
+ os = ld_output_alloc_section(ld, ".note.GNU-stack", NULL, NULL);
+ os->os_empty = 0;
+ os->os_addr = 0;
+ os->os_type = SHT_PROGBITS;
+ os->os_align = 1;
+ os->os_entsize = 0;
+ os->os_off = ls->ls_offset;
+ os->os_size = 0;
+ if (ld->ld_stack_exec)
+ os->os_flags = SHF_EXECINSTR;
+
+ (void) _create_elf_scn(ld, lo, os);
+
+ _add_to_shstrtab(ld, ".note.GNU-stack");
+
+ /*
+ * .note.GNU-stack is an empty section so we don't allocate any
+ * Elf_Data descriptors.
+ */
+}
+
+static uint64_t
+_find_entry_point(struct ld *ld)
+{
+ struct ld_output *lo;
+ struct ld_output_section *os;
+ char entry_symbol[] = "start";
+ uint64_t entry;
+
+ lo = ld->ld_output;
+
+ if (ld->ld_entry != NULL) {
+ if (ld_symbols_get_value(ld, ld->ld_entry, &entry) < 0)
+ ld_fatal(ld, "symbol %s is undefined", ld->ld_entry);
+ return (entry);
+ }
+
+ if (ld->ld_scp->lds_entry_point != NULL) {
+ if (ld_symbols_get_value(ld, ld->ld_scp->lds_entry_point,
+ &entry) == 0)
+ return (entry);
+ }
+
+ if (ld_symbols_get_value(ld, entry_symbol, &entry) == 0)
+ return (entry);
+
+ STAILQ_FOREACH(os, &lo->lo_oslist, os_next) {
+ if (os->os_empty)
+ continue;
+ if (strcmp(os->os_name, ".text") == 0)
+ return (os->os_addr);
+ }
+
+ return (0);
+}
+
+static void
+_create_phdr(struct ld *ld)
+{
+ struct ld_output *lo;
+ struct ld_output_section *os;
+ Elf32_Phdr *p32;
+ Elf64_Phdr *p64;
+ void *phdrs;
+ uint64_t addr, off, align, flags, filesz, memsz, phdr_addr;
+ uint64_t tls_addr, tls_off, tls_align, tls_flags;
+ uint64_t tls_filesz, tls_memsz;
+ unsigned w;
+ int i, new, first, tls;
+
+ /* TODO: support segments created by linker script command PHDR. */
+
+#define _WRITE_PHDR(T,O,A,FSZ,MSZ,FL,AL) \
+ do { \
+ if (lo->lo_ec == ELFCLASS32) { \
+ p32[i].p_type = (T); \
+ p32[i].p_offset = (O); \
+ p32[i].p_vaddr = (A); \
+ p32[i].p_paddr = (A); \
+ p32[i].p_filesz = (FSZ); \
+ p32[i].p_memsz = (MSZ); \
+ p32[i].p_flags = (FL); \
+ p32[i].p_align = (AL); \
+ } else { \
+ p64[i].p_type = (T); \
+ p64[i].p_offset = (O); \
+ p64[i].p_vaddr = (A); \
+ p64[i].p_paddr = (A); \
+ p64[i].p_filesz = (FSZ); \
+ p64[i].p_memsz = (MSZ); \
+ p64[i].p_flags = (FL); \
+ p64[i].p_align = (AL); \
+ } \
+ } while(0)
+
+ lo = ld->ld_output;
+ assert(lo->lo_elf != NULL);
+ assert(lo->lo_phdr_num != 0);
+ assert(ld->ld_arch != NULL);
+
+ if ((phdrs = gelf_newphdr(lo->lo_elf, lo->lo_phdr_num)) == NULL)
+ ld_fatal(ld, "gelf_newphdr failed: %s", elf_errmsg(-1));
+
+ p32 = NULL;
+ p64 = NULL;
+ if (lo->lo_ec == ELFCLASS32)
+ p32 = phdrs;
+ else
+ p64 = phdrs;
+
+ i = -1;
+
+ /* Calculate the start vma of output object. */
+ os = STAILQ_FIRST(&lo->lo_oslist);
+ addr = os->os_addr - os->os_off;
+
+ /* Create PT_PHDR segment for dynamically linked output object */
+ if (lo->lo_dso_needed > 0 && !ld->ld_dso) {
+ i++;
+ off = gelf_fsize(lo->lo_elf, ELF_T_EHDR, 1, EV_CURRENT);
+ phdr_addr = addr + off;
+ filesz = memsz = gelf_fsize(lo->lo_elf, ELF_T_PHDR,
+ lo->lo_phdr_num, EV_CURRENT);
+ align = lo->lo_ec == ELFCLASS32 ? 4 : 8;
+ flags = PF_R | PF_X;
+ _WRITE_PHDR(PT_PHDR, off, phdr_addr, filesz, memsz, flags,
+ align);
+ }
+
+ /* Create PT_INTERP segment for dynamically linked output object */
+ if (lo->lo_interp != NULL) {
+ i++;
+ os = lo->lo_interp;
+ _WRITE_PHDR(PT_INTERP, os->os_off, os->os_addr, os->os_size,
+ os->os_size, PF_R, 1);
+ }
+
+ /*
+ * Create PT_LOAD segments.
+ */
+
+ align = ld->ld_arch->get_max_page_size(ld);
+ new = 1;
+ w = 0;
+ off = filesz = memsz = 0;
+ flags = PF_R;
+ first = 1;
+
+ tls = 0;
+ tls_off = tls_addr = tls_filesz = tls_memsz = tls_align = 0;
+ tls_flags = PF_R; /* TLS segment is a read-only image */
+
+ STAILQ_FOREACH(os, &lo->lo_oslist, os_next) {
+ if (os->os_empty)
+ continue;
+
+ if ((os->os_flags & SHF_ALLOC) == 0) {
+ new = 1;
+ continue;
+ }
+
+ if ((os->os_flags & SHF_TLS) != 0) {
+ if (tls < 0)
+ ld_warn(ld, "can not have multiple TLS "
+ "segments");
+ else {
+ if (tls == 0) {
+ tls = 1;
+ tls_addr = os->os_addr;
+ tls_off = os->os_off;
+ }
+
+ if (os->os_align > tls_align)
+ tls_align = os->os_align;
+ }
+
+ } else if (tls > 0)
+ tls = -1;
+
+ if ((os->os_flags & SHF_WRITE) != w || new) {
+ new = 0;
+ w = os->os_flags & SHF_WRITE;
+
+ if (!first)
+ _WRITE_PHDR(PT_LOAD, off, addr, filesz, memsz,
+ flags, align);
+
+ i++;
+ if ((unsigned) i >= lo->lo_phdr_num)
+ ld_fatal(ld, "not enough room for program"
+ " headers");
+ if (!first) {
+ addr = os->os_addr;
+ off = os->os_off;
+ }
+ first = 0;
+ flags = PF_R;
+ filesz = 0;
+ memsz = 0;
+ }
+
+ memsz = os->os_addr + os->os_size - addr;
+ if (tls > 0)
+ tls_memsz = memsz;
+
+ if (os->os_type != SHT_NOBITS) {
+ filesz = memsz;
+ if (tls > 0)
+ tls_filesz = tls_memsz;
+ }
+
+ if (os->os_flags & SHF_WRITE)
+ flags |= PF_W;
+
+ if (os->os_flags & SHF_EXECINSTR)
+ flags |= PF_X;
+ }
+ if (i >= 0)
+ _WRITE_PHDR(PT_LOAD, off, addr, filesz, memsz, flags, align);
+
+ /*
+ * Create PT_DYNAMIC segment.
+ */
+ if (lo->lo_dynamic != NULL) {
+ i++;
+ os = lo->lo_dynamic;
+ _WRITE_PHDR(PT_DYNAMIC, os->os_off, os->os_addr, os->os_size,
+ os->os_size, PF_R | PF_W, lo->lo_ec == ELFCLASS32 ? 4 : 8);
+ }
+
+ /*
+ * Create PT_NOTE segment.
+ */
+
+ STAILQ_FOREACH(os, &lo->lo_oslist, os_next) {
+ if (os->os_type == SHT_NOTE) {
+ i++;
+ if ((unsigned) i >= lo->lo_phdr_num)
+ ld_fatal(ld, "not enough room for program"
+ " headers");
+ _WRITE_PHDR(PT_NOTE, os->os_off, os->os_addr,
+ os->os_size, os->os_size, PF_R, os->os_align);
+ break;
+ }
+ }
+
+ /*
+ * Create PT_TLS segment.
+ */
+
+ if (tls != 0) {
+ i++;
+ lo->lo_tls_size = tls_memsz;
+ lo->lo_tls_align = tls_align;
+ lo->lo_tls_addr = tls_addr;
+ _WRITE_PHDR(PT_TLS, tls_off, tls_addr, tls_filesz, tls_memsz,
+ tls_flags, tls_align);
+ }
+
+ /*
+ * Create PT_GNU_EH_FRAME segment.
+ */
+ if (ld->ld_ehframe_hdr) {
+ i++;
+ os = lo->lo_ehframe_hdr;
+ assert(os != NULL);
+ _WRITE_PHDR(PT_GNU_EH_FRAME, os->os_off, os->os_addr,
+ os->os_size, os->os_size, PF_R, 4);
+ }
+
+ /*
+ * Create PT_GNU_STACK segment.
+ */
+
+ if (ld->ld_gen_gnustack) {
+ i++;
+ flags = PF_R | PF_W;
+ if (ld->ld_stack_exec)
+ flags |= PF_X;
+ align = (lo->lo_ec == ELFCLASS32) ? 4 : 8;
+ _WRITE_PHDR(PT_GNU_STACK, 0, 0, 0, 0, flags, align);
+ }
+
+ assert((unsigned) i + 1 == lo->lo_phdr_num);
+
+#undef _WRITE_PHDR
+}
+
+void
+ld_output_create(struct ld *ld)
+{
+ struct ld_output *lo;
+ GElf_Ehdr eh;
+
+ lo = ld->ld_output;
+
+ if (gelf_getehdr(lo->lo_elf, &eh) == NULL)
+ ld_fatal(ld, "gelf_getehdr failed: %s", elf_errmsg(-1));
+
+ /* Set program header table offset. */
+ eh.e_phoff = gelf_fsize(lo->lo_elf, ELF_T_EHDR, 1, EV_CURRENT);
+ if (eh.e_phoff == 0)
+ ld_fatal(ld, "gelf_fsize failed: %s", elf_errmsg(-1));
+
+ /* Set section headers table offset. */
+ eh.e_shoff = lo->lo_shoff;
+
+ /* Set executable entry point. */
+ eh.e_entry = _find_entry_point(ld);
+
+ /* Save updated ELF header. */
+ if (gelf_update_ehdr(lo->lo_elf, &eh) == 0)
+ ld_fatal(ld, "gelf_update_ehdr failed: %s", elf_errmsg(-1));
+
+ /* Allocate space for internal sections. */
+ ld_input_alloc_internal_section_buffers(ld);
+
+ /* Finalize PLT and GOT sections. */
+ ld->ld_arch->finalize_got_and_plt(ld);
+
+ /* Join and sort dynamic relocation sections. */
+ _join_and_finalize_dynamic_reloc_sections(ld, lo);
+
+ /* Finalize sections for dynamically linked output object. */
+ ld_dynamic_finalize(ld);
+
+ /* Finalize dynamic symbol section. */
+ if (lo->lo_dynsym != NULL) {
+ ld_symbols_finalize_dynsym(ld);
+ _alloc_section_data_for_symtab(ld, lo->lo_dynsym,
+ lo->lo_dynsym->os_scn, ld->ld_dynsym);
+ }
+
+ /* Generate symbol table. */
+ _create_symbol_table(ld);
+
+ /* Copy and relocate input section data to output section. */
+ _copy_and_reloc_input_sections(ld);
+
+ /* Finalize .eh_frame_hdr section. */
+ if (ld->ld_ehframe_hdr)
+ ld_ehframe_finalize_hdr(ld);
+
+ /*
+ * Join normal relocation sections if the linker is creating a
+ * relocatable object or if option -emit-relocs is specified.
+ */
+ if (ld->ld_reloc || ld->ld_emit_reloc)
+ _join_normal_reloc_sections(ld, lo);
+
+ /* Produce relocation entries. */
+ _produce_reloc_sections(ld, lo);
+
+ /* Update section headers for the output sections. */
+ _update_section_header(ld);
+
+ /* Create program headers. */
+ if (!ld->ld_reloc)
+ _create_phdr(ld);
+
+ /* Finally write out the output ELF object. */
+ if (elf_update(lo->lo_elf, ELF_C_WRITE) < 0)
+ ld_fatal(ld, "elf_update failed: %s", elf_errmsg(-1));
+}
+
+static void
+_add_to_shstrtab(struct ld *ld, const char *name)
+{
+
+ if (ld->ld_shstrtab == NULL) {
+ ld->ld_shstrtab = ld_strtab_alloc(ld, 1);
+ ld_strtab_insert(ld, ld->ld_shstrtab, ".symtab");
+ ld_strtab_insert(ld, ld->ld_shstrtab, ".strtab");
+ ld_strtab_insert(ld, ld->ld_shstrtab, ".shstrtab");
+ }
+
+ ld_strtab_insert(ld, ld->ld_shstrtab, name);
+}
+
+static void
+_update_section_header(struct ld *ld)
+{
+ struct ld_strtab *st;
+ struct ld_output *lo;
+ struct ld_output_section *os, *_os;
+ GElf_Shdr sh;
+
+ lo = ld->ld_output;
+ st = ld->ld_shstrtab;
+ assert(st != NULL);
+
+ STAILQ_FOREACH(os, &lo->lo_oslist, os_next) {
+ if (os->os_scn == NULL)
+ continue;
+
+ if (gelf_getshdr(os->os_scn, &sh) == NULL)
+ ld_fatal(ld, "gelf_getshdr failed: %s",
+ elf_errmsg(-1));
+
+ sh.sh_name = ld_strtab_lookup(st, os->os_name);
+ sh.sh_flags = os->os_flags;
+ sh.sh_addr = os->os_addr;
+ sh.sh_addralign = os->os_align;
+ sh.sh_offset = os->os_off;
+ sh.sh_size = os->os_size;
+ sh.sh_type = os->os_type;
+ sh.sh_entsize = os->os_entsize;
+
+ /* Update "sh_link" field. */
+ if (os->os_link != NULL) {
+ if (!strcmp(os->os_link, ".symtab"))
+ sh.sh_link = lo->lo_symtab_shndx;
+ else {
+ HASH_FIND_STR(lo->lo_ostbl, os->os_link, _os);
+ if (_os == NULL)
+ ld_fatal(ld, "Internal: can not find"
+ " link section %s", os->os_link);
+ sh.sh_link = elf_ndxscn(_os->os_scn);
+ }
+ }
+
+ /* Update "sh_info" field. */
+ if (os->os_info != NULL)
+ sh.sh_info = elf_ndxscn(os->os_info->os_scn);
+ else
+ sh.sh_info = os->os_info_val;
+
+#if 0
+ printf("name=%s, shname=%#jx, offset=%#jx, size=%#jx, type=%#jx\n",
+ os->os_name, (uint64_t) sh.sh_name, (uint64_t) sh.sh_offset,
+ (uint64_t) sh.sh_size, (uint64_t) sh.sh_type);
+#endif
+
+ if (!gelf_update_shdr(os->os_scn, &sh))
+ ld_fatal(ld, "gelf_update_shdr failed: %s",
+ elf_errmsg(-1));
+ }
+}
+
+static void
+_create_symbol_table(struct ld *ld)
+{
+ struct ld_state *ls;
+ struct ld_strtab *st;
+ struct ld_output *lo;
+ Elf_Scn *scn_symtab, *scn_strtab;
+ Elf_Data *d;
+ GElf_Shdr sh;
+ size_t strndx;
+
+ ld_symbols_build_symtab(ld);
+
+ ls = &ld->ld_state;
+ lo = ld->ld_output;
+ st = ld->ld_shstrtab;
+ assert(st != NULL);
+
+ /*
+ * Create .symtab section.
+ */
+ scn_symtab = _create_elf_scn(ld, lo, NULL);
+ scn_strtab = _create_elf_scn(ld, lo, NULL);
+ lo->lo_symtab_shndx = elf_ndxscn(scn_symtab);
+ strndx = elf_ndxscn(scn_strtab);
+
+ if (gelf_getshdr(scn_symtab, &sh) == NULL)
+ ld_fatal(ld, "gelf_getshdr failed: %s", elf_errmsg(-1));
+
+ sh.sh_name = ld_strtab_lookup(st, ".symtab");
+ sh.sh_flags = 0;
+ sh.sh_addr = 0;
+ sh.sh_addralign = (lo->lo_ec == ELFCLASS32) ? 4 : 8;
+ sh.sh_offset = roundup(ls->ls_offset, sh.sh_addralign);
+ sh.sh_entsize = (lo->lo_ec == ELFCLASS32) ? sizeof(Elf32_Sym) :
+ sizeof(Elf64_Sym);
+ sh.sh_size = ld->ld_symtab->sy_size * sh.sh_entsize;
+ sh.sh_type = SHT_SYMTAB;
+ sh.sh_link = strndx;
+ sh.sh_info = ld->ld_symtab->sy_first_nonlocal;
+
+ if (!gelf_update_shdr(scn_symtab, &sh))
+ ld_fatal(ld, "gelf_update_shdr failed: %s", elf_errmsg(-1));
+
+ if ((d = elf_newdata(scn_symtab)) == NULL)
+ ld_fatal(ld, "elf_newdata failed: %s", elf_errmsg(-1));
+
+ d->d_align = sh.sh_addralign;
+ d->d_off = 0;
+ d->d_type = ELF_T_SYM;
+ d->d_size = sh.sh_size;
+ d->d_version = EV_CURRENT;
+ d->d_buf = ld->ld_symtab->sy_buf;
+
+ ls->ls_offset = sh.sh_offset + sh.sh_size;
+
+ /*
+ * Create .strtab section.
+ */
+ ld_output_create_string_table_section(ld, ".strtab", ld->ld_strtab,
+ scn_strtab);
+}
+
+void
+ld_output_create_string_table_section(struct ld *ld, const char *name,
+ struct ld_strtab *st, Elf_Scn *scn)
+{
+ struct ld_state *ls;
+ struct ld_output *lo;
+ Elf_Data *d;
+ GElf_Shdr sh;
+ size_t sz;
+
+ assert(st != NULL && name != NULL);
+
+ ls = &ld->ld_state;
+ lo = ld->ld_output;
+
+ if (scn == NULL)
+ scn = _create_elf_scn(ld, lo, NULL);
+
+ if (strcmp(name, ".shstrtab") == 0) {
+ if (!elf_setshstrndx(lo->lo_elf, elf_ndxscn(scn)))
+ ld_fatal(ld, "elf_setshstrndx failed: %s",
+ elf_errmsg(-1));
+ }
+
+ if (gelf_getshdr(scn, &sh) == NULL)
+ ld_fatal(ld, "gelf_getshdr failed: %s", elf_errmsg(-1));
+
+ sh.sh_name = ld_strtab_lookup(ld->ld_shstrtab, name);
+ sh.sh_flags = 0;
+ sh.sh_addr = 0;
+ sh.sh_addralign = 1;
+ sh.sh_offset = ls->ls_offset;
+ sh.sh_size = ld_strtab_getsize(st);
+ sh.sh_type = SHT_STRTAB;
+
+ if (!gelf_update_shdr(scn, &sh))
+ ld_fatal(ld, "gelf_update_shdr failed: %s", elf_errmsg(-1));
+
+ sz = ld_strtab_getsize(st);
+
+ if ((d = elf_newdata(scn)) == NULL)
+ ld_fatal(ld, "elf_newdata failed: %s", elf_errmsg(-1));
+
+ d->d_align = 1;
+ d->d_off = 0;
+ d->d_type = ELF_T_BYTE;
+ d->d_size = sz;
+ d->d_version = EV_CURRENT;
+ d->d_buf = ld_strtab_getbuf(ld, st);
+
+ ls->ls_offset += sz;
+}