nuttx/tools/nxflat/mknxflat.c
Marco Casaroli abbe0df26d !arch/arm: Use r9 as the PIC base register.
ARM PIC has used r10 as the base register, but the tree has never been
consistent about it.  Toolchain.defs gives CONFIG_BUILD_PIC
-mpic-register=r9 and CONFIG_PIC -mpic-register=r10, twenty-five lines
apart, and arm_initialstate.c sets REG_R9 from inline assembly under one
and REG_PIC under the other, with a comment reading "Set the PIC base
register (probably R10)".  This settles it on r9 for all of PIC: NXFLAT,
ELF PIC and CONFIG_BUILD_PIC alike.

r9 is the right choice rather than an arbitrary one.  It is the AAPCS
platform register, the "static base", and it is what GCC itself picks
for -msingle-pic-base on an EABI target; r10 is the non-EABI default.

It also removes a combination that cannot build today.  Stack checking
adds -ffixed-r10 in armv7-m/Toolchain.defs and armv8-m/Toolchain.defs,
while CONFIG_PIC adds -mpic-register=r10, and GCC rejects the pair with
"unable to use 'r10' for PIC register".  The comment above REG_PIC has
always said the register "can be R9 if stack checking is enabled", but
the definition was unconditionally REG_R10, so it would have named the
wrong register even had the build succeeded.

The thunk generator moves with the firmware.  NXFLAT import stubs had
the register baked in as "add ip,ip,sl", so a module built for r9 would
load and then branch to a wild address on its first call out.  The stubs
now come from NXFLAT_PIC_REG in the in-tree tool, which is built only
when CONFIG_NXFLAT is set, following the
CONFIG_BOARD_ETC_ROMFS_PASSWD_ENABLE precedent in tools/Unix.mk.

That leaves modules built before this change, and they are the reason
for the ABI marker.  The NXFLAT header cannot carry a version: h_magic
is written by ldnxflat, which is GPL, derived from elf2flt, and stays
out of this repository, so it can never be changed in step with the
loader.  The import table can, because both of its ends are in-tree --
mknxflat emits it and nxflat_bindimports() reads it -- and ldnxflat
passes it through untouched.  So every module now imports
__nxflat_abi_v2, the base firmware defines it, and a module that does
not import it is refused.

Making the marker a real exported symbol rather than a name the loader
special-cases is what keeps it out of the build system's way: a board's
symbol table picks it up exactly as it picks up printf, so mksymtab.sh
and its equivalents need no change.  It also gives the reverse direction
a diagnosis for free -- a module built against a newer ABI than its
firmware fails with "Exported symbol __nxflat_abi_v2 not found".

Most of the remaining churn is boards restating a default.  ARCHPICFLAGS
is a "?=" default so that a board only speaks up when it differs, and
twenty-six were assigning the value the default already had.  MKNXFLAT
gets the same treatment: thirteen boards named the same tool, and the
only thing that varies is ARM versus Thumb-2, which falls out of
CONFIG_ARM_THUMB.  LDNXFLAT gains a default too -- it stays an
out-of-tree PATH lookup, but naming it centrally fixes boards that never
assigned it, where it expanded to nothing and handed make a recipe
beginning "-e", whose leading dash make ate as "ignore errors".

The non-ARM boards carrying -mpic-register=r10 lose it: it is an
ARM-only option, reachable only through CPICFLAGS, which is only used to
build NXFLAT modules, and no non-ARM board enables NXFLAT.

Boards keep nothing about PIC flags any more.  ARCHPICFLAGS was set by
sixty-three of them and only ever fed CPICFLAGS, which is only used to
build NXFLAT modules; no board outside arch/arm enables NXFLAT, so every
non-ARM copy was setting a variable nothing read.  Those are removed
rather than moved somewhere more central, which would only make dead
text look load-bearing.  LDNXFLAT goes the same way as MKNXFLAT, for the
same reason: thirteen boards named the same tool that Toolchain.defs now
names once.

One of them was not merely redundant.  am67/t3-gem-o1 asked for
"-mpic-register=r10 -ffixed-r10", which GCC refuses outright with
"unable to use 'r10' for PIC register" -- the very combination the
filter-out machinery in Toolchain.defs exists to prevent.  It has
survived because that board does not build NXFLAT modules, so the flags
are never handed to a compiler.  Renaming the register would have
carried the fault forward unchanged, so the line goes.

Tested on lm3s6965-ek:qemu-nxflat under QEMU, configured and built with
no overrides.  The nxflat example runs the errno, hello and struct
modules with output identical to the same config built from master.
Built with the old out-of-tree thunk generator instead, the same
firmware refuses all three with ENOEXEC rather than locking up in a
HardFault, which is what this change is for.  mps3-an547:picostest,
which is CONFIG_PIC without CONFIG_NXFLAT, builds clean and does not
build the thunk generator.

The .def files pick up two cosmetic changes here alongside the register:
a "Dyanamic" typo that codespell rejects, and a reworded comment in each
thunk_*.c.  Neither appears in the emitted thunk -- both are in C
comments -- so the generated text is still what the upstream tool
produces, modulo the register itself.

BREAKING CHANGE: ARM PIC moves from r10 to r9.  An NXFLAT module built
before this change has r10 baked into its import stubs and will not run
against a firmware carrying it; the two cannot be mixed.  The module is
refused with ENOEXEC rather than branching to a wild address, by way of the
__nxflat_abi_v2 marker described below.

Quick fix: rebuild the module against this tree.  Its source needs no
change.  A board that reserved r10 by hand, or that assigned ARCHPICFLAGS
or MKNXFLAT to restate a default, should drop those assignments; nothing
else is affected, and CONFIG_PIC without CONFIG_NXFLAT needs no action.

Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
2026-08-06 01:38:23 +08:00

767 lines
20 KiB
C

/****************************************************************************
* 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;
}