diff --git a/arch/arm/src/common/Toolchain.defs b/arch/arm/src/common/Toolchain.defs index 43f3e1c6932..6325df91cb7 100644 --- a/arch/arm/src/common/Toolchain.defs +++ b/arch/arm/src/common/Toolchain.defs @@ -601,12 +601,12 @@ ifeq ($(CONFIG_NXFLAT),y) MKNXFLAT ?= $(TOPDIR)$(DELIM)tools$(DELIM)mknxflat$(HOSTEXEEXT) -a arm endif - # ldnxflat is still an out-of-tree tool, so this is a PATH lookup. It is - # named here only so that a board which never assigned it -- lm3s6965-ek - # is one -- does not expand it to nothing and hand make a recipe starting - # with '-e', whose leading dash make then eats as "ignore errors". + # Both tools are in the tree. Naming ldnxflat here also keeps a board that + # never assigned it -- lm3s6965-ek is one -- from expanding it to nothing + # and handing make a recipe that starts with '-e', whose leading dash make + # then eats as "ignore errors". - LDNXFLAT ?= ldnxflat + LDNXFLAT ?= $(TOPDIR)$(DELIM)tools$(DELIM)ldnxflat$(HOSTEXEEXT) endif # --fixed-r9 reserves r9 across the base firmware so a callback into module diff --git a/tools/.gitignore b/tools/.gitignore index a30ce43ca8d..c297e9f13c6 100644 --- a/tools/.gitignore +++ b/tools/.gitignore @@ -7,6 +7,7 @@ /gencromfs /initialconfig /mkconfig +/ldnxflat /mknxflat /mkdeps /cnvwindeps diff --git a/tools/Makefile.host b/tools/Makefile.host index 70cff7ce602..aaea3ab6d8f 100644 --- a/tools/Makefile.host +++ b/tools/Makefile.host @@ -47,7 +47,7 @@ ifdef HOSTEXEEXT .PHONY: b16 bdf-converter cmpconfig clean configure kconfig2html mkconfig \ mkdeps mksymtab mksyscall mkversion mkpasswd cnvwindeps nxstyle \ initialconfig gencromfs convert-comments lowhex detab rmcr incdir \ - mknxflat + mknxflat ldnxflat endif ifdef HOSTDYNEXT .PHONY: jlink-nuttx @@ -136,16 +136,23 @@ mksymtab: mksymtab$(HOSTEXEEXT) endif # mknxflat - Generate the thunk file for an NXFLAT module +# ldnxflat - Convert an ELF object into an NXFLAT module MKNXFLAT_SRCS = nxflat/mknxflat.c nxflat/thunk_arm.c nxflat/thunk_thumb2.c \ nxflat/nxflat_elf.c +LDNXFLAT_SRCS = nxflat/ldnxflat.c nxflat/nxflat_elf.c mknxflat$(HOSTEXEEXT): $(MKNXFLAT_SRCS) $(Q) $(HOSTCC) $(HOSTCFLAGS) -Inxflat -o mknxflat$(HOSTEXEEXT) \ $(MKNXFLAT_SRCS) +ldnxflat$(HOSTEXEEXT): $(LDNXFLAT_SRCS) + $(Q) $(HOSTCC) $(HOSTCFLAGS) -Inxflat -o ldnxflat$(HOSTEXEEXT) \ + $(LDNXFLAT_SRCS) + ifdef HOSTEXEEXT mknxflat: mknxflat$(HOSTEXEEXT) +ldnxflat: ldnxflat$(HOSTEXEEXT) endif # bdf-converter - Converts a BDF font to the NuttX font format @@ -286,6 +293,8 @@ clean: $(call DELFILE, mkconfig.exe) $(call DELFILE, mknxflat) $(call DELFILE, mknxflat.exe) + $(call DELFILE, ldnxflat) + $(call DELFILE, ldnxflat.exe) $(call DELFILE, mkdeps) $(call DELFILE, mkdeps.exe) $(call DELFILE, mksymtab) diff --git a/tools/Unix.mk b/tools/Unix.mk index 9169fb16a71..46823a0008f 100644 --- a/tools/Unix.mk +++ b/tools/Unix.mk @@ -289,6 +289,9 @@ tools/mkpasswd$(HOSTEXEEXT): tools/mknxflat$(HOSTEXEEXT): $(Q) $(MAKE) -C tools -f Makefile.host mknxflat$(HOSTEXEEXT) +tools/ldnxflat$(HOSTEXEEXT): + $(Q) $(MAKE) -C tools -f Makefile.host ldnxflat$(HOSTEXEEXT) + # .dirlinks, and helpers # # Directories links. Most of establishing the NuttX configuration involves @@ -673,12 +676,14 @@ ifeq ($(CONFIG_BOARD_ETC_ROMFS_PASSWD_ENABLE),y) PASSWD_TOOL_DEP += tools/mkpasswd$(HOSTEXEEXT) endif -# mknxflat generates the import thunks for an NXFLAT module, so it is only -# needed by a configuration that builds them. +# mknxflat generates the import thunks for an NXFLAT module and ldnxflat +# converts it, so both are needed by a configuration that builds them, and by +# no other. NXFLAT_TOOL_DEP = ifeq ($(CONFIG_NXFLAT),y) NXFLAT_TOOL_DEP += tools/mknxflat$(HOSTEXEEXT) +NXFLAT_TOOL_DEP += tools/ldnxflat$(HOSTEXEEXT) endif pass1dep: context tools/mkdeps$(HOSTEXEEXT) tools/cnvwindeps$(HOSTEXEEXT) $(PASSWD_TOOL_DEP) $(NXFLAT_TOOL_DEP) diff --git a/tools/nxflat/ldnxflat.c b/tools/nxflat/ldnxflat.c new file mode 100644 index 00000000000..6d7fb721811 --- /dev/null +++ b/tools/nxflat/ldnxflat.c @@ -0,0 +1,1247 @@ +/**************************************************************************** + * tools/nxflat/ldnxflat.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * ldnxflat converts the ELF object that + * + * ld -r -d -T binfmt/libnxflat/gnu-nxflat-gotoff.ld -no-check-sections + * + * produces into the NXFLAT container that binfmt/libnxflat loads. + * + * The file it writes: + * + * struct nxflat_hdr_s network byte order + * the I-Space image h_entry counts from the file + * the D-Space image, GOT first offsets count from its own start + * struct nxflat_reloc_s[] target byte order + * + * I-Space is mapped from the start of the file, so h_entry is biased by + * the header's size. An I-Space value left in D-Space is not: + * nxflat_bindrel32i() adds ispace + sizeof(struct nxflat_hdr_s) itself. + * + * A call within I-Space and a GOT-relative reference are resolved here. + * The rest name an address the loader only knows once it has placed the + * segments, and become records: REL32I for a D-Space word holding an + * I-Space address, REL32D for one holding a D-Space address. Every GOT + * entry is a REL32D, which is what makes the GOT work. + * + * Provenance. Written from include/nxflat.h, from what binfmt/libnxflat + * does with the container, and from gnu-nxflat-gotoff.ld; the relocation + * arithmetic is that of libs/libc/machine/arm/armv7-m/arch_elf.c, which the + * ELF loader runs on the target for the same relocations. Those files and + * this one are Apache-2.0. + * + * The tool of the same name in buildroot is GPL by descent from elf2flt and + * no part of it is used. It was run on the same inputs to compare output, + * which is how the conventions the container does not state -- where the GOT + * sits, the order of its entries and of the records -- were matched. Two of + * its results are deliberately not matched, because they are wrong: a GOT + * entry naming a .bss object, and the alignment gap before .bss in h_bssend. + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nxflat_elf.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* The container, field for field as include/nxflat.h has it. That header is + * not included because it reaches for nuttx/config.h. + */ + +#define NXFLAT_MAGIC "NxFT" +#define NXFLAT_HDR_SIZE 36 +#define NXFLAT_RELOC_TYPE_REL32I 0 +#define NXFLAT_RELOC_TYPE_REL32D 1 +#define NXFLAT_RELOC(t, o) (((uint32_t)((t) & 3) << 30) | ((o) & 0x3fffffff)) + +#define DEFAULT_STACK_SIZE 4096 + +/* The relocations a module carries, computed as arch_elf.c computes them. */ + +#define R_ARM_ABS32 2 +#define R_ARM_REL32 3 +#define R_ARM_THM_CALL 10 +#define R_ARM_GOTOFF32 24 +#define R_ARM_GOT_BREL 26 +#define R_ARM_PLT32 27 +#define R_ARM_CALL 28 +#define R_ARM_JUMP24 29 +#define R_ARM_TARGET1 38 +#define R_ARM_THM_JUMP24 30 +#define R_ARM_PC24 1 + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* Which segment a section, a symbol or a relocation belongs to. */ + +enum segment_e +{ + SEG_NONE = 0, + SEG_TEXT, + SEG_DATA +}; + +struct segment_s +{ + uint8_t *img; /* The image being built */ + uint32_t size; /* Bytes in it */ +}; + +/* One relocation, as the architecture is handed it. Addresses count from + * the start of their segment, which is what the loader adds its bases to. + */ + +struct reloc_s +{ + uint32_t type; /* ELF32_R_TYPE(r_info) */ + uint32_t place; /* Where the word is */ + enum segment_e pseg; /* ...and in which segment */ + uint32_t value; /* Where the symbol resolved to */ + enum segment_e vseg; /* ...and in which segment */ + uint32_t gotoffset; /* Its GOT entry, if the type takes one */ + bool isfunc; /* The symbol is a function */ + const char *symname; /* For the diagnostics */ + uint8_t *p; /* The word itself */ +}; + +/* What the architecture did with it */ + +enum reloc_action_e +{ + RELOC_DONE = 0, /* Resolved; the loader has nothing to do */ + RELOC_RECORD /* The word holds an address: record it */ +}; + +/* An architecture supplies what depends on the instruction set: which + * relocations exist, what they compute, and which reach through the GOT. + * The rest of this file is common, because a REL32I or REL32D fixup is the + * addition of a base to a 32-bit word. + */ + +struct nxflat_arch_s +{ + const char *name; + uint16_t machine; /* e_machine selects it */ + uint32_t (*entry)(uint32_t value); /* The entry point address */ + bool (*is_gotref)(uint32_t type); /* Type needs a GOT entry */ + enum reloc_action_e (*reloc)(struct reloc_s *r); +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static void fail(const char *fmt, ...); +static void select_arch(uint16_t machine); +static const char *sym_name(const struct elf32_sym_s *sym); +static uint32_t arm_entry(uint32_t value); +static bool arm_is_gotref(uint32_t type); +static enum reloc_action_e arm_reloc(struct reloc_s *r); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const char *g_program; +static const char *g_elf_filename; +static const char *g_out_filename; +static const char *g_entry_name; +static uint32_t g_stacksize = DEFAULT_STACK_SIZE; +static int g_verbose; + +static uint8_t *g_elf; /* The whole input file */ +static struct elf32_ehdr_s *g_ehdr; +static struct elf32_shdr_s *g_shdr; +static struct elf32_sym_s *g_syms; +static size_t g_nsyms; +static const char *g_strtab; +static enum segment_e *g_segof; /* Per section */ + +static const struct nxflat_arch_s *g_arch; + +static struct segment_s g_text; +static struct segment_s g_data; +static uint32_t g_bsssize; +static uint32_t g_gotsize; + +/* ld -r leaves a tentative definition ("int counter;") SHN_COMMON, with its + * alignment in st_value and its size in st_size. Nothing has placed it, so + * this does, at the end of the bss. Zero means the symbol is not one. + */ + +static uint32_t *g_commonaddr; + +/* One GOT entry per symbol that a GOT-relative reference names, in the order + * the relocations name them. + */ + +struct gotent_s +{ + uint32_t symidx; + uint32_t value; /* The address the entry will hold */ + enum segment_e seg; /* Which segment that address is in */ +}; + +static struct gotent_s *g_got; +static uint32_t g_ngot; + +/* The relocation records for the loader */ + +static uint32_t *g_relocs; +static uint32_t g_nrelocs; + +/* The architectures this tool knows. NXFLAT is tied to none of them, so + * another one is a table entry and a handler. + */ + +static const struct nxflat_arch_s g_arches[] = +{ + { + "arm", /* name */ + EM_ARM, /* machine */ + arm_entry, /* entry */ + arm_is_gotref, /* is_gotref */ + arm_reloc /* reloc */ + } +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void fail(const char *fmt, ...) +{ + va_list ap; + + fprintf(stderr, "%s: ", g_program); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "\n"); + exit(2); +} + +/**************************************************************************** + * Name: read_elf + * + * Description: + * Read the object and check that it is one this tool can convert. + * + ****************************************************************************/ + +static void read_elf(void) +{ + struct nxflat_elf_s elf; + + nxflat_elf_read(&elf, g_elf_filename, g_program); + + g_elf = elf.img; + g_ehdr = elf.ehdr; + g_shdr = elf.shdr; + g_syms = elf.syms; + g_nsyms = elf.nsyms; + g_strtab = elf.strtab; + + if (g_ehdr->e_type != ET_REL) + { + fail("%s is not a relocatable object; it must be linked with ld -r", + g_elf_filename); + } + + /* The images are written out as they were read, so a big-endian target + * would need every patched word swapped. None exists; refuse it rather + * than write a wrong file. + */ + + if (!elf.littleendian) + { + fail("%s is big endian, which this tool cannot convert", + g_elf_filename); + } + + select_arch(g_ehdr->e_machine); +} + +/**************************************************************************** + * Name: place_sections + * + * Description: + * Sort the allocated sections into the two segments and size them. The + * linker script has placed each one within its segment already, both + * segments starting at zero -- hence -no-check-sections on the link. + * + * Executable is I-Space; everything else allocated is D-Space, read-only + * data included, because the model reaches that through the GOT. + * + ****************************************************************************/ + +static void place_sections(void) +{ + uint32_t dataend = 0; + uint32_t bssend = 0; + size_t i; + + g_segof = calloc(g_ehdr->e_shnum, sizeof(*g_segof)); + if (g_segof == NULL) + { + fail("out of memory"); + } + + for (i = 1; i < g_ehdr->e_shnum; i++) + { + struct elf32_shdr_s *s = &g_shdr[i]; + uint32_t end = s->sh_addr + s->sh_size; + + if ((s->sh_flags & SHF_ALLOC) == 0) + { + continue; + } + + if ((s->sh_flags & SHF_EXECINSTR) != 0) + { + g_segof[i] = SEG_TEXT; + if (end > g_text.size) + { + g_text.size = end; + } + } + else + { + g_segof[i] = SEG_DATA; + + if (s->sh_type == SHT_NOBITS) + { + if (end > bssend) + { + bssend = end; + } + } + else if (end > dataend) + { + dataend = end; + } + } + } + + if (bssend < dataend) + { + bssend = dataend; + } + + g_data.size = dataend; + g_bsssize = bssend - dataend; +} + +/**************************************************************************** + * Name: sym_segment and sym_value + * + * Description: + * Where a symbol ends up. D-Space starts with the GOT, so everything the + * linker put there moves up by its size. + * + ****************************************************************************/ + +static enum segment_e sym_segment(const struct elf32_sym_s *sym) +{ + if (sym->st_shndx == SHN_COMMON) + { + return SEG_DATA; + } + + if (sym->st_shndx == SHN_UNDEF || sym->st_shndx >= g_ehdr->e_shnum) + { + return SEG_NONE; + } + + return g_segof[sym->st_shndx]; +} + +static uint32_t sym_value(const struct elf32_sym_s *sym) +{ + uint32_t value; + + if (sym->st_shndx == SHN_COMMON) + { + /* Placed by allocate_common(), which counts from the same zero the + * linker gave the sections. + */ + + value = g_commonaddr[sym - g_syms]; + } + else + { + value = sym->st_value; + + if (sym->st_shndx < g_ehdr->e_shnum) + { + value += g_shdr[sym->st_shndx].sh_addr; + } + } + + if (sym_segment(sym) == SEG_DATA) + { + value += g_gotsize; + } + + return value; +} + +/**************************************************************************** + * Name: allocate_common + * + * Description: + * Place every SHN_COMMON symbol at the end of the bss, in symbol table + * order, at the alignment it asks for. + * + ****************************************************************************/ + +static void allocate_common(void) +{ + uint32_t next = g_data.size + g_bsssize; + size_t i; + + g_commonaddr = calloc(g_nsyms, sizeof(*g_commonaddr)); + if (g_commonaddr == NULL) + { + fail("out of memory"); + } + + for (i = 0; i < g_nsyms; i++) + { + uint32_t align = g_syms[i].st_value; + + if (g_syms[i].st_shndx != SHN_COMMON) + { + continue; + } + + if (align > 1) + { + next = (next + align - 1) & ~(align - 1); + } + + g_commonaddr[i] = next; + next += g_syms[i].st_size; + + if (g_verbose > 1) + { + printf(" common %s at D-Space %08x, %u bytes\n", + sym_name(&g_syms[i]), next - g_syms[i].st_size, + g_syms[i].st_size); + } + } + + g_bsssize = next - g_data.size; +} + +static const char *sym_name(const struct elf32_sym_s *sym) +{ + return g_strtab + sym->st_name; +} + +/**************************************************************************** + * Name: find_symbol + ****************************************************************************/ + +static const struct elf32_sym_s *find_symbol(const char *name) +{ + size_t i; + + for (i = 0; i < g_nsyms; i++) + { + if (g_syms[i].st_name != 0 && + g_syms[i].st_shndx != SHN_UNDEF && + strcmp(sym_name(&g_syms[i]), name) == 0) + { + return &g_syms[i]; + } + } + + return NULL; +} + +/**************************************************************************** + * Name: allocate_got + * + * Description: + * One entry per symbol a GOT-relative reference names, in the order they + * are named. Sized before anything is placed, because the GOT sits at the + * start of D-Space and moves the rest up. + * + ****************************************************************************/ + +static uint32_t got_index(uint32_t symidx) +{ + uint32_t i; + + for (i = 0; i < g_ngot; i++) + { + if (g_got[i].symidx == symidx) + { + return i; + } + } + + g_got = realloc(g_got, (g_ngot + 1) * sizeof(*g_got)); + if (g_got == NULL) + { + fail("out of memory"); + } + + g_got[g_ngot].symidx = symidx; + g_got[g_ngot].value = 0; + g_got[g_ngot].seg = SEG_NONE; + return g_ngot++; +} + +static void allocate_got(void) +{ + size_t i; + size_t j; + + for (i = 1; i < g_ehdr->e_shnum; i++) + { + struct elf32_rel_s *rel; + size_t nrel; + + if (g_shdr[i].sh_type != SHT_REL || + g_segof[g_shdr[i].sh_info] == SEG_NONE) + { + continue; + } + + rel = (struct elf32_rel_s *)(g_elf + g_shdr[i].sh_offset); + nrel = g_shdr[i].sh_size / sizeof(struct elf32_rel_s); + + for (j = 0; j < nrel; j++) + { + if (g_arch->is_gotref(ELF32_R_TYPE(rel[j].r_info))) + { + got_index(ELF32_R_SYM(rel[j].r_info)); + } + } + } + + g_gotsize = g_ngot * sizeof(uint32_t); +} + +/**************************************************************************** + * Name: build_images + * + * Description: + * Copy each section to the address the linker gave it, above the GOT in + * D-Space. + * + ****************************************************************************/ + +static void build_images(void) +{ + size_t i; + + g_data.size += g_gotsize; + + g_text.img = calloc(1, g_text.size ? g_text.size : 1); + g_data.img = calloc(1, g_data.size ? g_data.size : 1); + if (g_text.img == NULL || g_data.img == NULL) + { + fail("out of memory"); + } + + for (i = 1; i < g_ehdr->e_shnum; i++) + { + struct elf32_shdr_s *s = &g_shdr[i]; + + if (g_segof[i] == SEG_NONE || s->sh_type == SHT_NOBITS || + s->sh_size == 0) + { + continue; + } + + if (g_segof[i] == SEG_TEXT) + { + memcpy(g_text.img + s->sh_addr, g_elf + s->sh_offset, s->sh_size); + } + else + { + memcpy(g_data.img + g_gotsize + s->sh_addr, + g_elf + s->sh_offset, s->sh_size); + } + } +} + +/**************************************************************************** + * Name: add_reloc + ****************************************************************************/ + +static void add_reloc(int type, uint32_t offset) +{ + g_relocs = realloc(g_relocs, (g_nrelocs + 1) * sizeof(uint32_t)); + if (g_relocs == NULL) + { + fail("out of memory"); + } + + g_relocs[g_nrelocs++] = NXFLAT_RELOC(type, offset); + + if (g_verbose > 1) + { + printf(" record %s at D-Space %08x\n", + type == NXFLAT_RELOC_TYPE_REL32I ? "REL32I" : "REL32D", offset); + } +} + +/**************************************************************************** + * Name: target_word + * + * Description: + * The word a relocation patches, in whichever image it lives in. + * + ****************************************************************************/ + +static uint8_t *target_word(enum segment_e seg, uint32_t offset) +{ + if (seg == SEG_TEXT) + { + if (offset + 4 > g_text.size) + { + fail("relocation at I-Space %08x is outside the text", offset); + } + + return g_text.img + offset; + } + + if (offset + 4 > g_data.size) + { + fail("relocation at D-Space %08x is outside the data", offset); + } + + return g_data.img + offset; +} + +static uint32_t get32(const uint8_t *p) +{ + return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | + ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); +} + +static void put32(uint8_t *p, uint32_t v) +{ + p[0] = (uint8_t)v; + p[1] = (uint8_t)(v >> 8); + p[2] = (uint8_t)(v >> 16); + p[3] = (uint8_t)(v >> 24); +} + +static uint32_t get16(const uint8_t *p) +{ + return (uint32_t)p[0] | ((uint32_t)p[1] << 8); +} + +static void put16(uint8_t *p, uint32_t v) +{ + p[0] = (uint8_t)v; + p[1] = (uint8_t)(v >> 8); +} + +/**************************************************************************** + * Name: reloc_thm_call + * + * Description: + * A Thumb BL or B.W, encoded as arch_elf.c encodes it. Both ends are in + * I-Space, so the loader never sees it. + * + ****************************************************************************/ + +static void reloc_thm_call(uint8_t *p, uint32_t place, uint32_t value, + bool isfunc) +{ + uint32_t upper_insn = get16(p); + uint32_t lower_insn = get16(p + 2); + int32_t offset; + uint32_t S; + uint32_t J1; + uint32_t J2; + + S = (upper_insn >> 10) & 1; + J1 = (lower_insn >> 13) & 1; + J2 = (lower_insn >> 11) & 1; + + offset = (int32_t)((S << 24) | + ((~(J1 ^ S) & 1) << 23) | + ((~(J2 ^ S) & 1) << 22) | + ((upper_insn & 0x03ff) << 12) | + ((lower_insn & 0x07ff) << 1)); + + if ((offset & 0x01000000) != 0) + { + offset -= 0x02000000; + } + + offset += (int32_t)value - (int32_t)place; + + if (isfunc && (offset & 1) == 0) + { + fail("THM_CALL at %08x needs an odd offset, got %08x", place, offset); + } + + if (offset < (int32_t)0xff000000 || offset >= (int32_t)0x01000000) + { + fail("THM_CALL at %08x is out of range, target %08x", place, offset); + } + + S = (offset >> 24) & 1; + J1 = S ^ (~(offset >> 23) & 1); + J2 = S ^ (~(offset >> 22) & 1); + + put16(p, (upper_insn & 0xf800) | (S << 10) | ((offset >> 12) & 0x03ff)); + put16(p + 2, (lower_insn & 0xd000) | (J1 << 13) | (J2 << 11) | + ((offset >> 1) & 0x07ff)); +} + +/**************************************************************************** + * Name: reloc_call24 + * + * Description: + * An ARM-mode BL, for the boards that build modules without Thumb. + * + ****************************************************************************/ + +static void reloc_call24(uint8_t *p, uint32_t place, uint32_t value) +{ + uint32_t insn = get32(p); + int32_t offset = (int32_t)((insn & 0x00ffffff) << 2); + + if ((offset & 0x02000000) != 0) + { + offset -= 0x04000000; + } + + offset += (int32_t)value - (int32_t)place; + + if ((offset & 3) != 0 || + offset < (int32_t)0xfe000000 || offset >= (int32_t)0x02000000) + { + fail("CALL at %08x is out of range, target %08x", place, offset); + } + + put32(p, (insn & 0xff000000) | ((offset >> 2) & 0x00ffffff)); +} + +/**************************************************************************** + * Name: arm_entry / arm_is_gotref / arm_reloc + * + * Description: + * ARM and Thumb-2, with the arithmetic of arch_elf.c. + * + ****************************************************************************/ + +static uint32_t arm_entry(uint32_t value) +{ + /* The loader adds this to the mapped I-Space and enters it, so it is an + * address, not a function pointer: the Thumb bit comes off. + */ + + return value & ~1u; +} + +static bool arm_is_gotref(uint32_t type) +{ + return type == R_ARM_GOT_BREL; +} + +static enum reloc_action_e arm_reloc(struct reloc_s *r) +{ + switch (r->type) + { + case R_ARM_ABS32: + case R_ARM_TARGET1: + + /* An address, which only the loader can finish. The word keeps the + * segment-relative value, Thumb bit and all, and gains a record. + */ + + if (r->pseg != SEG_DATA) + { + fail("ABS32 in I-Space at %08x cannot be relocated at load " + "time; only D-Space can", r->place); + } + + put32(r->p, get32(r->p) + r->value); + return RELOC_RECORD; + + case R_ARM_REL32: + + /* Both ends must be in one segment for this to survive the segments + * being placed apart. + */ + + if (r->pseg != r->vseg) + { + fail("REL32 at %s %08x reaches %s %08x across the segments; " + "build the module with -mno-pic-data-is-text-relative", + r->pseg == SEG_TEXT ? "I-Space" : "D-Space", r->place, + r->vseg == SEG_TEXT ? "I-Space" : "D-Space", r->value); + } + + put32(r->p, get32(r->p) + r->value - r->place); + return RELOC_DONE; + + case R_ARM_GOTOFF32: + + /* An offset from the base the module carries in its PIC register, + * which is the start of D-Space. + */ + + if (r->vseg != SEG_DATA) + { + fail("GOTOFF32 at %08x names %s, which is not in D-Space", + r->place, r->symname); + } + + put32(r->p, get32(r->p) + r->value); + return RELOC_DONE; + + case R_ARM_GOT_BREL: + + /* The reference holds where the entry is, counted from the start of + * the GOT. The entry itself is filled in and recorded by the + * generic half, because that is the same for every architecture. + */ + + put32(r->p, get32(r->p) + r->gotoffset); + return RELOC_DONE; + + case R_ARM_THM_CALL: + case R_ARM_THM_JUMP24: + + if (r->pseg != SEG_TEXT || r->vseg != SEG_TEXT) + { + fail("a Thumb branch at %08x leaves I-Space", r->place); + } + + reloc_thm_call(r->p, r->place, r->value, r->isfunc); + return RELOC_DONE; + + case R_ARM_PC24: + case R_ARM_CALL: + case R_ARM_JUMP24: + case R_ARM_PLT32: + + if (r->pseg != SEG_TEXT || r->vseg != SEG_TEXT) + { + fail("an ARM branch at %08x leaves I-Space", r->place); + } + + reloc_call24(r->p, r->place, r->value); + return RELOC_DONE; + + default: + fail("%s relocation %u at %s %08x is not handled", g_arch->name, + r->type, r->pseg == SEG_TEXT ? "I-Space" : "D-Space", r->place); + return RELOC_DONE; + } +} + +/**************************************************************************** + * Name: select_arch + ****************************************************************************/ + +static void select_arch(uint16_t machine) +{ + size_t i; + + for (i = 0; i < sizeof(g_arches) / sizeof(g_arches[0]); i++) + { + if (g_arches[i].machine == machine) + { + g_arch = &g_arches[i]; + return; + } + } + + fail("%s is for machine %u, which this tool does not know. An " + "architecture needs an entry in g_arches[] naming its relocations", + g_elf_filename, machine); +} + +/**************************************************************************** + * Name: resolve_relocs + * + * Description: + * Apply what can be applied; record what cannot. + * + ****************************************************************************/ + +static void resolve_relocs(void) +{ + size_t i; + size_t j; + + for (i = 1; i < g_ehdr->e_shnum; i++) + { + enum segment_e tseg; + struct elf32_rel_s *rel; + uint32_t tbase; + size_t nrel; + + if (g_shdr[i].sh_type != SHT_REL) + { + continue; + } + + tseg = g_segof[g_shdr[i].sh_info]; + if (tseg == SEG_NONE) + { + continue; + } + + /* Where the section this relocates sits in its segment. A D-Space + * section sits above the GOT. + */ + + tbase = g_shdr[g_shdr[i].sh_info].sh_addr; + if (tseg == SEG_DATA) + { + tbase += g_gotsize; + } + + rel = (struct elf32_rel_s *)(g_elf + g_shdr[i].sh_offset); + nrel = g_shdr[i].sh_size / sizeof(struct elf32_rel_s); + + for (j = 0; j < nrel; j++) + { + struct reloc_s r; + uint32_t type = ELF32_R_TYPE(rel[j].r_info); + uint32_t symidx = ELF32_R_SYM(rel[j].r_info); + const struct elf32_sym_s *sym; + enum segment_e sseg; + uint32_t place; + uint32_t value; + uint8_t *p; + + if (symidx >= g_nsyms) + { + fail("relocation names symbol %u of %zu", symidx, g_nsyms); + } + + sym = &g_syms[symidx]; + sseg = sym_segment(sym); + value = sym_value(sym); + place = tbase + rel[j].r_offset; + p = target_word(tseg, place); + + if (g_verbose > 1) + { + printf(" reloc %2u at %s %08x -> %s %08x %s\n", type, + tseg == SEG_TEXT ? "I" : "D", place, + sseg == SEG_TEXT ? "I" : sseg == SEG_DATA ? "D" : "?", + value, sym_name(sym)); + } + + if (sym->st_shndx == SHN_UNDEF) + { + fail("%s is undefined; every import must come from the thunk " + "that mknxflat generates", sym_name(sym)); + } + + r.type = type; + r.place = place; + r.pseg = tseg; + r.value = value; + r.vseg = sseg; + r.isfunc = ELF32_ST_TYPE(sym->st_info) == STT_FUNC; + r.symname = sym_name(sym); + r.p = p; + r.gotoffset = 0; + + if (g_arch->is_gotref(type)) + { + uint32_t idx = got_index(symidx); + + /* The entry holds the address, which may be a function in + * I-Space as readily as an object in D-Space, and keeps the + * Thumb bit or whatever else the symbol carries. + */ + + if (sseg == SEG_NONE) + { + fail("a GOT reference at %08x names %s, which is in " + "neither segment", place, sym_name(sym)); + } + + g_got[idx].value = value; + g_got[idx].seg = sseg; + r.gotoffset = idx * sizeof(uint32_t); + } + + if (g_arch->reloc(&r) == RELOC_RECORD) + { + add_reloc(sseg == SEG_TEXT ? NXFLAT_RELOC_TYPE_REL32I : + NXFLAT_RELOC_TYPE_REL32D, place); + } + } + } + + /* Every GOT entry is a D-Space word holding an address, so each one gets a + * record: the I-Space kind when it holds a function. + */ + + for (j = 0; j < g_ngot; j++) + { + put32(g_data.img + j * sizeof(uint32_t), g_got[j].value); + add_reloc(g_got[j].seg == SEG_TEXT ? NXFLAT_RELOC_TYPE_REL32I : + NXFLAT_RELOC_TYPE_REL32D, + j * sizeof(uint32_t)); + } +} + +/**************************************************************************** + * Name: put_be32 / write_output + * + * Description: + * Write the container. The header is in network order, which + * include/nxflat.h states and the loader's NTOHL() calls need; the images + * and the records are in the target's. + * + ****************************************************************************/ + +static void put_be32(uint8_t *p, uint32_t v) +{ + p[0] = (uint8_t)(v >> 24); + p[1] = (uint8_t)(v >> 16); + p[2] = (uint8_t)(v >> 8); + p[3] = (uint8_t)v; +} + +static void put_be16(uint8_t *p, uint32_t v) +{ + p[0] = (uint8_t)(v >> 8); + p[1] = (uint8_t)v; +} + +static void write_output(void) +{ + const struct elf32_sym_s *entry; + const struct elf32_sym_s *ibegin; + const struct elf32_sym_s *iend; + uint8_t hdr[NXFLAT_HDR_SIZE]; + uint32_t importsymbols = 0; + uint32_t importcount = 0; + uint32_t datastart; + uint32_t dataend; + FILE *out; + + entry = find_symbol(g_entry_name); + if (entry == NULL) + { + fail("entry point %s is not defined in %s", + g_entry_name, g_elf_filename); + } + + if (sym_segment(entry) != SEG_TEXT) + { + fail("entry point %s is not in I-Space", g_entry_name); + } + + /* mknxflat puts the array of imported symbols in D-Space and marks its + * ends. A module that imports nothing has neither symbol. + */ + + ibegin = find_symbol("__dynimport_begin"); + iend = find_symbol("__dynimport_end"); + + datastart = NXFLAT_HDR_SIZE + g_text.size; + dataend = datastart + g_data.size; + + if (ibegin != NULL && iend != NULL) + { + if (sym_segment(ibegin) != SEG_DATA) + { + fail("__dynimport_begin is not in D-Space"); + } + + importsymbols = datastart + sym_value(ibegin); + importcount = (sym_value(iend) - sym_value(ibegin)) / 8; + } + + memset(hdr, 0, sizeof(hdr)); + memcpy(hdr, NXFLAT_MAGIC, 4); + put_be32(hdr + 4, NXFLAT_HDR_SIZE + g_arch->entry(sym_value(entry))); + put_be32(hdr + 8, datastart); + put_be32(hdr + 12, dataend); + put_be32(hdr + 16, dataend + g_bsssize); + put_be32(hdr + 20, g_stacksize); + put_be32(hdr + 24, dataend); /* The records follow the data */ + put_be32(hdr + 28, importsymbols); + put_be16(hdr + 32, g_nrelocs); + put_be16(hdr + 34, importcount); + + out = fopen(g_out_filename, "wb"); + if (out == NULL) + { + fail("%s: cannot create: %s", g_out_filename, strerror(errno)); + } + + if (fwrite(hdr, sizeof(hdr), 1, out) != 1 || + (g_text.size != 0 && + fwrite(g_text.img, g_text.size, 1, out) != 1) || + (g_data.size != 0 && + fwrite(g_data.img, g_data.size, 1, out) != 1) || + (g_nrelocs != 0 && + fwrite(g_relocs, sizeof(uint32_t), g_nrelocs, out) != g_nrelocs)) + { + fail("%s: cannot write: %s", g_out_filename, strerror(errno)); + } + + fclose(out); + + if (g_verbose != 0) + { + printf("%s: entry %s at %08x\n", g_out_filename, g_entry_name, + NXFLAT_HDR_SIZE + g_arch->entry(sym_value(entry))); + printf(" I-Space %08x-%08x %u bytes\n", NXFLAT_HDR_SIZE, datastart, + g_text.size); + printf(" D-Space %08x-%08x %u bytes, of which %u GOT, %u bss\n", + datastart, dataend, g_data.size, g_gotsize, g_bsssize); + printf(" %u relocation record(s), %u import(s)\n", + g_nrelocs, importcount); + } +} + +/**************************************************************************** + * Name: show_usage + ****************************************************************************/ + +static void show_usage(void) +{ + fprintf(stderr, "Usage: %s -e -o [-s ] [-v] " + "\n\n", g_program); + fprintf(stderr, "Convert one ELF object into an NXFLAT module. The\n"); + fprintf(stderr, "object is what these produce, in this order:\n\n"); + fprintf(stderr, " ld -r -d -warn-common\n"); + fprintf(stderr, " mknxflat, assembled and linked in\n"); + fprintf(stderr, " ld -r -d -warn-common -T gnu-nxflat-gotoff.ld" + " -no-check-sections\n\n"); + fprintf(stderr, " -e The symbol the loader enters. Needed:\n"); + fprintf(stderr, " a module has no crt0, so no name for it\n"); + fprintf(stderr, " is conventional.\n"); + fprintf(stderr, " -o Where to write it. Needed.\n"); + fprintf(stderr, " -s Stack the module is given, in bytes" + " [%d].\n", DEFAULT_STACK_SIZE); + fprintf(stderr, " -v Say what was placed; twice, every" + " relocation.\n"); + fprintf(stderr, "\n"); + exit(1); +} + +/**************************************************************************** + * Name: parse_args + ****************************************************************************/ + +static void parse_args(int argc, char **argv) +{ + int opt; + + g_program = argv[0]; + + while ((opt = getopt(argc, argv, "e:o:s:v")) != -1) + { + switch (opt) + { + case 'e': + g_entry_name = optarg; + break; + + case 'o': + g_out_filename = optarg; + break; + + case 's': + g_stacksize = (uint32_t)strtoul(optarg, NULL, 0); + break; + + case 'v': + g_verbose++; + break; + + default: + show_usage(); + break; + } + } + + if (optind != argc - 1) + { + fprintf(stderr, "%s: one input file is needed\n", argv[0]); + show_usage(); + } + + g_elf_filename = argv[optind]; + + /* Neither is guessed: an entry point is whatever its author called it, + * and every board that builds a module already says so. + */ + + if (g_entry_name == NULL || g_out_filename == NULL) + { + fprintf(stderr, "%s: -e and -o are both needed\n", argv[0]); + show_usage(); + } +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int main(int argc, char **argv) +{ + parse_args(argc, argv); + + read_elf(); + place_sections(); + allocate_common(); + allocate_got(); + build_images(); + resolve_relocs(); + write_output(); + + return 0; +}