Skip to content

!arch/arm: Use r9 as the PIC base register, and bring mknxflat in-tree - #19600

Draft
casaroli wants to merge 4 commits into
apache:masterfrom
casaroli:arm-pic-r9
Draft

!arch/arm: Use r9 as the PIC base register, and bring mknxflat in-tree#19600
casaroli wants to merge 4 commits into
apache:masterfrom
casaroli:arm-pic-r9

Conversation

@casaroli

@casaroli casaroli commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Summary

ARM PIC uses r10 as the base register, but the tree has never been consistent about it. arch/arm/src/common/Toolchain.defs gives CONFIG_BUILD_PIC -mpic-register=r9 at line 594 and CONFIG_PIC -mpic-register=r10 at line 620, and arm_initialstate.c sets REG_R9 from inline assembly under one and REG_PIC under the other, with a comment that reads "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 register rather than an arbitrary one. It is the AAPCS platform register, the "static base", and it is what GCC itself selects for -msingle-pic-base on an EABI target; r10 is the non-EABI default.

It also fixes a configuration that cannot build today. Stack checking adds -ffixed-r10 in armv7-m/Toolchain.defs:149 and armv8-m/Toolchain.defs:168 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 if the build had succeeded.

Why the NXFLAT thunk generator comes with it

Moving the register on its own breaks NXFLAT, silently. Import thunks are generated by mknxflat, which had the register baked into its template as add ip,ip,sl, so a module compiled -mpic-register=r9 reaches its data through r9 while its import stubs still add r10, and it branches to a wild address on its first call into the base firmware.

mknxflat lives outside this repository, in the buildroot NXFLAT toolchain, so the kernel and the tool could drift with nothing to detect it. The first commit therefore brings it in-tree as tools/nxflat/, with libbfd replaced by reading the ELF symbol table directly — libbfd was only ever used to open the file and enumerate symbols, and it is GPL, which we cannot depend on. The thunks are then generated from a single NXFLAT_PIC_REG on every build, so the two cannot drift.

That commit changes no output. Against the upstream tool, for both ARM and Thumb-2, with and without -w, over modules exercising the plain, weak and non-returning thunk paths, the generated thunk files are byte-identical.

Modules built before this change

They are refused at load rather than left to crash.

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. Every module now imports __nxflat_abi_v2, the base firmware defines it, and a module that does not import it is refused with -ENOEXEC.

Making the marker a real exported symbol rather than a name the loader special-cases 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 diagnoses the reverse direction for free — a module built against a newer ABI than its firmware fails with Exported symbol "__nxflat_abi_v2" not found.

Impact

Warning

BREAKING CHANGE: ARM PIC moves from r10 to r9. An NXFLAT module built before this has r10 baked into its import stubs and will not run against a firmware carrying it; the loader refuses it with -ENOEXEC rather than branching to a wild address.

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. CONFIG_PIC without CONFIG_NXFLAT needs no action, and configurations with neither are untouched.

Most of the 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; those lines are removed rather than edited. 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; six other RISC-V boards already omitted it.

mknxflat is built only when CONFIG_NXFLAT is set, following the CONFIG_BOARD_ETC_ROMFS_PASSWD_ENABLE precedent in tools/Unix.mk.

Licensing

tools/nxflat/mknxflat.c and the two .def templates are BSD-3-Clause, copyright Gregory Nutt and Cadenux LLC, imported from the NuttX buildroot NXFLAT toolchain with their headers intact. The containing repository's COPYING is GPL-2.0, but these particular files carry their own BSD-3 grant, name only Nutt and Cadenux as copyright holders, and descend from XFLAT's ldelflib rather than from elf2flt. @patacongo for confirmation.

ldnxflat is deliberately not imported. It is GPL-2.0-or-later and derives from elf2flt, with roughly ten copyright holders reaching back to 1996 — it cannot be relicensed and cannot live in an Apache repository. NXFLAT continues to need it from the buildroot toolchain, exactly as today.

Here is the licensing agreement mailing list archive:
https://www.mail-archive.com/dev@nuttx.apache.org/msg14863.html

How to verify this on your board

Help with this is very welcome — §1.14 asks for runtime logs from more than one real architecture, and I have only an RP2350 here.

Any board, ARM or not (two minutes). Build your usual configuration, boot it, and run ostest. That is the whole test for most boards: the only thing that touches them is the PIC flag cleanup, which is a no-op unless the board sets ARCHPICFLAGS or enables NXFLAT. Non-ARM boards lose -mpic-register=r10, which was an ARM-only option reachable only through CPICFLAGS, and CPICFLAGS is only used to build NXFLAT modules — so nothing should change at all.

If your board enables CONFIG_NXFLAT. Build and run a module, for example olimex-lpc1766stk:thttpd-nxflat or apps/examples/nxflat. mknxflat now comes from tools/nxflat and the build makes it for you; ldnxflat is still an out-of-tree tool from buildroot, unchanged.

If you have an NXFLAT module built before this change. Run it without rebuilding. It must be refused, with

