Skip to content

Commit 78e4fec

Browse files
committed
tools/fdpic: Add the module build helpers the demo apps use.
apps/examples/fdpicxip is in apps master and its modules/Makefile reads FDPICDIR = $(NUTTX_DIR)/tools/fdpic MODULE_MK = $(FDPICDIR)/nuttx-fdpic.mk EMBED = $(FDPICDIR)/fdpic-embed.py which is not in this tree, so the committed module blobs cannot be regenerated. This adds what that Makefile names. nuttx-fdpic.mk builds a module: compile with the stock arm-none-eabi GCC, which emits correct FDPIC objects for C and C++, then assemble and link with an arm-uclinuxfdpiceabi binutils, which is the only piece a distribution does not carry. build-binutils.sh builds that, and takes about a minute. nuttx-exports.sh turns a built firmware's exec_symtab.c into a symbol list, fdpic-verify.sh checks a module's imports against it, and fdpic-embed.py turns a module into a C header for an image that carries one inside it. crt0.c is the module's own start-up file, the counterpart of arch/<arch>/src/common/crt0.c for a module the in-tree build produces. It walks .init_array and then calls main, so a C++ global object is constructed on the task that runs the module, in that task's group and with that task's data base, rather than on whichever task called the loader. That is why ENTRY is _start rather than main. It does not register the destructors: -fno-use-cxa-atexit puts them in .fini_array, which the loader walks on unload, and registering them here would run each one twice. A shared library is never entered, so ENTRY = 0 leaves crt0 out of the link and its constructors come from dlopen(). init-array.ld goes with crt0. The linker names the bounds of .init_array when it links an executable and not when it links a shared object, because a shared object is expected to be constructed by whoever loaded it. Reading DT_INIT_ARRAY out of _DYNAMIC instead is not an answer: the addresses there are link-time ones, and an FDPIC object cannot translate them, since its two segments move independently and only the loader knows by how much. The fragment uses INSERT, so the built-in script still applies. build-binutils.sh passes MAKEINFO=true. binutils builds its info pages by default, and a host without makeinfo, which macOS is, fails on doc/bfd.info after everything that matters has been built. The tools are host side and nothing in the NuttX build calls them. The loader that runs what they build is proposed separately, so a module built here has nothing to load it yet, and the page says so. Documentation/components/tools/fdpic.rst describes them, under Host Tools, where the tools index picks it up by glob. Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
1 parent c6b349b commit 78e4fec

8 files changed

Lines changed: 905 additions & 0 deletions

