/**************************************************************************** * tools/nxflat/mknxflat.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. * ****************************************************************************/ /**************************************************************************** * mknxflat generates the "thunk" assembly file for an NXFLAT module: one * stub per imported function, plus the import name string table and the * per-process __dyninfo array the loader fills in at load time. * * This is a port of the tool from the NuttX buildroot NXFLAT toolchain. * The one substantive change is that the symbol table is read from the ELF * file directly rather than through libbfd. libbfd is GPL, which an Apache * project cannot depend on, and it is awkward to obtain besides -- but the * dependency was never deep: the upstream tool used it only to open the * file and enumerate symbols, never to relocate or rewrite anything. * * The emitted text is unchanged. The format strings live in the .def * files, which are carried here byte-for-byte from upstream. ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include "nxflat_thunk.h" /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ #define dbg(format, ...) \ do \ { \ if (verbose) \ { \ printf(format, ##__VA_ARGS__); \ } \ } \ while (0) /* Just enough of the ELF32 ABI to walk a symbol table. Spelled out here * rather than pulled from so the tool builds on any host. */ #define EI_NIDENT 16 #define ELFCLASS32 1 #define ELFDATA2LSB 1 #define ELFDATA2MSB 2 #define SHT_SYMTAB 2 #define SHT_DYNSYM 11 #define SHN_UNDEF 0 #define STB_WEAK 2 #define STT_OBJECT 1 #define ELF_ST_BIND(i) ((i) >> 4) #define ELF_ST_TYPE(i) ((i) & 0x0f) #define MAX_EXPORT_NAMES 1024 /**************************************************************************** * Private Types ****************************************************************************/ struct elf32_ehdr_s { unsigned char e_ident[EI_NIDENT]; uint16_t e_type; uint16_t e_machine; uint32_t e_version; uint32_t e_entry; uint32_t e_phoff; uint32_t e_shoff; uint32_t e_flags; uint16_t e_ehsize; uint16_t e_phentsize; uint16_t e_phnum; uint16_t e_shentsize; uint16_t e_shnum; uint16_t e_shstrndx; }; struct elf32_shdr_s { uint32_t sh_name; uint32_t sh_type; uint32_t sh_flags; uint32_t sh_addr; uint32_t sh_offset; uint32_t sh_size; uint32_t sh_link; uint32_t sh_info; uint32_t sh_addralign; uint32_t sh_entsize; }; struct elf32_sym_s { uint32_t st_name; uint32_t st_value; uint32_t st_size; unsigned char st_info; unsigned char st_other; uint16_t st_shndx; }; /* One imported symbol, in symbol table order */ struct import_s { const char *name; int is_object; int is_weak; }; typedef int (*namefunc_type)(const char *name, void *arg); /**************************************************************************** * Private Data ****************************************************************************/ /* Command line settings (counters but treated like booleans) */ static int verbose = 0; static int weak_imports = 0; static int dsyms = 0; /* Characteristics of things */ static int calls_nonreturning_functions = 0; /* Names of things */ static const char *program_name = NULL; static const char *elf_filename = NULL; static const char *out_filename = NULL; /* The selected architecture's thunk format strings */ static const struct nxflat_thunk_s *thunk = NULL; /* The imported symbols, in symbol table order */ static struct import_s *imports = NULL; static long number_undefined = 0; static int counter; /* Big-endian input? ARM is normally little-endian but big-endian ARM * exists, so honour EI_DATA rather than assuming. */ static int need_swap = 0; /**************************************************************************** * Private constant data ****************************************************************************/ /* This is the list of names of libc and libpthread functions that * do not return. These may require some special handling -- at a * minimum, they must tie up resources that can only be released * when the function returns. */ static const char *const nonreturners[] = { "abort", /* Never returns */ "exit", /* Never returns */ "_exit", /* Never returns */ "longjmp", /* Never returns */ "_longjmp", /* Never returns */ "pthread_exit", /* Never returns */ "siglongjmp", /* Never returns */ NULL }; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: swap16 / swap32 ****************************************************************************/ static uint16_t swap16(uint16_t v) { return need_swap ? (uint16_t)((v >> 8) | (v << 8)) : v; } static uint32_t swap32(uint32_t v) { if (!need_swap) { return v; } return ((v & 0x000000fful) << 24) | ((v & 0x0000ff00ul) << 8) | ((v & 0x00ff0000ul) >> 8) | ((v & 0xff000000ul) >> 24); } /**************************************************************************** * Name: xread * * Description: * Read exactly nbytes at an absolute offset, or die. * ****************************************************************************/ static void xread(int fd, void *buffer, size_t nbytes, off_t offset) { ssize_t nread; if (lseek(fd, offset, SEEK_SET) == (off_t)-1) { fprintf(stderr, "%s: seek to %ld failed: %s\n", elf_filename, (long)offset, strerror(errno)); exit(2); } while (nbytes > 0) { nread = read(fd, buffer, nbytes); if (nread < 0) { if (errno == EINTR) { continue; } fprintf(stderr, "%s: read failed: %s\n", elf_filename, strerror(errno)); exit(2); } else if (nread == 0) { fprintf(stderr, "%s: unexpected end of file\n", elf_filename); exit(2); } buffer = (char *)buffer + nread; nbytes -= nread; } } /**************************************************************************** * Name: load_imports * * Description: * Collect every undefined, non-object symbol from the ELF file, in symbol * table order. * * The selection rule is the upstream one. Symbol typing is not * trustworthy here: imported functions are frequently emitted as * STT_NOTYPE rather than STT_FUNC, while a weakly defined *object* does * show up as an undefined object. So rather than looking for functions, * this takes everything undefined that is not explicitly an object. A * genuinely undefined object would be an error, and is left to the link. * ****************************************************************************/ static void load_imports(void) { struct elf32_ehdr_s ehdr; struct elf32_shdr_s *shdrs; struct elf32_sym_s *syms; char *strtab; int wanted = dsyms ? SHT_DYNSYM : SHT_SYMTAB; int symidx = -1; size_t nsyms; size_t strsize; size_t i; uint16_t probe; int host_le; int obj_le; int fd; fd = open(elf_filename, O_RDONLY); if (fd < 0) { fprintf(stderr, "%s: cannot open: %s\n", elf_filename, strerror(errno)); exit(2); } xread(fd, &ehdr, sizeof(ehdr), 0); if (memcmp(ehdr.e_ident, "\177ELF", 4) != 0) { fprintf(stderr, "%s: not an ELF file\n", elf_filename); exit(2); } if (ehdr.e_ident[4] != ELFCLASS32) { fprintf(stderr, "%s: not a 32-bit ELF file\n", elf_filename); exit(2); } /* Decide whether the host and the object disagree about byte order */ probe = 1; host_le = *(const unsigned char *)&probe; obj_le = (ehdr.e_ident[5] == ELFDATA2LSB); need_swap = (host_le != obj_le); /* Re-read the fields that mattered now that byte order is known */ ehdr.e_shoff = swap32(ehdr.e_shoff); ehdr.e_shnum = swap16(ehdr.e_shnum); ehdr.e_shentsize = swap16(ehdr.e_shentsize); if (ehdr.e_shnum == 0 || ehdr.e_shentsize != sizeof(struct elf32_shdr_s)) { fprintf(stderr, "%s: no usable section header table\n", elf_filename); exit(2); } shdrs = malloc((size_t)ehdr.e_shnum * sizeof(struct elf32_shdr_s)); if (shdrs == NULL) { fprintf(stderr, "Failed to allocate section headers\n"); exit(3); } xread(fd, shdrs, (size_t)ehdr.e_shnum * sizeof(struct elf32_shdr_s), ehdr.e_shoff); for (i = 0; i < ehdr.e_shnum; i++) { shdrs[i].sh_type = swap32(shdrs[i].sh_type); shdrs[i].sh_offset = swap32(shdrs[i].sh_offset); shdrs[i].sh_size = swap32(shdrs[i].sh_size); shdrs[i].sh_link = swap32(shdrs[i].sh_link); shdrs[i].sh_entsize = swap32(shdrs[i].sh_entsize); if ((int)shdrs[i].sh_type == wanted && symidx < 0) { symidx = (int)i; } } if (symidx < 0) { fprintf(stderr, "%s: no %s section\n", elf_filename, dsyms ? "dynamic symbol table" : "symbol table"); exit(2); } if (shdrs[symidx].sh_entsize != sizeof(struct elf32_sym_s)) { fprintf(stderr, "%s: unexpected symbol entry size\n", elf_filename); exit(2); } nsyms = shdrs[symidx].sh_size / sizeof(struct elf32_sym_s); syms = malloc(shdrs[symidx].sh_size); if (syms == NULL) { fprintf(stderr, "Failed to allocate symbol table\n"); exit(3); } xread(fd, syms, shdrs[symidx].sh_size, shdrs[symidx].sh_offset); /* The linked string table holds the names */ if (shdrs[symidx].sh_link >= ehdr.e_shnum) { fprintf(stderr, "%s: symbol table has no string table\n", elf_filename); exit(2); } strsize = shdrs[shdrs[symidx].sh_link].sh_size; strtab = malloc(strsize + 1); if (strtab == NULL) { fprintf(stderr, "Failed to allocate string table\n"); exit(3); } xread(fd, strtab, strsize, shdrs[shdrs[symidx].sh_link].sh_offset); strtab[strsize] = '\0'; close(fd); imports = calloc(nsyms + 1, sizeof(struct import_s)); if (imports == NULL) { fprintf(stderr, "Failed to allocate import table\n"); exit(3); } /* The ABI marker goes first, so that every module has at least one * import and the loader can tell what it was built for. It is a marker * rather than a real import: nothing calls it and no board exports it -- * the loader matches it by name and skips resolution. */ imports[0].name = NXFLAT_ABI_SYMBOL; imports[0].is_object = 0; imports[0].is_weak = 0; number_undefined = 1; for (i = 0; i < nsyms; i++) { uint32_t st_name = swap32(syms[i].st_name); uint32_t st_value = swap32(syms[i].st_value); uint16_t st_shndx = swap16(syms[i].st_shndx); unsigned char info = syms[i].st_info; if (st_shndx != SHN_UNDEF || st_value != 0 || st_name == 0 || st_name >= strsize) { continue; } if (ELF_ST_TYPE(info) == STT_OBJECT) { /* An undefined object is not something a thunk can stand in * for; leave it to the link to complain. */ continue; } imports[number_undefined].name = &strtab[st_name]; imports[number_undefined].is_object = 0; imports[number_undefined].is_weak = (ELF_ST_BIND(info) == STB_WEAK); number_undefined++; } free(shdrs); free(syms); dbg("Found %ld undefined symbols\n", number_undefined); } /**************************************************************************** * Name: traverse_undefined_functions ****************************************************************************/ static int traverse_undefined_functions(void *arg, namefunc_type fn) { long i; for (i = 0; i < number_undefined; i++) { /* Is it imported as a "weak" symbol? If so, we will process the * symbol only if we were requested to do so from the command line. */ if (imports[i].is_weak && weak_imports == 0) { continue; } if (fn(imports[i].name, arg) != 0) { return 1; } } return 0; } /**************************************************************************** * Name: put_string ****************************************************************************/ static void put_string(int fd, const char *string) { ssize_t bytes_available = strlen(string); ssize_t bytes_written = write(fd, string, bytes_available); if (bytes_written < 0) { fprintf(stderr, "Failed to write %ld bytes of string to output, errno=%d\n", (long)bytes_available, errno); exit(5); } else if (bytes_written != bytes_available) { fprintf(stderr, "Only wrote %ld of %ld bytes of string to output\n", (long)bytes_written, (long)bytes_available); exit(6); } } /**************************************************************************** * Name: does_not_return_name ****************************************************************************/ static int does_not_return_name(const char *func_name) { int i; for (i = 0; nonreturners[i] != NULL; i++) { if (strcmp(func_name, nonreturners[i]) == 0) { return 1; } } return 0; } static int check_nonreturning(const char *func_name, void *arg) { if (does_not_return_name(func_name)) { calls_nonreturning_functions = 1; } return 0; } /**************************************************************************** * Name: put_import_name_strtab / put_dynimport_decl / ... * * Description: * The four emission passes. Each walks the import list in the same * order, so the %04d counters line up across passes. * ****************************************************************************/ static int put_import_name(const char *func_name, void *arg) { char buffer[4096]; int fd = *(int *)arg; snprintf(buffer, sizeof(buffer), thunk->import_name_strtab_format, counter, counter, counter, func_name, counter, counter); put_string(fd, buffer); counter++; return 0; } static int put_dynimport_decl(const char *func_name, void *arg) { char buffer[4096]; int fd = *(int *)arg; snprintf(buffer, sizeof(buffer), thunk->dynimport_decl_format, counter, func_name, counter); put_string(fd, buffer); counter++; return 0; } static int put_dynimport_array(const char *func_name, void *arg) { char buffer[4096]; int fd = *(int *)arg; snprintf(buffer, sizeof(buffer), thunk->dynimport_array_format, counter, func_name, counter, counter, counter); put_string(fd, buffer); counter++; return 0; } static int put_dyncall(const char *func_name, void *arg) { char buffer[4096]; int fd = *(int *)arg; const char *format; if (does_not_return_name(func_name)) { format = thunk->nonreturning_dyncall_format; } else { format = thunk->dyncall_format; } snprintf(buffer, sizeof(buffer), format, func_name, func_name, func_name, func_name, counter, counter, counter, func_name, func_name); put_string(fd, buffer); counter++; return 0; } /**************************************************************************** * Name: show_usage ****************************************************************************/ static void show_usage(void) { fprintf(stderr, "Usage: %s [options] \n\n", program_name); fprintf(stderr, "Where options are one or more of the following. Note\n"); fprintf(stderr, "that a space is always required between the\n"); fprintf(stderr, "option and any following arguments.\n\n"); fprintf(stderr, " -a \n"); fprintf(stderr, " Instruction set of the module: arm or thumb2\n"); fprintf(stderr, " [thumb2]\n"); fprintf(stderr, " -d Use dynamic symbol table. [symtab]\n"); fprintf(stderr, " -o \n"); fprintf(stderr, " Output to [stdout]\n"); fprintf(stderr, " -v Verbose output [no output]\n"); fprintf(stderr, " -w Import weakly declared functions, i.e., weakly\n"); fprintf(stderr, " declared functions are expected to be\n"); fprintf(stderr, " provided at load-time [not imported]\n"); fprintf(stderr, "\n"); exit(1); } /**************************************************************************** * Name: parse_args ****************************************************************************/ static void parse_args(int argc, char **argv) { const char *arch = "thumb2"; int opt; program_name = argv[0]; while ((opt = getopt(argc, argv, "a:do:vw")) != -1) { switch (opt) { case 'a': arch = optarg; break; case 'd': dsyms++; break; case 'o': out_filename = optarg; break; case 'v': verbose++; break; case 'w': weak_imports++; break; default: show_usage(); break; } } if (strcmp(arch, "thumb2") == 0) { thunk = &g_thunk_thumb2; } else if (strcmp(arch, "arm") == 0) { thunk = &g_thunk_arm; } else { fprintf(stderr, "Unrecognized architecture '%s'\n\n", arch); show_usage(); } if (optind >= argc) { fprintf(stderr, "No ELF file provided\n\n"); show_usage(); } elf_filename = argv[optind]; } /**************************************************************************** * Public Functions ****************************************************************************/ int main(int argc, char **argv, char **envp) { int fd = 1; parse_args(argc, argv); if (out_filename != NULL) { fd = open(out_filename, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd < 0) { fprintf(stderr, "Failed to open %s: %s\n", out_filename, strerror(errno)); exit(4); } } load_imports(); traverse_undefined_functions(NULL, check_nonreturning); /* Output the thunk file in the same order the upstream tool used: * prologue, import name string table, the __dyninfo declarations, the * __dyninfo array, then the call thunks. */ put_string(fd, thunk->file_prologue); put_string(fd, thunk->import_prologue); put_string(fd, thunk->import_name_strtab_prologue); counter = 0; traverse_undefined_functions(&fd, put_import_name); put_string(fd, thunk->dynimport_decl_prologue); counter = 0; traverse_undefined_functions(&fd, put_dynimport_decl); put_string(fd, thunk->dynimport_array_prologue); counter = 0; traverse_undefined_functions(&fd, put_dynimport_array); put_string(fd, thunk->dynimport_array_epilogue); put_string(fd, thunk->dyncall_decl_prologue); counter = 0; traverse_undefined_functions(&fd, put_dyncall); put_string(fd, thunk->file_epilogue); if (fd != 1) { close(fd); } return 0; }