nxflat_bindimports: ERROR: Module does not declare __nxflat_abi_v2: it was built by a
toolchain predating the r9 PIC base register.  Rebuild it.

and exec() failing with -ENOEXEC (-8). A crash or a wild branch instead of that message is a bug — please report it. Rebuilding the module is the fix; its source needs no change.

Build logs and ostest output from any architecture are welcome and I will collect them here.

Testing

Host: macOS 26.5.1 on arm64, Arm GNU Toolchain 14.2.Rel1, QEMU 11.0.3.

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 configuration built from master:

* Executing errno      -> Hello, World on stdout / stderr, errno is 2
* Executing hello      -> Hello, world!  It has been said.
* Executing struct     -> End-of-Test.. Exit-ing

The same firmware, with the modules rebuilt by the old out-of-tree generator, refuses all three:

ERROR: exec(errno) failed: 8
ERROR: exec(hello) failed: 8

Before the ABI marker that same combination locked up in a HardFault with no console output, which is what the marker is for.

mps3-an547:picostest, which is CONFIG_PIC without CONFIG_NXFLAT, builds clean and does not build the thunk generator.

tools/checkpatch.sh -f passes on every changed C file and header.

Not covered: ELF PIC has no runtime coverage here. mps3-an547:picostest builds and boots identically to master, but NSH takes no console input in that configuration under QEMU — on master as well — so no module is actually executed. The modules were confirmed statically to use r9 as their PIC base.

An NXFLAT module reaches the base firmware through a "thunk" file: one
assembly stub per imported function, generated by mknxflat.  That tool
has always lived outside this repository, in the NuttX buildroot NXFLAT
toolchain, so building an NXFLAT module needs a separate checkout and a
separate build of a tool that links against libbfd.

libbfd is why it stayed out.  It is GPL, which an Apache project cannot
depend on, and it is awkward to obtain besides -- a stock binutils
install often ships libbfd without the libiberty it needs to link.  But
the dependency was never deep.  mknxflat used libbfd for eight calls,
all of them opening the file and walking the symbol table; it never
relocates or rewrites anything.  That is replaced here by reading the
ELF symbol table directly, which removes the dependency outright and
costs about a hundred lines.

The emitted text is unchanged.  The format strings live in the .def
files, which are carried here byte-for-byte from upstream, and the
selection rule for what becomes a thunk is the upstream one: everything
undefined that is not explicitly an object.  Symbol typing cannot be
trusted for this -- imported functions are routinely emitted as
STT_NOTYPE rather than STT_FUNC, while a weakly defined object does
appear as an undefined object -- so the test is on what a symbol is not.

Upstream chose the instruction set at compile time through an "arch"
symlink pointing at either arm/ or thumb2/.  A symlink cannot be carried
in the repository, and one host binary has to serve boards of both
flavours, since lpc31xx is ARM while lpc17xx, tiva, stm32f1 and rp23xx
are Thumb-2.  That choice becomes a runtime "-a" option.  The "-f"
option, which read further command line arguments from a file, is
dropped; nothing in the tree used it.

This commit changes no output.  Against the upstream tool, for both
architectures, with and without -w, over modules exercising the plain,
weak and non-returning thunk paths, the generated thunk files are
byte-identical.

Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
@github-actions github-actions Bot added Arch: arm Issues related to ARM (32-bit) architecture Size: XL The size of the change in this PR is very large. Consider breaking down the PR into smaller pieces. Area: BINFMT Board: arm Board: hc Board: openrisc Board: risc-v labels Aug 2, 2026
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown

MemBrowse Memory Report

No memory changes detected for:

Comment thread boards/arm/tiva/eagle100/scripts/Make.defs Outdated
Comment thread boards/arm/am67/t3-gem-o1/scripts/Make.defs Outdated
Comment thread boards/arm/cxd56xx/spresense/scripts/Make.defs Outdated
Comment thread boards/hc/m9s12/demo9s12ne64/scripts/Make.defs Outdated
Comment thread binfmt/libnxflat/libnxflat_bind.c
xiaoxiang781216
xiaoxiang781216 previously approved these changes Aug 3, 2026
@casaroli

casaroli commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

@patacongo — a licensing question, since you are the copyright holder.

This PR imports mknxflat into the tree as tools/nxflat/. The reason is in the PR body: moving the ARM PIC base register to r9 breaks NXFLAT silently, because mknxflat bakes the register into the import thunks it generates as add ip,ip,sl, and while the tool lives outside this repository it can drift out of step with the kernel.

The three files taken are mknxflat.c and arm/dyncall_skeleton.def / thumb2/dyncall_skeleton.def from the buildroot NXFLAT toolchain. Each carries a BSD-3-Clause header naming you and Cadenux LLC, and notes that the code derives from XFLAT's ldelflib. The containing repository's COPYING is GPL-2.0, so I would like that confirmed rather than assumed: is the per-file BSD-3 grant the operative licence for those three files, and are you content for them to be imported here with their headers intact?

ldnxflat is deliberately not imported. It carries an explicit GPL-2.0-or-later notice and descends from elf2flt, with roughly ten copyright holders going back to 1996, so it stays where it is and NXFLAT continues to need it from buildroot.

The only substantive change to the imported code is dropping the libbfd dependency, which was used solely to open the file and enumerate symbols; the tool now reads the ELF symbol table directly. The generated thunk files are byte-identical to what the current tool produces, for both ARM and Thumb-2, with and without -w.

xiaoxiang781216
xiaoxiang781216 previously approved these changes Aug 3, 2026
@casaroli

casaroli commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

Related: https://www.mail-archive.com/dev@nuttx.apache.org/msg14863.html

@cederom cederom left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @casaroli lets just wait for Gregory Nutt states agreement on the mailing list to relicense files from BSD to Apache so we can already merge Apache :-)

@patacongo

patacongo commented Aug 3, 2026 via email

Copy link
Copy Markdown
Contributor

@cederom cederom added the breaking change This change requires a mitigation entry in the release notes. label Aug 3, 2026
@cederom

cederom commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

@jerpelea what do you think?? :-)

@patacongo

Copy link
Copy Markdown
Contributor

Cadenus License

Some of the code lists Cadenux LLC as the copyright holder. Cadenux was Linux engineering services company that existed from around 2002-2004. I and a partner were the owners of Cadenux LLC. I was the only partner involved with XFLAT. I grant that software which included the Cadenux license to the Apache Software Foundation.

I did not discuss that with my ex-partner but I think I would have his concurrence and, in any event, I think I do have the authority to grant that code to the ASF even without concurrence.

The tool arrived from the buildroot NXFLAT toolchain under BSD-3-Clause,
jointly copyright Gregory Nutt and Cadenux, LLC.  Gregory Nutt owned
Cadenux and was its only developer on this code, and has agreed to the
conversion, so the six files take the ASF header like the rest of the
NuttX code he donated.  Copyright attribution moves to NOTICE, which is
where the donation put it for everything else of his in the tree.

This covers only what was imported: mknxflat and the thunk skeletons it
emits from.  ldnxflat is the file with an elf2flt lineage, and it is not
here -- it stays out of tree in buildroot, and NuttX keeps calling it as
an external tool.

The .def files also gain their in-tree path on the first line, which the
import had left pointing at the buildroot layout.

Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
@github-actions github-actions Bot removed the breaking change This change requires a mitigation entry in the release notes. label Aug 3, 2026
@casaroli

casaroli commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

I have no issue with the BSD code being converted to pure Apache 2.0 following the ASF headers. Some files were jointly copyrighted with Cadenux LLC which I owned many years ago and I was the only person doing the development. So I am comfortable with all of that. The only concern would be the code that has a some ancient uCLinux influenced logic in it. This is original code but derives from ancient GPL uClinux code in some logical sense (although I am avoiding the word "derived" because of its copyright implications). I think that is only one file (?) If everyone else is comfortable, then I am too. I would wait until people in Europe, especially Alin, have a chance to weigh in, however. Alin did all of the original BSD/GPL to Apache licensing analysis and changes when we first joined the ASF so his opinion carries the greatest weight.

Thanks Greg, and the file you are thinking of is not in this PR.

That is ldnxflat.c: its header says "converted from elf2flt.c" and carries Greg Ungerer's and Phil Blundell's copyrights. It stays out of tree in buildroot, and NuttX keeps calling it as an external tool exactly as today.

What is imported is six files: mknxflat.c, nxflat_thunk.h, thunk_arm.c, thunk_thumb2.c and the two dyncall skeletons. mknxflat.c says "Modified from ldelflib", the code you describe as yours and Cadenux's, and I grepped all six for elf2flt, uClinux, XFLAT and GPL — there is nothing.

I have converted those six to the ASF header in a separate commit, so the change is reviewable on its own and easy to drop or reword. Waiting on Alin before this goes anywhere.

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>
mknxflat is built from tools/nxflat by the NuttX build itself, so the
toolchain section no longer sends the reader to buildroot for it; only
ldnxflat still comes from there.  Bring the mknxflat usage text in line
with the tool, note that MKNXFLAT and LDNXFLAT are supplied by the ARM
Toolchain.defs, and correct the r10 references left in the PIC
descriptions.

Describe the module ABI marker, so that a user whose prebuilt module
starts failing exec() with ENOEXEC finds out that the loader refuses a
module whose import table does not name __nxflat_abi_v2, and that
rebuilding the module is the fix.

Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
@casaroli casaroli changed the title arch/arm: Use r9 as the PIC base register, and bring mknxflat in-tree !arch/arm: Use r9 as the PIC base register, and bring mknxflat in-tree Aug 3, 2026
@casaroli
casaroli marked this pull request as draft August 3, 2026 21:30
@casaroli
casaroli requested a review from cederom August 4, 2026 05:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Arch: arm Issues related to ARM (32-bit) architecture Area: BINFMT Board: arm Board: avr Board: hc Board: openrisc Board: renesas Board: risc-v Board: simulator Board: xtensa Size: XL The size of the change in this PR is very large. Consider breaking down the PR into smaller pieces.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants