nuttx/Documentation/components/nxflat.rst

492 lines
20 KiB
ReStructuredText
Raw Normal View History

.. _nxflat:
2020-07-20 19:18:26 -03:00
======
NXFLAT
======
Overview
========
Functionality
-------------
NXFLAT is a customized and simplified version of binary format
implemented a few years ago called
`XFLAT <http://xflat.sourceforge.net/>`__ With the NXFLAT binary format
you will be able to do the following:
- Place separately linked programs in a file system, and
- Execute those programs by dynamically linking them to the base NuttX
code.
This allows you to extend the NuttX base code after it has been written
into FLASH. One motivation for implementing NXFLAT is support clean CGI
under an HTTPD server.
This feature is especially attractive when combined with the NuttX ROMFS
support: ROMFS allows you to execute programs in place (XIP) in flash
without copying anything other than the .data section to RAM. In fact,
the initial NXFLAT release only worked on ROMFS. Later extensions also
support execution NXFLAT binaries from an SRAM copy as well.
This NuttX feature includes:
- A dynamic loader that is built into the NuttX core (See
`GIT <https://github.com/apache/nuttx/blob/master/binfmt/>`__).
2020-07-20 19:18:26 -03:00
- Minor changes to RTOS to support position independent code, and
- A linker to bind ELF binaries to produce the NXFLAT binary format
(See GIT).
Background
----------
NXFLAT is derived from `XFLAT <http://xflat.sourceforge.net/>`__. XFLAT
is a toolchain add that provides full shared library and XIP executable
support for processors that have no Memory Management Unit
(MMU:sup:`1`). NXFLAT is greatly simplified for the deeply embedded
environment targeted by NuttX:
2020-07-20 19:18:26 -03:00
- NXFLAT does not support shared libraries, because
- NXFLAT does not support *exportation* of symbol values from a module
Rather, the NXFLAT module only *imports* symbol values. In the NXFLAT
model, the (PIC:sup:`2`) NXFLAT module resides in a FLASH file system
and when it is loaded at run time, it is dynamically linked only to the
(non-PIC) base NuttX code: The base NuttX *exports* a symbol table; the
NXFLAT module *imports* those symbol value to dynamically bind the
module to the base code.
Limitations
-----------
- **XIP-Capable File System (or RAM mapping) Only**:
2020-07-20 19:18:26 -03:00
The current NXFLAT release will work only with either (1) NXFLAT
executable modules residing on a file system that supports execute in
place, or (2) executables residing on other file systems provided that
CONFIG_FS_RAMMAP is defined. This limitation is because the loader depends
on the capability to mmap() the code segment. See the NuttX User Guide for
further information.
2020-07-20 19:18:26 -03:00
NUTTX does not provide any general kind of file mapping capability.
In fact, true file mapping is only possible with MCUs that provide an MMU1.
Without an MMU, file system may support eXecution In Place (XIP) to mimic
file mapping. Two file systems support that kind of XIP execution needed
by NXFLAT: :doc:`ROMFS <filesystem/romfs>`, whose image is built on the
host and is read-only, and :doc:`XIPFS <filesystem/xipfs>`, which is
writable, so a module can be downloaded onto the board at run time and
still be executed out of flash.
2020-07-20 19:18:26 -03:00
It is also possible to simulate file mapping by allocating memory, copying
the NXFLAT binary file into memory, and executing from the copy of the
executable file in RAM. That capability can be enabled with the CONFIG_FS_RAMMAP
configuration option. With that option enabled, NXFLAT will work that kind
of file system but will require copying of all NXFLAT executables to RAM.
- **GCC/ARM/Cortex-M3/4 Only**:
At present, the NXFLAT toolchain is only available for ARM and Cortex-M3/4 (thumb2) targets.
2020-07-20 19:18:26 -03:00
- **Read-Only Data in RAM**:
With older GCC compilers (at least up to 4.3.3), read-only data must
reside in RAM. In code generated by GCC, all data references are
indexed by the PIC2 base register (r9 for the ARM processors).
The includes read-only data (.rodata). Embedded
2020-07-20 19:18:26 -03:00
firmware developers normally like to keep .rodata in FLASH with
the code sections. But because all data is referenced with the
PIC base register, all of that data must lie in RAM. A NXFLAT
change to work around this is under investigation3.
Newer GCC compilers (at least from 4.6.3), read-only data is
no long GOT-relative, but is now accessed PC-relative.
With PC relative addressing, read-only data must reside in the I-Space.
- **Globally Scoped Function Function Pointers**:
If a function pointer is taken to a statically defined function,
then (at least for ARM) GCC will generate a relocation that NXFLAT
cannot handle. The workaround is make all such functions global in
2020-07-20 19:18:26 -03:00
scope. A fix would involve a change to the GCC compiler as described
in Appendix B.
2020-07-20 19:18:26 -03:00
- **Special Handling of Callbacks**:
Callbacks through function pointers must be avoided or, when
then cannot be avoided, handled very specially. The reason
for this is that the PIC module requires setting of a special
value in a PIC register. If the callback does not set the PIC
register, then the called back function will fail because it
will be unable to correctly access data memory. Special logic
is in place to handle some NuttX callbacks: Signal callbacks
and watchdog timer callbacks. But other callbacks (like those
used with qsort() must be avoided in an NXFLAT module.
2020-07-20 19:18:26 -03:00
Supported Processors
--------------------
As mentioned `above <#limitations>`__, the NXFLAT toolchain is only
available for ARM and Cortex-M3 (thumb2) targets. Furthermore, NXFLAT
has only been tested on the Eagle-100 LMS6918 Cortex-M3 board.
Development Status
------------------
The initial release of NXFLAT was made in NuttX version 0.4.9. Testing
is limited to the tests found under ``apps/examples/nxflat`` in the
source tree. Some known problems exist (see the
`TODO <https://github.com/apache/nuttx/blob/master/TODO>`__ list). As
2020-07-20 19:18:26 -03:00
such, NXFLAT is currently in an early alpha phase.
NXFLAT Toolchain
================
Getting the NXFLAT Tools
------------------------
Building an NXFLAT module takes two tools beyond an ordinary GCC toolchain:
``mknxflat``, which generates the module's *thunk* file, and ``ldnxflat``,
which links the module into the NXFLAT binary format.
``mknxflat`` is part of NuttX, in ``tools/nxflat``. The build produces it as
``tools/mknxflat`` whenever ``CONFIG_NXFLAT`` is selected, so there is
nothing to install.
``ldnxflat`` is not part of NuttX and cannot be: it is GPL and derives from
elf2flt. It lives in the NuttX buildroot toolchain, at
`patacongo/buildroot <https://github.com/patacongo/buildroot>`__, under
``toolchain/nxflat``.
Only that one tool is needed. The rest of the buildroot toolchain is not:
an ordinary ``arm-none-eabi`` GCC compiles and links NXFLAT modules, so a
board does not have to select ``CONFIG_ARM_TOOLCHAIN_BUILDROOT`` to use
them.
``ldnxflat`` reads its input through libbfd, so it is built against a
binutils source tree and a binutils build of the same version.
``toolchain/nxflat/Makefile`` takes those as ``BINUTILS_DIR`` and
``BINUTILS_DIR1``, and expects an ``arch`` symbolic link naming the target,
``thumb2`` or ``arm``. Where the binutils build leaves ``libbfd.a`` varies,
so the library path may need adjusting. Put the result on your ``PATH``.
On ARM, ``arch/arm/src/common/Toolchain.defs`` provides both ``MKNXFLAT``
(with the ``-a`` option following ``CONFIG_ARM_THUMB``) and ``LDNXFLAT``, so
a board only has to assign them if it needs something different.
2020-07-20 19:18:26 -03:00
mknxflat
--------
``mknxflat`` is used to build a *thunk* file. See below
for usage::
Usage: mknxflat [options] <elf-filename>
2020-07-20 19:18:26 -03:00
Where options are one or more of the following. Note
that a space is always required between the
option and any following arguments.
2020-07-20 19:18:26 -03:00
-a <arch>
Instruction set of the module: arm or thumb2
[thumb2]
2020-07-20 19:18:26 -03:00
-d Use dynamic symbol table. [symtab]
-o <out-filename>
Output to <out-filename> [stdout]
2020-07-20 19:18:26 -03:00
-v Verbose output [no output]
-w Import weakly declared functions, i.e., weakly
declared functions are expected to be
provided at load-time [not imported]
A module calls an imported function through one of these thunks, which
reaches the import table using the module's PIC base register. The thunks
are emitted as assembly, so ``-a`` must match the instruction set the module
was compiled for.
2020-07-20 19:18:26 -03:00
ldnxflat
--------
``ldnxflat`` is use to link your object files along with the *thunk*
file generated by ``mknxflat`` to produce the NXFLAT
binary module. See below for usage::
Usage: ldnxflat [options] <bfd-filename>
Where options are one or more of the following. Note
that a space is always required between the option and
any following arguments.
-d Use dynamic symbol table [Default: symtab]
-e <entry-point>
Entry point to module [Default: _start]
-o <out-filename>
Output to <out-filename> [Default: <bfd-filename>.nxf]
-s <stack-size>
Set stack size to <stack-size> [Default: 4096]
-v Verbose output. If -v is applied twice, additional
debug output is enabled [Default: no verbose output].
mksymtab
--------
There is a small helper program available in ``nuttx/tools`` call
``mksymtab``. ``mksymtab`` can be sued to generate symbol tables for the
NuttX base code that would be usable by the typical NXFLAT application.
``mksymtab`` builds symbol tables from common-separated value (CSV)
files. In particular, the CSV files:
#. ``nuttx/syscall/syscall.csv`` that describes the NuttX RTOS
interface, and
#. ``nuttx/libc/libc.csv`` that describes the NuttX C library interface.
2025-04-29 04:29:56 +02:00
#. ``nuttx/libc/math.cvs`` that describes any math library.
2020-07-20 19:18:26 -03:00
::
USAGE: ./mksymtab <cvs-file> <symtab-file>
Where:
<cvs-file> : The path to the input CSV file
<symtab-file>: The path to the output symbol table file
-d : Enable debug output
For example,
::
cd nuttx/tools
cat ../syscall/syscall.csv ../libc/libc.csv | sort >tmp.csv
./mksymtab.exe tmp.csv tmp.c
Making an NXFLAT module
-----------------------
Below is a snippet from an NXFLAT make file (simplified from NuttX
`Hello,
World! <https://github.com/apache/nuttx-apps/blob/master/examples/nxflat/tests/hello/Makefile>`__
2020-07-20 19:18:26 -03:00
example).
* Target 1:
.. code-block:: makefile
hello.r1: hello.o
abc-nuttx-elf-ld -r -d -warn-common -o $@ $^
* Target 2:
.. code-block:: makefile
2020-07-20 19:18:26 -03:00
hello-thunk.S: hello.r1
$(MKNXFLAT) -o $@ $^
2020-07-20 19:18:26 -03:00
* Target 3:
.. code-block:: makefile
hello.r2: hello-thunk.S
2020-07-20 19:18:26 -03:00
abc-nuttx-elf-ld -r -d -warn-common -T binfmt/libnxflat/gnu-nxflat-gotoff.ld -no-check-sections -o $@ hello.o hello-thunk.o
* Target 4:
.. code-block:: makefile
2020-07-20 19:18:26 -03:00
hello: hello.r2
$(LDNXFLAT) -e main -s 2048 -o $@ $^
2020-07-20 19:18:26 -03:00
**Target 1**. This target links all of the module's object files
together into one relocatable object. Two relocatable objects will be
generated; this is the first one (hence, the suffic ``.r1``). In this
"Hello, World!" case, there is only a single object file, ``hello.o``,
that is linked to produce the ``hello.r1`` object.
When the module's object files are compiled, some special compiler
CFLAGS must be provided. First, the option ``-fpic`` is required to tell
the compiler to generate position independent code (other GCC options,
like ``-fno-jump-tables`` might also be desirable). For ARM compilers,
two additional compilation options are required: ``-msingle-pic-base``
!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-02 21:30:02 +02:00
and ``-mpic-register=r9``. On ARM these are supplied centrally rather
arch/arm: Reserve r10 via ARCHCFLAGS and hoist the PIC module flags. Toolchain.defs adds --fixed-r10 to CFLAGS under CONFIG_PIC, but nearly every board Make.defs includes that file and then assigns CFLAGS := $(ARCHCFLAGS) $(ARCHOPTIMIZATION) $(ARCHCPUFLAGS) ... with ':=', which discards it. 266 of the 269 ARM board files assign CFLAGS that way; the remaining three delegate to a shared makefile that does the same thing. The flag is what keeps the base firmware from allocating r10, the register a PIC module reaches its own data through. Losing it is silent and the symptom is remote from the cause: the build succeeds, and only a callback from base firmware into module code -- qsort() with a module comparison function is the standard case -- misbehaves, reading its data through a register the firmware has since felt free to reuse. Adding it to ARCHCFLAGS puts it on the far side of that ':=', which re-expands ARCHCFLAGS, so every board picks it up with no board changes at all. A module is the other side of the --fixed-r10 contract: it gets r10 via -mpic-register=r10, and GCC rejects both on one command line with "unable to use 'r10' for PIC register". Every ARM board carried the same three lines to set that up: ARCHPICFLAGS = -fpic -msingle-pic-base -mpic-register=r10 CPICFLAGS = $(ARCHPICFLAGS) $(CFLAGS) CXXPICFLAGS = $(ARCHPICFLAGS) $(CXXFLAGS) They move to arch/arm/src/common/Toolchain.defs, which also filters --fixed-r10 back out of CPICFLAGS, CXXPICFLAGS, CELFFLAGS and CXXELFFLAGS in one place instead of each board having to know about the interaction. ARCHPICFLAGS uses '?=' and the derived flags use deferred '=', so a board can still override or append after including the file, and CFLAGS is whatever the board finally set it to. Two boards keep a definition because they genuinely differ: am67 adds -ffixed-r10 and tiva conditionally adds -mno-pic-data-is-text-relative. tlsr82 previously appended -fpic to an unset variable, so only -fpic ever reached its compiler; it now inherits the standard set, verified against tc32-elf-gcc 4.5.1.tc32-elf-1.5 (Telink TC32 v2.0), whose ARM-derived backend honors -msingle-pic-base and -mpic-register=r10. The mps2, mps3, qemu-armv7a, qemu-armv7r, fvp and mcx-nxxx families referenced ARCHPICFLAGS without ever defining it, so their CPICFLAGS carried no PIC flags at all; they get the standard set too. Also adds the missing space in CXXELFFLAGS = $(CXXFLAGS)-fvisibility=hidden -mlong-calls which ran the last token of CXXFLAGS into -fvisibility=hidden, yielding a single malformed token such as -DNDEBUG-fvisibility=hidden. Also exempts the pre-existing "*.siz" gsize comment in Toolchain.defs from codespell, which reads "siz" as a misspelling and fires for any patch touching the file, since checkpatch scans whole files rather than changed lines. Before, with CONFIG_PIC=y: CFLAGS has --fixed-r10: NO After: CFLAGS has --fixed-r10: YES CPICFLAGS/CELFFLAGS: filtered out Assisted-by: Claude Code:claude-opus-4-8 Assisted-by: Claude Code:claude-fable-5 Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
2026-07-24 13:17:12 +02:00
than per board; see `Where the ARM PIC flags come from`_ below.
2020-07-20 19:18:26 -03:00
**Target 2**. Given the ``hello.r1`` relocatable object, this target
will invoke ```mknxflat`` <#mknxflat>`__ to make the *thunk* file,
``hello-thunk.S``. This *thunk* file contains all of the information
needed to create the imported function list.
**Target 3** This target is similar to **Target 1**. In this case, it
will link together the module's object files (only ``hello.o`` here)
along with the assembled *thunk* file, ``hello-thunk.o`` to create the
second relocatable object, ``hello.r2``. The linker script,
``gnu-nxflat-gotoff.ld`` is required at this point to correctly position
the sections. This linker script produces two segments: An *I-Space*
(Instruction Space) segment containing mostly ``.text`` and a *D-Space*
(Data Space) segment containing ``.got``, ``.data``, and ``.bss``
sections. The I-Space section must be origined at address 0 (so that the
segment's addresses are really offsets into the I-Space segment) and the
D-Space section must also be origined at address 0 (so that segment's
addresses are really offsets into the I-Space segment). The option
``-no-check-sections`` is required to prevent the linker from failing
because these segments overlap.
**NOTE:** There are two linker scripts located at ``binfmt/libnxflat/``.
#. ``binfmt/libnxflat/gnu-nxflat-gotoff.ld``. Older versions of GCC
(at least up to GCC 4.3.3), use GOT-relative addressing to access RO
data. In that case, read-only data (.rodata) must reside in D-Space
and this linker script should be used.
#. ``binfmt/libnxflat/gnu-nxflat-pcrel.ld``. Newer versions of GCC
(at least as of GCC 4.6.3), use PC-relative addressing to access RO
data. In that case, read-only data (.rodata) must reside in I-Space
and this linker script should be used.
**Target 4**. Finally, this target will use the ``hello.r2`` relocatable
object to create the final, NXFLAT module ``hello`` by executing
``ldnxflat``.
**binfmt Registration** NXFLAT calls :c:func:`register_binfmt` to
incorporate itself into the system.
2020-07-20 19:18:26 -03:00
Module ABI marker
-----------------
The NXFLAT header carries no version field -- ``h_magic`` is written by
``ldnxflat``, which lives outside this repository and cannot be changed in
step with the loader -- so the module ABI is declared through the import
table instead. Every module built by ``mknxflat`` imports the symbol
``__nxflat_abi_v2``, the base firmware defines it, and the loader refuses a
module whose import table does not name it. The generation is bumped
whenever the module ABI changes; v2 is the move of the PIC base register to
r9, and a v1 module's thunks would add r10 and branch to a wild address on
the first call into the base firmware.
A module built before the marker therefore fails ``exec()`` with
``ENOEXEC``, and with binfmt debug output enabled the loader reports::
ERROR: Module does not declare __nxflat_abi_v2: it was built by a
toolchain predating the r9 PIC base register. Rebuild it.
Rebuilding the module with the current ``mknxflat`` is the entire fix;
nothing in the module source has to change. A module built against a newer
ABI than the firmware fails the other way, with ``Exported symbol
"__nxflat_abi_vN" not found`` and ``ENOENT``.
The marker resolves like any other import, so the exported symbol table
passed to ``exec()`` has to contain it. A table generated from the modules'
thunk files, the way ``apps/tools/mksymtab.sh`` generates one, picks it up
automatically; a hand-written table has to list it.
arch/arm: Reserve r10 via ARCHCFLAGS and hoist the PIC module flags. Toolchain.defs adds --fixed-r10 to CFLAGS under CONFIG_PIC, but nearly every board Make.defs includes that file and then assigns CFLAGS := $(ARCHCFLAGS) $(ARCHOPTIMIZATION) $(ARCHCPUFLAGS) ... with ':=', which discards it. 266 of the 269 ARM board files assign CFLAGS that way; the remaining three delegate to a shared makefile that does the same thing. The flag is what keeps the base firmware from allocating r10, the register a PIC module reaches its own data through. Losing it is silent and the symptom is remote from the cause: the build succeeds, and only a callback from base firmware into module code -- qsort() with a module comparison function is the standard case -- misbehaves, reading its data through a register the firmware has since felt free to reuse. Adding it to ARCHCFLAGS puts it on the far side of that ':=', which re-expands ARCHCFLAGS, so every board picks it up with no board changes at all. A module is the other side of the --fixed-r10 contract: it gets r10 via -mpic-register=r10, and GCC rejects both on one command line with "unable to use 'r10' for PIC register". Every ARM board carried the same three lines to set that up: ARCHPICFLAGS = -fpic -msingle-pic-base -mpic-register=r10 CPICFLAGS = $(ARCHPICFLAGS) $(CFLAGS) CXXPICFLAGS = $(ARCHPICFLAGS) $(CXXFLAGS) They move to arch/arm/src/common/Toolchain.defs, which also filters --fixed-r10 back out of CPICFLAGS, CXXPICFLAGS, CELFFLAGS and CXXELFFLAGS in one place instead of each board having to know about the interaction. ARCHPICFLAGS uses '?=' and the derived flags use deferred '=', so a board can still override or append after including the file, and CFLAGS is whatever the board finally set it to. Two boards keep a definition because they genuinely differ: am67 adds -ffixed-r10 and tiva conditionally adds -mno-pic-data-is-text-relative. tlsr82 previously appended -fpic to an unset variable, so only -fpic ever reached its compiler; it now inherits the standard set, verified against tc32-elf-gcc 4.5.1.tc32-elf-1.5 (Telink TC32 v2.0), whose ARM-derived backend honors -msingle-pic-base and -mpic-register=r10. The mps2, mps3, qemu-armv7a, qemu-armv7r, fvp and mcx-nxxx families referenced ARCHPICFLAGS without ever defining it, so their CPICFLAGS carried no PIC flags at all; they get the standard set too. Also adds the missing space in CXXELFFLAGS = $(CXXFLAGS)-fvisibility=hidden -mlong-calls which ran the last token of CXXFLAGS into -fvisibility=hidden, yielding a single malformed token such as -DNDEBUG-fvisibility=hidden. Also exempts the pre-existing "*.siz" gsize comment in Toolchain.defs from codespell, which reads "siz" as a misspelling and fires for any patch touching the file, since checkpatch scans whole files rather than changed lines. Before, with CONFIG_PIC=y: CFLAGS has --fixed-r10: NO After: CFLAGS has --fixed-r10: YES CPICFLAGS/CELFFLAGS: filtered out Assisted-by: Claude Code:claude-opus-4-8 Assisted-by: Claude Code:claude-fable-5 Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
2026-07-24 13:17:12 +02:00
Where the ARM PIC flags come from
---------------------------------
On ARM the compilation flags described under **Target 1** are supplied by
``arch/arm/src/common/Toolchain.defs``, not by each board. A board only has
to say something when it differs from the default::
!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-02 21:30:02 +02:00
ARCHPICFLAGS ?= -fpic -msingle-pic-base -mpic-register=r9
arch/arm: Reserve r10 via ARCHCFLAGS and hoist the PIC module flags. Toolchain.defs adds --fixed-r10 to CFLAGS under CONFIG_PIC, but nearly every board Make.defs includes that file and then assigns CFLAGS := $(ARCHCFLAGS) $(ARCHOPTIMIZATION) $(ARCHCPUFLAGS) ... with ':=', which discards it. 266 of the 269 ARM board files assign CFLAGS that way; the remaining three delegate to a shared makefile that does the same thing. The flag is what keeps the base firmware from allocating r10, the register a PIC module reaches its own data through. Losing it is silent and the symptom is remote from the cause: the build succeeds, and only a callback from base firmware into module code -- qsort() with a module comparison function is the standard case -- misbehaves, reading its data through a register the firmware has since felt free to reuse. Adding it to ARCHCFLAGS puts it on the far side of that ':=', which re-expands ARCHCFLAGS, so every board picks it up with no board changes at all. A module is the other side of the --fixed-r10 contract: it gets r10 via -mpic-register=r10, and GCC rejects both on one command line with "unable to use 'r10' for PIC register". Every ARM board carried the same three lines to set that up: ARCHPICFLAGS = -fpic -msingle-pic-base -mpic-register=r10 CPICFLAGS = $(ARCHPICFLAGS) $(CFLAGS) CXXPICFLAGS = $(ARCHPICFLAGS) $(CXXFLAGS) They move to arch/arm/src/common/Toolchain.defs, which also filters --fixed-r10 back out of CPICFLAGS, CXXPICFLAGS, CELFFLAGS and CXXELFFLAGS in one place instead of each board having to know about the interaction. ARCHPICFLAGS uses '?=' and the derived flags use deferred '=', so a board can still override or append after including the file, and CFLAGS is whatever the board finally set it to. Two boards keep a definition because they genuinely differ: am67 adds -ffixed-r10 and tiva conditionally adds -mno-pic-data-is-text-relative. tlsr82 previously appended -fpic to an unset variable, so only -fpic ever reached its compiler; it now inherits the standard set, verified against tc32-elf-gcc 4.5.1.tc32-elf-1.5 (Telink TC32 v2.0), whose ARM-derived backend honors -msingle-pic-base and -mpic-register=r10. The mps2, mps3, qemu-armv7a, qemu-armv7r, fvp and mcx-nxxx families referenced ARCHPICFLAGS without ever defining it, so their CPICFLAGS carried no PIC flags at all; they get the standard set too. Also adds the missing space in CXXELFFLAGS = $(CXXFLAGS)-fvisibility=hidden -mlong-calls which ran the last token of CXXFLAGS into -fvisibility=hidden, yielding a single malformed token such as -DNDEBUG-fvisibility=hidden. Also exempts the pre-existing "*.siz" gsize comment in Toolchain.defs from codespell, which reads "siz" as a misspelling and fires for any patch touching the file, since checkpatch scans whole files rather than changed lines. Before, with CONFIG_PIC=y: CFLAGS has --fixed-r10: NO After: CFLAGS has --fixed-r10: YES CPICFLAGS/CELFFLAGS: filtered out Assisted-by: Claude Code:claude-opus-4-8 Assisted-by: Claude Code:claude-fable-5 Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
2026-07-24 13:17:12 +02:00
!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-02 21:30:02 +02:00
CPICFLAGS = $(ARCHPICFLAGS) $(filter-out --fixed-r9,$(CFLAGS))
CXXPICFLAGS = $(ARCHPICFLAGS) $(filter-out --fixed-r9,$(CXXFLAGS))
arch/arm: Reserve r10 via ARCHCFLAGS and hoist the PIC module flags. Toolchain.defs adds --fixed-r10 to CFLAGS under CONFIG_PIC, but nearly every board Make.defs includes that file and then assigns CFLAGS := $(ARCHCFLAGS) $(ARCHOPTIMIZATION) $(ARCHCPUFLAGS) ... with ':=', which discards it. 266 of the 269 ARM board files assign CFLAGS that way; the remaining three delegate to a shared makefile that does the same thing. The flag is what keeps the base firmware from allocating r10, the register a PIC module reaches its own data through. Losing it is silent and the symptom is remote from the cause: the build succeeds, and only a callback from base firmware into module code -- qsort() with a module comparison function is the standard case -- misbehaves, reading its data through a register the firmware has since felt free to reuse. Adding it to ARCHCFLAGS puts it on the far side of that ':=', which re-expands ARCHCFLAGS, so every board picks it up with no board changes at all. A module is the other side of the --fixed-r10 contract: it gets r10 via -mpic-register=r10, and GCC rejects both on one command line with "unable to use 'r10' for PIC register". Every ARM board carried the same three lines to set that up: ARCHPICFLAGS = -fpic -msingle-pic-base -mpic-register=r10 CPICFLAGS = $(ARCHPICFLAGS) $(CFLAGS) CXXPICFLAGS = $(ARCHPICFLAGS) $(CXXFLAGS) They move to arch/arm/src/common/Toolchain.defs, which also filters --fixed-r10 back out of CPICFLAGS, CXXPICFLAGS, CELFFLAGS and CXXELFFLAGS in one place instead of each board having to know about the interaction. ARCHPICFLAGS uses '?=' and the derived flags use deferred '=', so a board can still override or append after including the file, and CFLAGS is whatever the board finally set it to. Two boards keep a definition because they genuinely differ: am67 adds -ffixed-r10 and tiva conditionally adds -mno-pic-data-is-text-relative. tlsr82 previously appended -fpic to an unset variable, so only -fpic ever reached its compiler; it now inherits the standard set, verified against tc32-elf-gcc 4.5.1.tc32-elf-1.5 (Telink TC32 v2.0), whose ARM-derived backend honors -msingle-pic-base and -mpic-register=r10. The mps2, mps3, qemu-armv7a, qemu-armv7r, fvp and mcx-nxxx families referenced ARCHPICFLAGS without ever defining it, so their CPICFLAGS carried no PIC flags at all; they get the standard set too. Also adds the missing space in CXXELFFLAGS = $(CXXFLAGS)-fvisibility=hidden -mlong-calls which ran the last token of CXXFLAGS into -fvisibility=hidden, yielding a single malformed token such as -DNDEBUG-fvisibility=hidden. Also exempts the pre-existing "*.siz" gsize comment in Toolchain.defs from codespell, which reads "siz" as a misspelling and fires for any patch touching the file, since checkpatch scans whole files rather than changed lines. Before, with CONFIG_PIC=y: CFLAGS has --fixed-r10: NO After: CFLAGS has --fixed-r10: YES CPICFLAGS/CELFFLAGS: filtered out Assisted-by: Claude Code:claude-opus-4-8 Assisted-by: Claude Code:claude-fable-5 Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
2026-07-24 13:17:12 +02:00
``ARCHPICFLAGS`` uses ``?=``, and the two derived variables use deferred
``=``, so a board that includes this file may still override
``ARCHPICFLAGS`` afterwards or append to it, and ``CFLAGS`` is whatever the
board finally set it to. One board does differ: lm3s6965-ek conditionally
appends ``-mno-pic-data-is-text-relative``.
arch/arm: Reserve r10 via ARCHCFLAGS and hoist the PIC module flags. Toolchain.defs adds --fixed-r10 to CFLAGS under CONFIG_PIC, but nearly every board Make.defs includes that file and then assigns CFLAGS := $(ARCHCFLAGS) $(ARCHOPTIMIZATION) $(ARCHCPUFLAGS) ... with ':=', which discards it. 266 of the 269 ARM board files assign CFLAGS that way; the remaining three delegate to a shared makefile that does the same thing. The flag is what keeps the base firmware from allocating r10, the register a PIC module reaches its own data through. Losing it is silent and the symptom is remote from the cause: the build succeeds, and only a callback from base firmware into module code -- qsort() with a module comparison function is the standard case -- misbehaves, reading its data through a register the firmware has since felt free to reuse. Adding it to ARCHCFLAGS puts it on the far side of that ':=', which re-expands ARCHCFLAGS, so every board picks it up with no board changes at all. A module is the other side of the --fixed-r10 contract: it gets r10 via -mpic-register=r10, and GCC rejects both on one command line with "unable to use 'r10' for PIC register". Every ARM board carried the same three lines to set that up: ARCHPICFLAGS = -fpic -msingle-pic-base -mpic-register=r10 CPICFLAGS = $(ARCHPICFLAGS) $(CFLAGS) CXXPICFLAGS = $(ARCHPICFLAGS) $(CXXFLAGS) They move to arch/arm/src/common/Toolchain.defs, which also filters --fixed-r10 back out of CPICFLAGS, CXXPICFLAGS, CELFFLAGS and CXXELFFLAGS in one place instead of each board having to know about the interaction. ARCHPICFLAGS uses '?=' and the derived flags use deferred '=', so a board can still override or append after including the file, and CFLAGS is whatever the board finally set it to. Two boards keep a definition because they genuinely differ: am67 adds -ffixed-r10 and tiva conditionally adds -mno-pic-data-is-text-relative. tlsr82 previously appended -fpic to an unset variable, so only -fpic ever reached its compiler; it now inherits the standard set, verified against tc32-elf-gcc 4.5.1.tc32-elf-1.5 (Telink TC32 v2.0), whose ARM-derived backend honors -msingle-pic-base and -mpic-register=r10. The mps2, mps3, qemu-armv7a, qemu-armv7r, fvp and mcx-nxxx families referenced ARCHPICFLAGS without ever defining it, so their CPICFLAGS carried no PIC flags at all; they get the standard set too. Also adds the missing space in CXXELFFLAGS = $(CXXFLAGS)-fvisibility=hidden -mlong-calls which ran the last token of CXXFLAGS into -fvisibility=hidden, yielding a single malformed token such as -DNDEBUG-fvisibility=hidden. Also exempts the pre-existing "*.siz" gsize comment in Toolchain.defs from codespell, which reads "siz" as a misspelling and fires for any patch touching the file, since checkpatch scans whole files rather than changed lines. Before, with CONFIG_PIC=y: CFLAGS has --fixed-r10: NO After: CFLAGS has --fixed-r10: YES CPICFLAGS/CELFFLAGS: filtered out Assisted-by: Claude Code:claude-opus-4-8 Assisted-by: Claude Code:claude-fable-5 Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
2026-07-24 13:17:12 +02:00
!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-02 21:30:02 +02:00
Reserving r9 in the base firmware
---------------------------------
arch/arm: Reserve r10 via ARCHCFLAGS and hoist the PIC module flags. Toolchain.defs adds --fixed-r10 to CFLAGS under CONFIG_PIC, but nearly every board Make.defs includes that file and then assigns CFLAGS := $(ARCHCFLAGS) $(ARCHOPTIMIZATION) $(ARCHCPUFLAGS) ... with ':=', which discards it. 266 of the 269 ARM board files assign CFLAGS that way; the remaining three delegate to a shared makefile that does the same thing. The flag is what keeps the base firmware from allocating r10, the register a PIC module reaches its own data through. Losing it is silent and the symptom is remote from the cause: the build succeeds, and only a callback from base firmware into module code -- qsort() with a module comparison function is the standard case -- misbehaves, reading its data through a register the firmware has since felt free to reuse. Adding it to ARCHCFLAGS puts it on the far side of that ':=', which re-expands ARCHCFLAGS, so every board picks it up with no board changes at all. A module is the other side of the --fixed-r10 contract: it gets r10 via -mpic-register=r10, and GCC rejects both on one command line with "unable to use 'r10' for PIC register". Every ARM board carried the same three lines to set that up: ARCHPICFLAGS = -fpic -msingle-pic-base -mpic-register=r10 CPICFLAGS = $(ARCHPICFLAGS) $(CFLAGS) CXXPICFLAGS = $(ARCHPICFLAGS) $(CXXFLAGS) They move to arch/arm/src/common/Toolchain.defs, which also filters --fixed-r10 back out of CPICFLAGS, CXXPICFLAGS, CELFFLAGS and CXXELFFLAGS in one place instead of each board having to know about the interaction. ARCHPICFLAGS uses '?=' and the derived flags use deferred '=', so a board can still override or append after including the file, and CFLAGS is whatever the board finally set it to. Two boards keep a definition because they genuinely differ: am67 adds -ffixed-r10 and tiva conditionally adds -mno-pic-data-is-text-relative. tlsr82 previously appended -fpic to an unset variable, so only -fpic ever reached its compiler; it now inherits the standard set, verified against tc32-elf-gcc 4.5.1.tc32-elf-1.5 (Telink TC32 v2.0), whose ARM-derived backend honors -msingle-pic-base and -mpic-register=r10. The mps2, mps3, qemu-armv7a, qemu-armv7r, fvp and mcx-nxxx families referenced ARCHPICFLAGS without ever defining it, so their CPICFLAGS carried no PIC flags at all; they get the standard set too. Also adds the missing space in CXXELFFLAGS = $(CXXFLAGS)-fvisibility=hidden -mlong-calls which ran the last token of CXXFLAGS into -fvisibility=hidden, yielding a single malformed token such as -DNDEBUG-fvisibility=hidden. Also exempts the pre-existing "*.siz" gsize comment in Toolchain.defs from codespell, which reads "siz" as a misspelling and fires for any patch touching the file, since checkpatch scans whole files rather than changed lines. Before, with CONFIG_PIC=y: CFLAGS has --fixed-r10: NO After: CFLAGS has --fixed-r10: YES CPICFLAGS/CELFFLAGS: filtered out Assisted-by: Claude Code:claude-opus-4-8 Assisted-by: Claude Code:claude-fable-5 Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
2026-07-24 13:17:12 +02:00
!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-02 21:30:02 +02:00
A module reaches its data through r9, and the base firmware has to leave
arch/arm: Reserve r10 via ARCHCFLAGS and hoist the PIC module flags. Toolchain.defs adds --fixed-r10 to CFLAGS under CONFIG_PIC, but nearly every board Make.defs includes that file and then assigns CFLAGS := $(ARCHCFLAGS) $(ARCHOPTIMIZATION) $(ARCHCPUFLAGS) ... with ':=', which discards it. 266 of the 269 ARM board files assign CFLAGS that way; the remaining three delegate to a shared makefile that does the same thing. The flag is what keeps the base firmware from allocating r10, the register a PIC module reaches its own data through. Losing it is silent and the symptom is remote from the cause: the build succeeds, and only a callback from base firmware into module code -- qsort() with a module comparison function is the standard case -- misbehaves, reading its data through a register the firmware has since felt free to reuse. Adding it to ARCHCFLAGS puts it on the far side of that ':=', which re-expands ARCHCFLAGS, so every board picks it up with no board changes at all. A module is the other side of the --fixed-r10 contract: it gets r10 via -mpic-register=r10, and GCC rejects both on one command line with "unable to use 'r10' for PIC register". Every ARM board carried the same three lines to set that up: ARCHPICFLAGS = -fpic -msingle-pic-base -mpic-register=r10 CPICFLAGS = $(ARCHPICFLAGS) $(CFLAGS) CXXPICFLAGS = $(ARCHPICFLAGS) $(CXXFLAGS) They move to arch/arm/src/common/Toolchain.defs, which also filters --fixed-r10 back out of CPICFLAGS, CXXPICFLAGS, CELFFLAGS and CXXELFFLAGS in one place instead of each board having to know about the interaction. ARCHPICFLAGS uses '?=' and the derived flags use deferred '=', so a board can still override or append after including the file, and CFLAGS is whatever the board finally set it to. Two boards keep a definition because they genuinely differ: am67 adds -ffixed-r10 and tiva conditionally adds -mno-pic-data-is-text-relative. tlsr82 previously appended -fpic to an unset variable, so only -fpic ever reached its compiler; it now inherits the standard set, verified against tc32-elf-gcc 4.5.1.tc32-elf-1.5 (Telink TC32 v2.0), whose ARM-derived backend honors -msingle-pic-base and -mpic-register=r10. The mps2, mps3, qemu-armv7a, qemu-armv7r, fvp and mcx-nxxx families referenced ARCHPICFLAGS without ever defining it, so their CPICFLAGS carried no PIC flags at all; they get the standard set too. Also adds the missing space in CXXELFFLAGS = $(CXXFLAGS)-fvisibility=hidden -mlong-calls which ran the last token of CXXFLAGS into -fvisibility=hidden, yielding a single malformed token such as -DNDEBUG-fvisibility=hidden. Also exempts the pre-existing "*.siz" gsize comment in Toolchain.defs from codespell, which reads "siz" as a misspelling and fires for any patch touching the file, since checkpatch scans whole files rather than changed lines. Before, with CONFIG_PIC=y: CFLAGS has --fixed-r10: NO After: CFLAGS has --fixed-r10: YES CPICFLAGS/CELFFLAGS: filtered out Assisted-by: Claude Code:claude-opus-4-8 Assisted-by: Claude Code:claude-fable-5 Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
2026-07-24 13:17:12 +02:00
that register alone -- otherwise a call *back* from the firmware into module
code arrives with the wrong data base. ``qsort()`` with a comparison
function inside the module is the usual way to meet this. Under
!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-02 21:30:02 +02:00
``CONFIG_PIC`` the firmware is therefore built with ``--fixed-r9``.
arch/arm: Reserve r10 via ARCHCFLAGS and hoist the PIC module flags. Toolchain.defs adds --fixed-r10 to CFLAGS under CONFIG_PIC, but nearly every board Make.defs includes that file and then assigns CFLAGS := $(ARCHCFLAGS) $(ARCHOPTIMIZATION) $(ARCHCPUFLAGS) ... with ':=', which discards it. 266 of the 269 ARM board files assign CFLAGS that way; the remaining three delegate to a shared makefile that does the same thing. The flag is what keeps the base firmware from allocating r10, the register a PIC module reaches its own data through. Losing it is silent and the symptom is remote from the cause: the build succeeds, and only a callback from base firmware into module code -- qsort() with a module comparison function is the standard case -- misbehaves, reading its data through a register the firmware has since felt free to reuse. Adding it to ARCHCFLAGS puts it on the far side of that ':=', which re-expands ARCHCFLAGS, so every board picks it up with no board changes at all. A module is the other side of the --fixed-r10 contract: it gets r10 via -mpic-register=r10, and GCC rejects both on one command line with "unable to use 'r10' for PIC register". Every ARM board carried the same three lines to set that up: ARCHPICFLAGS = -fpic -msingle-pic-base -mpic-register=r10 CPICFLAGS = $(ARCHPICFLAGS) $(CFLAGS) CXXPICFLAGS = $(ARCHPICFLAGS) $(CXXFLAGS) They move to arch/arm/src/common/Toolchain.defs, which also filters --fixed-r10 back out of CPICFLAGS, CXXPICFLAGS, CELFFLAGS and CXXELFFLAGS in one place instead of each board having to know about the interaction. ARCHPICFLAGS uses '?=' and the derived flags use deferred '=', so a board can still override or append after including the file, and CFLAGS is whatever the board finally set it to. Two boards keep a definition because they genuinely differ: am67 adds -ffixed-r10 and tiva conditionally adds -mno-pic-data-is-text-relative. tlsr82 previously appended -fpic to an unset variable, so only -fpic ever reached its compiler; it now inherits the standard set, verified against tc32-elf-gcc 4.5.1.tc32-elf-1.5 (Telink TC32 v2.0), whose ARM-derived backend honors -msingle-pic-base and -mpic-register=r10. The mps2, mps3, qemu-armv7a, qemu-armv7r, fvp and mcx-nxxx families referenced ARCHPICFLAGS without ever defining it, so their CPICFLAGS carried no PIC flags at all; they get the standard set too. Also adds the missing space in CXXELFFLAGS = $(CXXFLAGS)-fvisibility=hidden -mlong-calls which ran the last token of CXXFLAGS into -fvisibility=hidden, yielding a single malformed token such as -DNDEBUG-fvisibility=hidden. Also exempts the pre-existing "*.siz" gsize comment in Toolchain.defs from codespell, which reads "siz" as a misspelling and fires for any patch touching the file, since checkpatch scans whole files rather than changed lines. Before, with CONFIG_PIC=y: CFLAGS has --fixed-r10: NO After: CFLAGS has --fixed-r10: YES CPICFLAGS/CELFFLAGS: filtered out Assisted-by: Claude Code:claude-opus-4-8 Assisted-by: Claude Code:claude-fable-5 Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
2026-07-24 13:17:12 +02:00
That flag goes into ``ARCHCFLAGS`` rather than ``CFLAGS``, because nearly
every board ``Make.defs`` includes ``Toolchain.defs`` and then assigns::
CFLAGS := $(ARCHCFLAGS) $(ARCHOPTIMIZATION) $(ARCHCPUFLAGS) ...
with ``:=``, which discards anything added to ``CFLAGS`` beforehand while
re-expanding ``ARCHCFLAGS``. Losing the flag is silent, and the symptom is
remote from the cause: everything builds, and only a callback into module
code misbehaves.
The two sides of that contract cannot both appear on one command line. A
!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-02 21:30:02 +02:00
module gets r9 through ``-mpic-register=r9``, and GCC rejects it alongside
``--fixed-r9`` with *"unable to use 'r9' for PIC register"*. Since
arch/arm: Reserve r10 via ARCHCFLAGS and hoist the PIC module flags. Toolchain.defs adds --fixed-r10 to CFLAGS under CONFIG_PIC, but nearly every board Make.defs includes that file and then assigns CFLAGS := $(ARCHCFLAGS) $(ARCHOPTIMIZATION) $(ARCHCPUFLAGS) ... with ':=', which discards it. 266 of the 269 ARM board files assign CFLAGS that way; the remaining three delegate to a shared makefile that does the same thing. The flag is what keeps the base firmware from allocating r10, the register a PIC module reaches its own data through. Losing it is silent and the symptom is remote from the cause: the build succeeds, and only a callback from base firmware into module code -- qsort() with a module comparison function is the standard case -- misbehaves, reading its data through a register the firmware has since felt free to reuse. Adding it to ARCHCFLAGS puts it on the far side of that ':=', which re-expands ARCHCFLAGS, so every board picks it up with no board changes at all. A module is the other side of the --fixed-r10 contract: it gets r10 via -mpic-register=r10, and GCC rejects both on one command line with "unable to use 'r10' for PIC register". Every ARM board carried the same three lines to set that up: ARCHPICFLAGS = -fpic -msingle-pic-base -mpic-register=r10 CPICFLAGS = $(ARCHPICFLAGS) $(CFLAGS) CXXPICFLAGS = $(ARCHPICFLAGS) $(CXXFLAGS) They move to arch/arm/src/common/Toolchain.defs, which also filters --fixed-r10 back out of CPICFLAGS, CXXPICFLAGS, CELFFLAGS and CXXELFFLAGS in one place instead of each board having to know about the interaction. ARCHPICFLAGS uses '?=' and the derived flags use deferred '=', so a board can still override or append after including the file, and CFLAGS is whatever the board finally set it to. Two boards keep a definition because they genuinely differ: am67 adds -ffixed-r10 and tiva conditionally adds -mno-pic-data-is-text-relative. tlsr82 previously appended -fpic to an unset variable, so only -fpic ever reached its compiler; it now inherits the standard set, verified against tc32-elf-gcc 4.5.1.tc32-elf-1.5 (Telink TC32 v2.0), whose ARM-derived backend honors -msingle-pic-base and -mpic-register=r10. The mps2, mps3, qemu-armv7a, qemu-armv7r, fvp and mcx-nxxx families referenced ARCHPICFLAGS without ever defining it, so their CPICFLAGS carried no PIC flags at all; they get the standard set too. Also adds the missing space in CXXELFFLAGS = $(CXXFLAGS)-fvisibility=hidden -mlong-calls which ran the last token of CXXFLAGS into -fvisibility=hidden, yielding a single malformed token such as -DNDEBUG-fvisibility=hidden. Also exempts the pre-existing "*.siz" gsize comment in Toolchain.defs from codespell, which reads "siz" as a misspelling and fires for any patch touching the file, since checkpatch scans whole files rather than changed lines. Before, with CONFIG_PIC=y: CFLAGS has --fixed-r10: NO After: CFLAGS has --fixed-r10: YES CPICFLAGS/CELFFLAGS: filtered out Assisted-by: Claude Code:claude-opus-4-8 Assisted-by: Claude Code:claude-fable-5 Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
2026-07-24 13:17:12 +02:00
``CPICFLAGS``, ``CXXPICFLAGS``, ``CELFFLAGS`` and ``CXXELFFLAGS`` all derive
from ``CFLAGS``, the flag is filtered back out where they are defined,
rather than in every board that builds modules.
2020-07-20 19:18:26 -03:00
Appendix A: No GOT Operation
============================
When GCC generate position independent code, new code sections will
appear in your programs. One of these is the GOT (Global Offset Table)
and, in ELF environments, another is the PLT (Procedure Lookup Table.
For example, if your C code generated (ARM) assembly language like this
without PIC:
2020-09-02 16:10:06 -03:00
.. code-block:: asm
2020-07-20 19:18:26 -03:00
ldr r1, .L0 /* Fetch the offset to 'x' */
ldr r0, [r9, r1] /* Load the value of 'x' with PIC offset */
2020-07-20 19:18:26 -03:00
/* ... */
.L0: .word x /* Offset to 'x' */
Then when PIC is enabled (say with the -fpic compiler option), it will
generate code like this:
2020-09-02 16:10:06 -03:00
.. code-block:: asm
2020-07-20 19:18:26 -03:00
ldr r1, .L0 /* Fetch the offset to the GOT entry */
ldr r1, [r9, r1] /* Fetch the (relocated) address of 'x' from the GOT */
2020-07-20 19:18:26 -03:00
ldr r0, [r1, #0] /* Fetch the value of 'x' */
/* ... */
.L1 .word x(GOT) /* Offset to entry in the GOT */
See
`reference <http://xflat.sourceforge.net/NoMMUSharedLibs.html#shlibsgot>`__
Notice that the generates an extra level of indirection through the GOT.
This indirection is not needed by NXFLAT and only adds more RAM usage
and execution time.
NXFLAT (like `XFLAT <http://xflat.sourceforge.net/>`__) can work even
better without the GOT. Patches against older version of GCC exist to
eliminate the GOT indirections. Several are available
`here <http://xflat.cvs.sourceforge.net/viewvc/xflat/xflat/gcc/>`__ if
you are inspired to port them to a new GCC version.
Appendix B: PIC Text Workaround
===============================
There is a problem with the memory model in GCC that prevents it from
being used as you need to use it in the NXFLAT context. The problem is
that GCC PIC model assumes that the executable lies in a flat,
contiguous (virtual) address space like::
Virtual
.text
.got
.data
.bss
!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-02 21:30:02 +02:00
It assumes that the PIC base register (r9 for ARM) points to
2020-07-20 19:18:26 -03:00
the base of ``.text`` so that any address in ``.text``, ``.got``,
``.data``, ``.bss`` can be found with an offset from the same base
address. But that is not the memory arrangement that we need in the XIP
embedded environment. We need two memory regions, one in FLASH
containing shared code and on per task in RAM containing task-specific
data::
Flash RAM
.text .got
.data
.bss
The PIC base register needs to point to the base of the ``.got`` and
only addresses in the ``.got``, ``.data``, and ``.bss`` sections can be
accessed as an offset from the PIC base register. See also this `XFLAT
discussion <http://xflat.cvs.sourceforge.net/viewvc/*checkout*/xflat/xflat/gcc/README?revision=1.1.1.1>`__.
Patches against older version of GCC exist to correct this GCC behavior.
Several are available
`here <http://xflat.cvs.sourceforge.net/viewvc/xflat/xflat/gcc/>`__ if
you are inspired to port them to a new GCC version.