blob: 467e2daa290fb65bab7ba490ca0db51931d412b3 [file]
/****************************************************************************
* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#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 <elf.h> 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] <elf-filename>\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 <arch>\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 <out-filename>\n");
fprintf(stderr, " Output to <out-filename> [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;
}