File tree

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
=====================================
2+
fdpic: FDPIC module build tooling
3+
=====================================
4+
5+
``tools/fdpic`` is everything needed to build an FDPIC module out of tree. A
6+
module is an ELF shared object whose read-only segment the target maps straight
7+
out of flash and executes in place, while its writable segment is copied to RAM
8+
once per running instance. It links against nothing: libc and everything else
9+
are imported from the firmware's exported symbol table at load time.
10+
11+
These are host tools only. ``apps/examples/fdpicxip`` uses them to rebuild its
12+
committed module blobs, which is what they are in the tree for. The loader that
13+
runs such a module is proposed separately; until it is in, a module built here
14+
has nothing in NuttX to load it.
15+
16+
Contents
17+
========
18+
19+
============================ ==================================================
20+
File Purpose
21+
============================ ==================================================
22+
``nuttx-fdpic.mk`` The module build itself; include it from a
23+
two-line makefile
24+
``fdpic-verify.sh`` Checks a built module's imports resolve against
25+
the firmware
26+
``nuttx-exports.sh`` Turns ``libs/libc/exec_symtab.c`` into a symbol
27+
list
28+
``fdpic-embed.py`` Turns a built module into a C header, for carrying
29+
one inside an image
30+
``build-binutils.sh`` Builds the ``arm-uclinuxfdpiceabi`` binutils, the
31+
one from-source dependency
32+
``crt0.c`` The module start-up file, linked into every
33+
module that is entered
34+
``init-array.ld`` Names the bounds of ``.init_array`` for it
35+
============================ ==================================================
36+
37+
Building a module
38+
=================
39+
40+
A whole module is three lines of makefile beside the source. Taking
41+
``apps/examples/fdpicxip/modules/qsorter.c``, which is a module in its own
42+
right, as the source:
43+
44+
.. code:: makefile
45+
46+
MODULE = qsorter
47+
SRCS = qsorter.c
48+
49+
include /path/to/nuttx/tools/fdpic/nuttx-fdpic.mk
50+
51+
Then:
52+
53+
.. code:: console
54+
55+
$ make NUTTX_DIR=/path/to/nuttx
56+
CC crt0.c
57+
CC qsorter.c
58+
LD qsorter.fdpic
59+
OK qsorter.fdpic: FDPIC, entry 0x2a1, 4 imports resolved
60+
61+
``NUTTX_DIR`` has to be a configured, built tree: the compile needs its headers
62+
and the verify step needs the export table generated into
63+
``libs/libc/exec_symtab.c``.
64+
65+
Toolchain
66+
=========
67+
68+
Two toolchains are involved. The stock ``arm-none-eabi`` compiler does the
69+
compiling -- it emits perfectly good FDPIC objects for both C and C++ -- and
70+
``arm-uclinuxfdpiceabi`` **binutils** does the linking, because
71+
``arm-none-eabi-ld`` cannot produce an FDPIC object at all. So the
72+
from-source dependency is binutils alone, which ``build-binutils.sh`` builds in
73+
about a minute.
74+
75+
Verification runs as part of the default target on purpose: a module that
76+
imports a symbol the firmware does not export links perfectly happily and fails
77+
only once it is on the target, as a bare ``-ENOENT`` that names nothing.
78+
79+
Start-up file
80+
=============
81+
82+
``crt0.c`` is the entry point of a module, the counterpart of
83+
``arch/<arch>/src/common/crt0.c`` for a module the in-tree build produces. It
84+
walks ``.init_array`` and then calls ``main``, so a C++ global object is
85+
constructed on the task that runs the module, in that task's group and with
86+
that task's data base, rather than on whichever task called the loader. It is
87+
why ``ENTRY`` defaults to ``_start`` rather than ``main``.
88+
89+
It does not register the destructors. ``-fno-use-cxa-atexit`` puts them in
90+
``.fini_array``, which the loader walks when the module is unloaded, so
91+
registering them here would run each one twice.
92+
93+
A shared library is never entered, so ``ENTRY = 0`` leaves ``crt0.c`` out of
94+
the link. Its constructors run from ``dlopen()`` instead.
95+
96+
``init-array.ld`` goes with it. The linker names the bounds of
97+
``.init_array`` when it links an executable and not when it links a shared
98+
object, because a shared object is expected to be constructed by whoever
99+
loaded it. Reading ``DT_INIT_ARRAY`` out of ``_DYNAMIC`` instead is not an
100+
answer: the addresses there are link-time ones, and an FDPIC object cannot
101+
translate them, since its two segments move independently and only the
102+
loader knows by how much. The fragment uses ``INSERT``, so the linker's
103+
built-in script still applies.
104+
105+
Modules carried inside an image
106+
===============================
107+
108+
``fdpic-embed.py`` exists for applications that have to load a module before
109+
there is any way to put files on the target, so they embed one and write it out
110+
at run time. ``apps/examples/fdpicxip/modules/`` uses it that way, and is the
111+
worked example of driving this tooling for several modules at once.

tools/fdpic/build-binutils.sh

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#!/bin/bash
2+
############################################################################
3+
# tools/fdpic/build-binutils.sh
4+
#
5+
# SPDX-License-Identifier: Apache-2.0
6+
#
7+
# Licensed to the Apache Software Foundation (ASF) under one or more
8+
# contributor license agreements. See the NOTICE file distributed with
9+
# this work for additional information regarding copyright ownership. The
10+
# ASF licenses this file to you under the Apache License, Version 2.0 (the
11+
# "License"); you may not use this file except in compliance with the
12+
# License. You may obtain a copy of the License at
13+
#
14+
# http://www.apache.org/licenses/LICENSE-2.0
15+
#
16+
# Unless required by applicable law or agreed to in writing, software
17+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
18+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
19+
# License for the specific language governing permissions and limitations
20+
# under the License.
21+
#
22+
############################################################################
23+
24+
# Build arm-uclinuxfdpiceabi binutils -- the only part of the module
25+
# toolchain that has to be built from source.
26+
#
27+
# The compiling is done by the stock Arm bare-metal toolchain, which emits
28+
# correct FDPIC objects for both C and C++. What it cannot do is *link*
29+
# them: arm-none-eabi-ld is configured with the `armelf` emulation alone, so
30+
# it produces an object marked "UNIX - System V" that the loader refuses.
31+
# The FDPIC linker carries armelf_linux_fdpiceabi, and that is the whole of
32+
# the gap.
33+
#
34+
# So this builds binutils and nothing else: about a minute, roughly 23 MB.
35+
# An FDPIC GCC is not needed for any of this.
36+
#
37+
# Usage: build-binutils.sh <workdir> [install-prefix]
38+
#
39+
# Then add <install-prefix>/bin to PATH.
40+
41+
set -e
42+
43+
WORK="${1:?usage: build-binutils.sh <workdir> [prefix]}"
44+
PREFIX="${2:-$WORK/toolchain}"
45+
TARGET=arm-uclinuxfdpiceabi
46+
BINUTILS=binutils-2.43
47+
J="$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 4)"
48+
49+
mkdir -p "$WORK/src" "$WORK/build"
50+
51+
cd "$WORK/src"
52+
[ -d "$BINUTILS" ] || {
53+
curl -fL -O "https://ftp.gnu.org/gnu/binutils/$BINUTILS.tar.xz"
54+
tar xf "$BINUTILS.tar.xz"
55+
}
56+
57+
rm -rf "$WORK/build/binutils"
58+
mkdir -p "$WORK/build/binutils"
59+
cd "$WORK/build/binutils"
60+
61+
# --with-system-zlib because the bundled copy does not compile against the
62+
# macOS SDK headers. Harmless elsewhere.
63+
64+
"$WORK/src/$BINUTILS/configure" \
65+
--target="$TARGET" \
66+
--prefix="$PREFIX" \
67+
--disable-nls \
68+
--disable-werror \
69+
--with-system-zlib
70+
71+
# MAKEINFO=true because binutils builds its info pages by default and a
72+
# host without makeinfo, which macOS is, fails the build on doc/bfd.info
73+
# after everything that matters has already been built.
74+
75+
make -j"$J" MAKEINFO=true
76+
make install MAKEINFO=true
77+
78+
echo
79+
echo "Installed to $PREFIX/bin"
80+
echo
81+
"$PREFIX/bin/$TARGET-ld" -V | head -8
82+
echo
83+
echo "armelf_linux_fdpiceabi in the list above is the one that matters."
84+
echo "Add to PATH: export PATH=$PREFIX/bin:\$PATH"

tools/fdpic/crt0.c

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/****************************************************************************
2+
* tools/fdpic/crt0.c
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*
6+
* Licensed to the Apache Software Foundation (ASF) under one or more
7+
* contributor license agreements. See the NOTICE file distributed with
8+
* this work for additional information regarding copyright ownership. The
9+
* ASF licenses this file to you under the Apache License, Version 2.0 (the
10+
* "License"); you may not use this file except in compliance with the
11+
* License. You may obtain a copy of the License at
12+
*
13+
* http://www.apache.org/licenses/LICENSE-2.0
14+
*
15+
* Unless required by applicable law or agreed to in writing, software
16+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
17+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
18+
* License for the specific language governing permissions and limitations
19+
* under the License.
20+
*
21+
****************************************************************************/
22+
23+
/* The start-up file of an FDPIC module, the counterpart of
24+
* arch/<arch>/src/common/crt0.c for a module that the in-tree build
25+
* produces. A module that exec() runs is entered here rather than at main,
26+
* so that its constructors run on the task that runs the module, in that
27+
* task's group and with that task's data base, rather than on whichever
28+
* task happened to call the loader.
29+
*
30+
* Destructors are not registered here. -fno-use-cxa-atexit puts them in
31+
* .fini_array, which the loader walks in libelf_uninit() when the module is
32+
* unloaded, and registering them again would run each one twice.
33+
*
34+
* A shared library is not entered at all, so it does not link this file.
35+
* Its constructors run from libelf_insert() when dlopen() maps it.
36+
*/
37+
38+
/****************************************************************************
39+
* Included Files
40+
****************************************************************************/
41+
42+
#include <stdint.h>
43+
44+
/****************************************************************************
45+
* Public Function Prototypes
46+
****************************************************************************/
47+
48+
int main(int argc, char *argv[]);
49+
void exit(int status);
50+
51+
/****************************************************************************
52+
* Private Data
53+
****************************************************************************/
54+
55+
/* init-array.ld defines these around .init_array. The linker's built-in
56+
* script does not, when it links a shared object.
57+
*
58+
* They are declared hidden so that the compiler reaches them through the
59+
* GOT of this object rather than through a dynamic symbol, which is what
60+
* lets the loader resolve them with an ordinary R_ARM_RELATIVE.
61+
*
62+
* Each entry is a code address rather than a function descriptor: the
63+
* linker resolves .init_array with R_ARM_RELATIVE too, not with
64+
* R_ARM_FUNCDESC_VALUE.
65+
*/
66+
67+
extern uintptr_t __init_array_start[] __attribute__((visibility("hidden")));
68+
extern uintptr_t __init_array_end[] __attribute__((visibility("hidden")));
69+
70+
/****************************************************************************
71+
* Private Functions
72+
****************************************************************************/
73+
74+
/****************************************************************************
75+
* Name: call_initializer
76+
*
77+
* Description:
78+
* Enter one .init_array entry. A plain call through a function pointer
79+
* cannot be used: under -mfdpic the compiler would take the entry for a
80+
* function descriptor and load a data base out of the code address. The
81+
* data base is right already, because this runs inside the module.
82+
*
83+
****************************************************************************/
84+
85+
static void call_initializer(uintptr_t entry)
86+
{
87+
__asm__ __volatile__
88+
(
89+
"blx %[entry]\n"
90+
:
91+
: [entry] "r" (entry)
92+
: "r0", "r1", "r2", "r3", "r12", "lr", "cc", "memory"
93+
);
94+
}
95+
96+
/****************************************************************************
97+
* Public Functions
98+
****************************************************************************/
99+
100+
/****************************************************************************
101+
* Name: _start
102+
*
103+
* Description:
104+
* The entry point of a module. Runs the constructors, calls main and
105+
* passes its return value to exit().
106+
*
107+
* Input Parameters:
108+
* argc - The number of parameters being passed.
109+
* argv - The parameters being passed.
110+
*
111+
* Returned Value:
112+
* Does not return.
113+
*
114+
****************************************************************************/
115+
116+
void _start(int argc, char *argv[])
117+
{
118+
uintptr_t *entry;
119+
120+
for (entry = __init_array_start; entry != __init_array_end; entry++)
121+
{
122+
call_initializer(*entry);
123+
}
124+
125+
exit(main(argc, argv));
126+
}

0 commit comments

Comments
 (0)