Skip to content

tools/nxflat: Place the sections of C++ modules and refuse what NXFLAT cannot run. - #20805

Merged
acassis merged 5 commits into
apache:masterfrom
casaroli:nxflat-tls
Oct 20, 2026
Merged

acassis merged 5 commits into
apache:masterfrom
casaroli:nxflat-tls

Conversation

@casaroli

@casaroli casaroli commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Summary

ldnxflat converted some modules into a broken image with no message. The NXFLAT linker scripts give an address only to the sections that they name. ld -r keeps each member of a section group (a C++ COMDAT group) as a section of its own, and it leaves .init_array, .fini_array, .tdata and .tbss unplaced too. Such a section stays at address zero, on top of .text or .data, and moves the sections that follow it. ldnxflat copied all of them into the image.

Examples: a C++ module with the static thread_local member of a class template has its emutls control objects in two COMDAT groups. ld put both at D-Space 0 and .bss at 0x10, inside .data, and exec() of the module failed. hello++3 of apps/examples/nxflat has its .init_array at D-Space 0 and its .bss at 4, and nothing runs its constructor.

This PR:

  • tools/nxflat/ldnxflat: Refuse a module with .init_array, .fini_array or .preinit_array. Nothing runs these arrays in an NXFLAT module.
  • tools/nxflat/ldnxflat: Place the sections in ldnxflat, in the order of the section headers, data before .bss, and do not use the addresses of the linker script. Group members then get their own place.
  • tools/nxflat/ldnxflat: Refuse native TLS (.tdata, .tbss) with a message that says to use a toolchain that emulates TLS. Before, the module failed at the first TLS relocation with "arm relocation 104 ... is not handled".
  • tools/nxflat/testsuite.sh: Show the message of a failed conversion. The column was empty because ldnxflat does not write "error".
  • Documentation: The NXFLAT page gets two limitations (emulated TLS only, no static constructors) and says that ldnxflat places the sections.

With a toolchain that emulates TLS (arm-none-eabi 13.2, as in CI), __thread and thread_local already work in an NXFLAT module: the module imports __emutls_get_address() like any other function, and its control objects and templates are relocated correctly. The new tests are in apache/nuttx-apps#3880.

Impact

Host tool and documentation only. All modules of apps/examples/nxflat that converted before convert to the same bytes. hello++3 (not built by default) is now refused instead of converted wrong.

Testing

lm3s6965-ek:qemu-nxflat with arm-none-eabi 13.2, CONFIG_HAVE_CXX=y, under qemu-system-arm -M lm3s6965evb, with the tests of apache/nuttx-apps#3880:

module master master + #20723 this PR this PR + #20723
errno, hello, longjmp, mutex, pthread, signal, struct, task PASS PASS PASS PASS
tls, main thread PASS PASS PASS PASS
tls, second thread FAIL PASS FAIL PASS
tlsxx exec() fails, ENOENT exec() fails, ENOENT loads, second thread FAIL PASS

The checks of the second thread pass only when the base code keeps a copy of each __thread variable per thread. libgcc's emutls keeps one copy for all threads; #20723 adds a per-thread copy to libc. With this PR and #20723, both modules pass with both linker scripts (gnu-nxflat-pcrel.ld and gnu-nxflat-gotoff.ld).

tools/nxflat/testsuite.sh (gotoff): all modules convert as before; hello++3 now fails with ".init_array holds static constructors or destructors, which an NXFLAT module cannot run". With arm-none-eabi 15.3 (native TLS), tls fails with ".tbss holds native TLS, which an NXFLAT module cannot use; build it with a toolchain that emulates TLS".

tools/checkpatch.sh -c -u -m -g passes.

An NXFLAT module starts at its main() and has no start-up code, so
nothing runs its .init_array, .fini_array or .preinit_array.  The NXFLAT
linker scripts do not place these sections either.  ld leaves them at
address zero, on top of .data, and ldnxflat copied them there.

hello++3 of apps/examples/nxflat is an example.  Its .init_array is at
D-Space address 0, and its .bss starts at 4, inside .data.  The
constructor pointer overwrites the first word of .data, the static
variables share their addresses with initialized data, and the
constructor never runs.

Refuse such a module and name the section.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
ld -r keeps each member of a section group as a section of its own, and
the NXFLAT linker scripts do not place it.  It stays at address zero, on
top of .text or .data, and the sections that the script places after it
move.  C++ puts inline functions and template instances in COMDAT
groups, so many C++ modules have such sections.

For example, the emutls control objects of a static thread_local member
of a class template are in two groups.  ld put them at D-Space address 0
and .bss at 0x10, so all three overlapped .data.  ldnxflat copied one
over the other with no message.  exec() then failed with ENOENT: the
loader read the name of the first import as "flat_abi_v2".

Place the sections in ldnxflat: in the order of the section headers, each
at its alignment, the data of D-Space before its .bss.  A relocatable ELF
object gives symbols and relocations as offsets in their section, so
nothing else changes.  The modules of apps/examples/nxflat convert to the
same bytes as before.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
A toolchain with native TLS, such as Arm GNU 15.3 for arm-none-eabi,
puts __thread variables in .tdata and .tbss and reaches them with
__tls_get_addr() and R_ARM_TLS_GD32.  NXFLAT has no thread pointer and
no TLS relocations, so such a module cannot work.  ldnxflat refused it
only at the first TLS relocation, with "arm relocation 104 at I-Space
00000118 is not handled", which does not say why.

Refuse a module with a TLS section and say what to do: build it with a
toolchain that emulates TLS.  There, __thread compiles into calls to
__emutls_get_address(), which the module imports like any other
function.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
When ldnxflat failed, the report showed the first line of its output
that holds "error".  ldnxflat does not write that word, so the reason
column stayed empty.  Show the first line of the output instead.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
…FLAT.

Add two limitations to the NXFLAT page: a module can use __thread and
thread_local variables only with emulated TLS, and nothing runs the
static constructors and destructors of a module.  ldnxflat refuses a
module with native TLS sections or with an .init_array or .fini_array.

Also say that ldnxflat places the sections itself, so a section that the
linker script does not name does not overlap another one.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
@github-actions github-actions Bot added Area: Documentation Improvements or additions to documentation Size: S The size of the change in this PR is small labels Oct 19, 2026

@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 :-)

@github-actions

Copy link
Copy Markdown

MemBrowse Memory Report · Explore what changed →

No memory changes detected for:


Catch memory regressions in CI with MemBrowse. Free for open source.

@acassis
acassis merged commit a44a93f into apache:master Oct 20, 2026
54 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: Documentation Improvements or additions to documentation Size: S The size of the change in this PR is small

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants