mirror of
https://github.com/apache/nuttx.git
synced 2026-08-01 20:28:58 +00:00
Merge 1239a6afbb into 8cec1d01db
This commit is contained in:
commit
65c9105583
38 changed files with 3585 additions and 8 deletions
|
|
@ -47,6 +47,7 @@ ignore-words-list =
|
|||
mot,
|
||||
mis,
|
||||
nexted,
|
||||
nneeded,
|
||||
numer,
|
||||
nwe,
|
||||
oen,
|
||||
|
|
|
|||
81
Documentation/applications/examples/fdpicxip/index.rst
Normal file
81
Documentation/applications/examples/fdpicxip/index.rst
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
================================================
|
||||
``fdpicxip`` FDPIC Executed In Place from XIPFS
|
||||
================================================
|
||||
|
||||
Writes FDPIC modules into a :doc:`XIPFS </components/filesystem/xipfs>` volume
|
||||
at run time, the way a download would, and runs them. Their text is executed
|
||||
directly out of flash and shared between concurrent instances; each instance
|
||||
gets its own data.
|
||||
|
||||
This is the FDPIC counterpart of :doc:`nxflatxip <../nxflatxip/index>`, kept
|
||||
separate from it because the two module formats have little in common beyond
|
||||
running in place, and one program demonstrating both would demonstrate
|
||||
neither clearly. What FDPIC adds over NXFLAT is shared libraries and C++ with
|
||||
global constructors — see :doc:`/components/fdpic`.
|
||||
|
||||
It demonstrates; it does not assert. The assertions live in the ``fdpic`` and
|
||||
``reject`` sections of :doc:`the xipfs test suite </applications/testing/xipfs/index>`.
|
||||
|
||||
Usage::
|
||||
|
||||
fdpicxip [qsort | solib | cxx | jmprel]
|
||||
|
||||
``qsort``
|
||||
The simplest case the loader has: one self-contained module, no library
|
||||
behind it, run as two concurrent instances. This is what runs with no
|
||||
argument::
|
||||
|
||||
nsh> fdpicxip
|
||||
=== FDPIC module executed in place from xipfs ===
|
||||
|
||||
staged qsorter (2768 bytes)
|
||||
extent: block 98 x1, size 2768, flash addr 0x10162000
|
||||
|
||||
spawning two concurrent instances...
|
||||
|
||||
[while running] pins on the shared text = 2
|
||||
|
||||
[inst 1] 11 21 31 41 51 61 71 81
|
||||
[inst 2] 12 22 32 42 52 62 72 82
|
||||
|
||||
[after exit] pins on the shared text = 0
|
||||
|
||||
One copy of the text is mapped in flash and pinned once per instance, which
|
||||
is what stops the defragmenter relocating code that is executing. The two
|
||||
rows differ because each instance has its own data; shared data would show
|
||||
up as interleaved or corrupted output.
|
||||
|
||||
``solib``
|
||||
Adds a shared library, so two objects are mapped and each instance gets its
|
||||
own copy of *both* objects' data. If the library's state were shared, the
|
||||
two totals would interleave instead of each reaching ``seed * 3``.
|
||||
|
||||
``cxx``
|
||||
The same in C++, which additionally requires each object's global
|
||||
constructors to have run, in dependency order, before ``main``, and its
|
||||
destructors to run on unload with that instance's own state.
|
||||
|
||||
``jmprel``
|
||||
A module whose imports are all bound through ``DT_JMPREL`` rather than
|
||||
``DT_REL``. A loader that ignored that table would load the module happily
|
||||
and fault only when it first called out.
|
||||
|
||||
The modules are embedded in the image as C headers. Their sources are in
|
||||
``apps/examples/fdpicxip/modules``, along with the makefile that rebuilds
|
||||
every header from them::
|
||||
|
||||
make -C apps/examples/fdpicxip/modules regen NUTTX_DIR=/path/to/nuttx
|
||||
|
||||
That is never part of the application build, because linking a module needs
|
||||
``arm-uclinuxfdpiceabi`` binutils, which building NuttX does not. See
|
||||
:ref:`fdpic_tooling`.
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
``CONFIG_EXAMPLES_FDPICXIP``
|
||||
Enable the example. Depends on ``CONFIG_FS_XIPFS``, ``CONFIG_FDPIC`` and
|
||||
``CONFIG_LIBC_EXECFUNCS``.
|
||||
|
||||
``CONFIG_EXAMPLES_FDPICXIP_MOUNTPT``
|
||||
Where the xipfs volume is mounted. Defaults to ``/mnt/xipfs``.
|
||||
|
|
@ -79,6 +79,20 @@ Sections
|
|||
power loss mid-program leaves behind. These need
|
||||
``CONFIG_FS_XIPFS_FAULT_INJECT``.
|
||||
|
||||
``fdpic``
|
||||
That a module staged into the volume loads and runs from flash: global
|
||||
constructors in dependency order, per-instance data, manufactured
|
||||
descriptors, both relocation tables, the GOT fallback for a library with no
|
||||
PLT, and every firmware entry point that has to resolve a module callback.
|
||||
Each module reports its own invariants through its exit status. Needs
|
||||
:doc:`CONFIG_FDPIC </components/fdpic>`.
|
||||
|
||||
``reject``
|
||||
That the loader refuses a module it cannot honour rather than loading
|
||||
something broken -- a mutated header, an absent dependency, a missing
|
||||
import, more ``DT_NEEDED`` entries than the walk will follow. Needs
|
||||
``CONFIG_FDPIC``.
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
|
|
|
|||
490
Documentation/components/fdpic.rst
Normal file
490
Documentation/components/fdpic.rst
Normal file
|
|
@ -0,0 +1,490 @@
|
|||
=============
|
||||
FDPIC Modules
|
||||
=============
|
||||
|
||||
FDPIC modules are ELF shared objects that execute **in place** out of
|
||||
memory-mapped flash. The read-only segment is mapped where it already sits on
|
||||
the media and never copied to RAM; only the writable segment is copied, once
|
||||
per running instance. Shared libraries work, and so does C++ with global
|
||||
constructors.
|
||||
|
||||
The loader is ``binfmt/fdpic.c``, enabled by ``CONFIG_FDPIC``. Modules are
|
||||
built out of tree with the tooling in ``tools/fdpic``.
|
||||
|
||||
How it works
|
||||
============
|
||||
|
||||
An FDPIC object has two ``PT_LOAD`` segments that may be placed independently
|
||||
of one another. Nothing in the code depends on the distance between them,
|
||||
which is what lets the loader leave the read-only segment in flash and put the
|
||||
writable one wherever RAM is available.
|
||||
|
||||
Code reaches its own data through a base register -- **r9** on ARM -- holding
|
||||
the address of that object's GOT. A function pointer is therefore not enough
|
||||
to call a function: the callee needs its data base too. FDPIC represents a
|
||||
function pointer as a two-word **descriptor**:
|
||||
|
||||
=========== ==============================================================
|
||||
Word Contents
|
||||
=========== ==============================================================
|
||||
``entry`` code address, including its Thumb bit
|
||||
``got`` data base to install in r9 before branching
|
||||
=========== ==============================================================
|
||||
|
||||
Building those descriptors is most of what the loader does. Because each one
|
||||
names its own base, two objects can be in use at once with separate writable
|
||||
segments, and a pointer handed to the firmware carries everything needed to
|
||||
call back into the module later.
|
||||
|
||||
A module links against nothing. libc and everything else are undefined
|
||||
imports, resolved at load time against other loaded objects first and the
|
||||
firmware's exported symbol table second.
|
||||
|
||||
Load sequence
|
||||
-------------
|
||||
|
||||
``binfmt/fdpic.c``, in order:
|
||||
|
||||
#. Parse the ELF and program headers and check the FDPIC marker. Anything
|
||||
else is declined quietly so another binfmt handler can try.
|
||||
|
||||
#. Read ``PT_DYNAMIC`` from the file before either segment is placed: the
|
||||
relocation count bounds the descriptor pool the writable allocation needs.
|
||||
|
||||
#. Pin the read-only segment on the media with ``XIPFSIOC_PIN``. The returned
|
||||
pointer *is* the execution address. The pin stops the filesystem
|
||||
relocating code that is executing.
|
||||
|
||||
#. Allocate the writable segment, copy ``.data`` from flash, zero ``.bss``.
|
||||
|
||||
#. Walk ``DT_NEEDED`` and load each shared library the same way, depth capped
|
||||
by ``FDPIC_MAX_DEPTH``.
|
||||
|
||||
#. Apply relocations for every object, from both ``DT_REL`` and ``DT_JMPREL``.
|
||||
|
||||
#. Run ``DT_INIT_ARRAY`` for every object, dependencies first.
|
||||
|
||||
#. Hand the scheduler a ``struct dspace_s`` holding the module's GOT, which
|
||||
``up_initial_state()`` installs into r9.
|
||||
|
||||
On unload the loader runs ``DT_FINI_ARRAY`` -- module first, then its
|
||||
libraries -- while their writable segments still exist, drops the pin, and
|
||||
frees the allocation. The pin release does not depend on the module calling
|
||||
``munmap``: ``mm_map_destroy()`` invokes each mapping's callback during group
|
||||
release, so a module that faults or is killed still drops its pin.
|
||||
|
||||
Differences from NXFLAT and ELF PIC
|
||||
===================================
|
||||
|
||||
All three run position-independent code from flash on a target with no MMU,
|
||||
and all three give several instances of one module a shared ``.text`` with
|
||||
private ``.data``. They differ in what a *pointer* can express and in what
|
||||
the toolchain has to provide.
|
||||
|
||||
========================= ============== ============== ============
|
||||
Property NXFLAT ELF PIC (XIP) FDPIC
|
||||
========================= ============== ============== ============
|
||||
Format NuttX only ELF ELF
|
||||
Extra build tools yes none linker only
|
||||
Base register r10 r10 r9
|
||||
Data base per task task object
|
||||
Shared libraries no no yes
|
||||
Foreign-thread callback no no yes
|
||||
``.rodata`` in flash linker script automatic automatic
|
||||
Cores Cortex-M3/M4 CONFIG_PIC Thumb-2
|
||||
========================= ============== ============== ============
|
||||
|
||||
**NXFLAT** is a NuttX-specific format. A module imports symbols from the base
|
||||
firmware but cannot export any, so shared libraries are not possible, and the
|
||||
build needs ``mknxflat`` to generate a thunk, ``ldnxflat`` to link, and one of
|
||||
the ``binfmt/libnxflat`` linker scripts to place the sections -- with which
|
||||
script depending on how the compiler addresses ``.rodata``.
|
||||
|
||||
**ELF PIC** needs no extra tools at all. With ``CONFIG_PIC`` the loader
|
||||
allocates the writable sections separately and, when the filesystem answers
|
||||
``FIOC_XIPBASE``, leaves the read-only ones on the media
|
||||
(``libs/libc/elf/elf_load.c``); ``mps3-an547:picostest`` runs ``ostest`` that
|
||||
way. Two limits follow from having one base register per task. A shared
|
||||
object is loaded as a single allocation, because "the relative positions
|
||||
between text and data must be maintained due to references to the GOT", so its
|
||||
text cannot stay in flash. And ``tcb->dspace`` is installed into ``REG_PIC``
|
||||
once per task, so every object in a task shares one data base and there is no
|
||||
``DT_NEEDED`` walk to load a second one.
|
||||
|
||||
**FDPIC** pays for its descriptors with an ``arm-uclinuxfdpiceabi`` linker, and
|
||||
gets back the two things a single register cannot express:
|
||||
|
||||
* **A module can be called back on a thread it never created.** A task or
|
||||
pthread the module starts inherits its data base through
|
||||
``nxtask_dup_dspace()``, so a register is enough there. A work-queue worker
|
||||
is not: it was created at boot and carries no module base. A descriptor
|
||||
supplies one, which is how ``SIGEV_THREAD`` notifications from
|
||||
``timer_create()`` and ``mq_notify()`` reach module code.
|
||||
|
||||
* **Two objects hold distinct data bases at once.** A module and the
|
||||
libraries it imports each have their own writable segment and still call one
|
||||
another, so two instances of a module share a library's text while each gets
|
||||
its own copy of the library's data.
|
||||
|
||||
Requirements
|
||||
============
|
||||
|
||||
**An ARM Thumb-2 core.** The boundary is the instruction set, not the core
|
||||
profile: GCC rejects FDPIC in Thumb-1 mode.
|
||||
|
||||
========================= ========================== =====
|
||||
Core Architecture FDPIC
|
||||
========================= ========================== =====
|
||||
Cortex-M3 / M4 / M7 ARMv7-M / ARMv7E-M yes
|
||||
Cortex-M33 ARMv8-M Mainline yes
|
||||
Cortex-M0 / M0+ / M23 ARMv6-M / ARMv8-M Baseline no
|
||||
========================= ========================== =====
|
||||
|
||||
If the toolchain's multilib list includes Thumb-1 variants, the Thumb-1 error
|
||||
appears even when the target is a Thumb-2 core. Configure the multilib set to
|
||||
exclude them.
|
||||
|
||||
RISC-V has no FDPIC ABI -- the psABI addendum is an unmerged RFC and no
|
||||
``EI_OSABI`` value is assigned -- so a RISC-V target cannot use this loader.
|
||||
|
||||
**Flash that is memory mapped and executable**, exposed by a filesystem that
|
||||
answers ``BIOC_XIPBASE``: :doc:`XIPFS <filesystem/xipfs>` or ROMFS.
|
||||
|
||||
**The linker.** A stock ``arm-none-eabi`` GCC compiles correct FDPIC objects
|
||||
for both C and C++. What it cannot do is link them: ``arm-none-eabi-ld`` is
|
||||
configured with the ``armelf`` emulation alone, and rather than failing it
|
||||
marks the output ``UNIX - System V``, which the loader refuses at run time.
|
||||
Only ``arm-uclinuxfdpiceabi`` carries ``armelf_linux_fdpiceabi``.
|
||||
|
||||
No distribution packages it, so build it -- binutils alone, about a minute::
|
||||
|
||||
tools/fdpic/build-binutils.sh ~/fdpic
|
||||
export PATH=~/fdpic/toolchain/bin:$PATH
|
||||
|
||||
An FDPIC GCC is not needed and nothing here uses one.
|
||||
|
||||
GNU ld's ARM FDPIC support has open defects, notably a failure on
|
||||
``R_ARM_GOTOFFFUNCDESC`` against a hidden function symbol (binutils PR31407,
|
||||
PR31408, PR31409).
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
``CONFIG_FDPIC`` selects ``BINFMT_LOADABLE`` and ``PIC``, and depends on
|
||||
``CONFIG_ARCH_ARM``. A working configuration also needs a symbol table for
|
||||
modules to import from and a filesystem that can expose its media::
|
||||
|
||||
CONFIG_FDPIC=y
|
||||
CONFIG_LIBC_EXECFUNCS=y
|
||||
CONFIG_EXECFUNCS_HAVE_SYMTAB=y
|
||||
CONFIG_EXECFUNCS_SYSTEM_SYMTAB=y
|
||||
CONFIG_FS_XIPFS=y
|
||||
|
||||
The options under ``if FDPIC``:
|
||||
|
||||
===================== =========== =====================================
|
||||
Option Default Meaning
|
||||
===================== =========== =====================================
|
||||
``FDPIC_LIBPATH`` /mnt/xipfs Where ``DT_NEEDED`` objects are found
|
||||
``FDPIC_STACKSIZE`` 2048 Stack for a task made from a module
|
||||
``FDPIC_ALLOW_COPY`` n Copy text to RAM when the media
|
||||
cannot be mapped
|
||||
===================== =========== =====================================
|
||||
|
||||
``FDPIC_ALLOW_COPY`` is for bring-up. Left off, a module that cannot execute
|
||||
in place fails to load instead of silently costing the RAM this mechanism
|
||||
exists to save.
|
||||
|
||||
**The base firmware must reserve r9.** It is not enough for the module to be
|
||||
well behaved: a firmware routine calling back into module code arrives with
|
||||
the module's GOT in r9 only if the compiler was never free to allocate that
|
||||
register elsewhere. ``arch/arm/src/common/Toolchain.defs`` adds
|
||||
``--fixed-r9`` to ``ARCHCPUFLAGS`` under ``CONFIG_FDPIC`` -- not to ``CFLAGS``,
|
||||
which most board ``Make.defs`` files assign with ``:=`` afterwards and would
|
||||
discard. A lost flag is silent, so check it arrived::
|
||||
|
||||
make V=1 2>&1 | grep -m1 fixed-r9
|
||||
|
||||
.. _fdpic_tooling:
|
||||
|
||||
Building a module
|
||||
=================
|
||||
|
||||
A whole module is three lines of makefile beside the source. Taking
|
||||
``apps/examples/fdpicxip/modules/qsorter.c`` as the source::
|
||||
|
||||
MODULE = qsorter
|
||||
SRCS = qsorter.c
|
||||
|
||||
include /path/to/nuttx/tools/fdpic/nuttx-fdpic.mk
|
||||
|
||||
::
|
||||
|
||||
make NUTTX_DIR=/path/to/nuttx
|
||||
CC qsorter.c
|
||||
LD qsorter.fdpic
|
||||
OK qsorter.fdpic: FDPIC, entry 0x2a1, 4 imports resolved
|
||||
|
||||
``NUTTX_DIR`` must be a configured, **built** tree: the compile needs its
|
||||
headers and the verification step needs ``libs/libc/exec_symtab.c``, which the
|
||||
build generates.
|
||||
|
||||
C++ sources go in ``CXXSRCS``. ``ARM_TOOLCHAIN=`` and ``FDPIC_TOOLCHAIN=``
|
||||
override the two toolchain prefixes, and ``BINDNOW=`` empties the default
|
||||
``-z now``. ``make`` will run the verification step alone against a stale
|
||||
``.fdpic`` and report ``OK``, so ``make clean`` when changing toolchains.
|
||||
|
||||
The commands it runs are::
|
||||
|
||||
arm-none-eabi-gcc -mcpu=cortex-m33 -mthumb -mfdpic -fPIC -Os \
|
||||
-fno-builtin -I$NUTTX/include -D__NuttX__ -c qsorter.c -o qsorter.o
|
||||
|
||||
arm-uclinuxfdpiceabi-ld -m armelf_linux_fdpiceabi -shared -z now \
|
||||
-e main -o qsorter.fdpic qsorter.o
|
||||
|
||||
Four flags carry weight:
|
||||
|
||||
* **-mfdpic** is stated rather than assumed, so a mis-set toolchain fails
|
||||
loudly instead of producing a plain ELF the loader refuses.
|
||||
|
||||
* **-fPIC** is not implied by ``-mfdpic`` on the bare-metal target, and
|
||||
without it the link emits ``TEXTREL``. Text relocations cannot work
|
||||
against text executed from read-only flash.
|
||||
|
||||
* **-shared** preserves the ``R_ARM_FUNCDESC_VALUE`` relocations for imported
|
||||
symbols. A PIE link with ``--unresolved-symbols=ignore-all`` appears to
|
||||
work but degrades every import to ``R_ARM_NONE``, and the module branches to
|
||||
zero on its first call into the firmware.
|
||||
|
||||
* **-m armelf_linux_fdpiceabi** is required: this linker supports four
|
||||
emulations and will not guess.
|
||||
|
||||
Verifying imports
|
||||
-----------------
|
||||
|
||||
A module links with ``-shared``, so undefined symbols are permitted: importing
|
||||
something the firmware does not export links cleanly and fails on the target
|
||||
as a bare ``-ENOENT`` naming no symbol. ``fdpic-verify.sh`` runs as part of the
|
||||
default target and turns that into a build failure::
|
||||
|
||||
FAIL imports the firmware does not export:
|
||||
this_symbol_does_not_exist
|
||||
|
||||
The export list comes from ``CONFIG_EXECFUNCS_SYSTEM_SYMTAB``, which generates
|
||||
``libs/libc/exec_symtab.c``. Most entries there sit behind
|
||||
``#if defined(CONFIG_...)`` guards, so the file has to go through the
|
||||
preprocessor before its names mean anything -- read textually it offers every
|
||||
symbol that *could* be exported rather than the ones that were::
|
||||
|
||||
tools/fdpic/nuttx-exports.sh /path/to/nuttx # the list
|
||||
make exports NUTTX_DIR=/path/to/nuttx # the count
|
||||
|
||||
Shared libraries
|
||||
----------------
|
||||
|
||||
A library is built the same way with a soname and no entry point, and the
|
||||
module names it on the link line::
|
||||
|
||||
arm-uclinuxfdpiceabi-ld ... -shared -soname libcounter.so \
|
||||
-e 0 -o libcounter.so libcounter.o
|
||||
|
||||
arm-uclinuxfdpiceabi-ld ... -shared -e main \
|
||||
-o user.fdpic user.o libcounter.so
|
||||
|
||||
At run time the library must be present at ``CONFIG_FDPIC_LIBPATH`` under the
|
||||
name in the module's ``DT_NEEDED`` entry. Each object gets its own GOT and its
|
||||
own writable data, so two modules using one library see independent library
|
||||
state while sharing one copy of its code in flash.
|
||||
|
||||
A leaf library -- one that calls nothing outside itself -- has no
|
||||
``DT_PLTGOT``, because with no external calls there is no PLT. It still has a
|
||||
GOT, and the loader falls back to ``PT_DYNAMIC`` vaddr plus memsz.
|
||||
|
||||
C++ modules
|
||||
-----------
|
||||
|
||||
Three compile flags are not optional:
|
||||
|
||||
* ``-fno-use-cxa-atexit``. The default registers each static object's
|
||||
destructor with ``__cxa_atexit(dtor, obj, &__dso_handle)``, and
|
||||
``__dso_handle`` comes from ``crtbegin``, which ``-nostartfiles`` excludes;
|
||||
the link fails outright. Turning it off also puts destructors in
|
||||
``.fini_array``, which is what the loader walks on unload.
|
||||
|
||||
* ``-fno-exceptions -fno-rtti``. Both need ``libsupc++``, which a module
|
||||
linking ``-nostdlib`` cannot reach.
|
||||
|
||||
C++ is compiled with ``arm-none-eabi-g++`` and linked by the FDPIC linker,
|
||||
exactly as C is.
|
||||
|
||||
``DT_INIT_ARRAY`` runs after relocations and before the module is entered,
|
||||
``DT_FINI_ARRAY`` on unload, both per object and in dependency order.
|
||||
Constructors run in the task that called the loader, not the task that will
|
||||
run the module, so a constructor sees the loading task's identity.
|
||||
|
||||
Getting this wrong is quiet: a loader that ignores ``DT_INIT_ARRAY`` still
|
||||
loads a C++ module, resolves every symbol and runs it, with every global left
|
||||
zero.
|
||||
|
||||
Calling back into a module
|
||||
==========================
|
||||
|
||||
A module's function pointer is the address of a descriptor in its **writable
|
||||
segment**. Firmware that stores one and later branches to it jumps into RAM
|
||||
data. ``fdpic_callback()`` resolves such a pointer to a code address,
|
||||
deciding whether it is a descriptor by testing r9: a module task runs with its
|
||||
GOT there, a kernel task with zero.
|
||||
|
||||
It is applied at every entry point a module can reach today: ``qsort``,
|
||||
``bsearch``, ``pthread_create``, ``signal``, ``sigaction``,
|
||||
``task_create``/``task_create_with_stack``, ``task_spawn``, ``pthread_once``,
|
||||
``scandir`` (its filter), and ``mq_notify``/``timer_create`` with
|
||||
``SIGEV_THREAD``.
|
||||
|
||||
A new entry point that takes a module callback must resolve it too, under
|
||||
three rules:
|
||||
|
||||
* **Resolve once, in the innermost common routine.** Resolving twice treats a
|
||||
code address as a descriptor. ``qsort`` recurses, so its wrapper resolves
|
||||
and the recursive body does not; ``signal()`` does not resolve because
|
||||
``nxsig_action()`` does it for both paths; ``scandir`` resolves its filter
|
||||
but not the comparison function it hands to ``qsort``.
|
||||
|
||||
* **Exclude sentinel values by hand.** ``fdpic_callback()`` declines to
|
||||
dereference NULL and nothing else. ``sigaction`` excludes ``SIG_IGN``,
|
||||
``SIG_DFL``, ``SIG_HOLD`` and ``SIG_ERR`` -- the integers 0, 1, 2 and -1.
|
||||
|
||||
* **A callback on a shared thread needs its base installed.** A
|
||||
``SIGEV_THREAD`` notification runs on a work-queue worker with no module
|
||||
base, so resolving the entry is not enough. Capture the base at
|
||||
registration with ``fdpic_base()``, in the module's context, and install it
|
||||
around the call with ``fdpic_invoke()``.
|
||||
|
||||
Everywhere else the callback runs in a task that inherited the module's
|
||||
D-Space, so only the code address needs resolving.
|
||||
|
||||
Reference
|
||||
=========
|
||||
|
||||
Object layout
|
||||
-------------
|
||||
|
||||
The FDPIC marker is ``e_ident[EI_OSABI] == ELFOSABI_ARM_FDPIC`` (65), which
|
||||
``readelf -h`` shows as "OS/ABI: ARM FDPIC". It is *not* in ``e_flags``,
|
||||
which read an ordinary ``0x5000000, Version5 EABI``. Validate the OS/ABI byte
|
||||
and nothing else.
|
||||
|
||||
A linked module has the shape execute-in-place needs with no linker script::
|
||||
|
||||
LOAD vaddr 0x00000000 R E .text .rodata .hash .dynsym .dynstr
|
||||
LOAD vaddr 0x000012b4 RW .dynamic .got .data .bss
|
||||
DYNAMIC DT_PLTGOT -> .got
|
||||
|
||||
``.rodata`` lands in the RX segment on its own, resolved PC-relative or
|
||||
GOT-indirect. That matters: in the RW segment it would be copied to RAM with
|
||||
``.data`` and most of the saving would evaporate, silently and with everything
|
||||
still working.
|
||||
|
||||
``DT_PLTGOT`` gives the GOT base, which is the value r9 must hold.
|
||||
|
||||
Relocations
|
||||
-----------
|
||||
|
||||
The static link resolves ``R_ARM_GOT_BREL`` and ``R_ARM_GOTFUNCDESC`` into the
|
||||
GOT already, so only three types carry work into a linked module.
|
||||
``R_ARM_NONE`` is accepted and skipped.
|
||||
|
||||
``R_ARM_RELATIVE``
|
||||
An address needing its segment's base added.
|
||||
|
||||
``R_ARM_FUNCDESC_VALUE``
|
||||
A descriptor the linker has laid out, for the loader to fill in with
|
||||
{resolved code, GOT base}. This is what a *call* to an imported function
|
||||
produces, and where the firmware's symbol table is consulted.
|
||||
|
||||
``R_ARM_FUNCDESC``
|
||||
A pointer to a descriptor that does not exist yet, which the loader
|
||||
manufactures from a pool behind the writable segment. This is what *taking
|
||||
the address* of an imported function produces -- a different thing from
|
||||
calling one, and both can appear for the same symbol. Unlike NXFLAT, a
|
||||
pointer to a ``static`` function needs no workaround.
|
||||
|
||||
Constants, from binutils ``include/elf/arm.h`` and now in
|
||||
``arch/arm/include/elf.h``: ``R_ARM_GOTFUNCDESC`` 161,
|
||||
``R_ARM_GOTOFFFUNCDESC`` 162, ``R_ARM_FUNCDESC`` 163,
|
||||
``R_ARM_FUNCDESC_VALUE`` 164.
|
||||
|
||||
Relocation tables
|
||||
-----------------
|
||||
|
||||
Which table an imported function's descriptor lands in is the linker's
|
||||
decision. With ``-z now`` imports stay in ``DT_REL``; without it they go to
|
||||
``DT_JMPREL`` and a module may have no ``DT_REL`` at all.
|
||||
|
||||
The loader binds **both**, eagerly. There is no lazy resolver, so an unwalked
|
||||
table would not be deferred work -- the descriptors would stay unrelocated
|
||||
until the module branched through one. Nothing is lost by binding early: a
|
||||
module carries a handful of relocations.
|
||||
|
||||
The two are not walked identically. In ``DT_REL`` the word being overwritten
|
||||
is the addend and is added to the resolved symbol value. In ``DT_JMPREL`` it
|
||||
is the lazy-binding bootstrap -- the address of the entry's own PLT stub, with
|
||||
a GOT half of ``-1`` -- and the binder overwrites the descriptor outright.
|
||||
|
||||
``DT_PLTREL`` must say ``REL``. Nothing reads ``RELA``, whose entries are
|
||||
twelve bytes rather than eight, so an object declaring that layout is refused
|
||||
rather than misread.
|
||||
|
||||
``.rofixup`` is skipped. It is the self-relocation list a *static*
|
||||
executable's ``crt0`` walks to find its own GOT; a module links ``-shared
|
||||
-nostartfiles``, so no ``crt0`` runs, and the section holds only the GOT
|
||||
self-pointer that the loader supplies from r9 instead. Everything else
|
||||
load-dependent is in ``.rel.dyn``/``.rel.plt``, and a PIC object cannot keep an
|
||||
absolute pointer in its read-only segment.
|
||||
|
||||
binfmt integration
|
||||
------------------
|
||||
|
||||
The loader registers a ``struct binfmt_s``, so modules load through the
|
||||
standard dispatch alongside ELF and NXFLAT. Two details of that contract are
|
||||
easy to get wrong:
|
||||
|
||||
* ``binp->picbase`` is a ``struct dspace_s *``, not an address.
|
||||
``up_initial_state()`` dereferences ``->region``, so a bare GOT puts a kernel
|
||||
address in r9 and panics.
|
||||
|
||||
* ``binp->stacksize`` must be set, or ``binfmt_execmodule`` rejects the load
|
||||
with ``-EINVAL``.
|
||||
|
||||
``binfmt`` reports any loader error as ``-ENOENT``, because at the dispatch
|
||||
layer "refused it" and "not my format" are the same answer. The reason
|
||||
reaches the syslog only, so do not read ``-ENOENT`` from ``exec`` as "file not
|
||||
found".
|
||||
|
||||
Testing
|
||||
=======
|
||||
|
||||
``apps/testing/fs/xipfs`` stages modules out of the filesystem and asserts on
|
||||
them. ``xipfs_test fdpic`` runs that part in about twenty seconds, and
|
||||
``xipfs_test reject`` the loader's refusal paths.
|
||||
|
||||
It covers what fails silently: constructors in dependency order, per-instance
|
||||
data, manufactured descriptors, both relocation tables, the GOT fallback for a
|
||||
library with no PLT, and every callback entry point. Each check has been
|
||||
verified against a deliberate one-line breakage of the loader.
|
||||
|
||||
**A run that ends without its summary line is a failure, not a hang.** These
|
||||
tests call into modules whose relocations must be correct; when they are not,
|
||||
the module branches to an unrelocated address and the HardFault takes the
|
||||
system down mid-test, because on Cortex-M a HardFault is system-wide rather
|
||||
than per-task.
|
||||
|
||||
The module sources are in ``apps/examples/fdpicxip/modules``: ``libshape.cpp``
|
||||
for the init/fini arrays, ``funcdesc.c`` for the descriptor pool, ``lazymod.c``
|
||||
for ``DT_JMPREL``, ``libcounter.c`` for a leaf library with no ``DT_PLTGOT``.
|
||||
An opt-in ``make regen`` there rebuilds the headers both apps embed; it is
|
||||
never part of the application build, so the tree stays buildable with a plain
|
||||
toolchain.
|
||||
|
||||
``apps/examples/fdpicxip`` is a runnable demonstration of the same modules --
|
||||
see :doc:`/applications/examples/fdpicxip/index`.
|
||||
|
|
@ -14,6 +14,7 @@ case, you can head to the :doc:`reference <../reference/index>`.
|
|||
binfmt.rst
|
||||
concurrency/index.rst
|
||||
drivers/index.rst
|
||||
fdpic.rst
|
||||
nxflat.rst
|
||||
nxgraphics/index.rst
|
||||
paging.rst
|
||||
|
|
|
|||
|
|
@ -177,6 +177,14 @@ xipfs
|
|||
XIPFS mounted on the on-board flash, with the ``xipfs`` command and the
|
||||
XIPFS test suite.
|
||||
|
||||
xipfs-fdpic
|
||||
-----------
|
||||
|
||||
Same as ``xipfs``, plus the FDPIC module loader, the FDPIC half of the test
|
||||
suite and the ``fdpicxip`` demo. The modules it runs are prebuilt blobs, so
|
||||
this configuration builds with a plain toolchain; rebuilding them from source
|
||||
needs an ``arm-uclinuxfdpiceabi`` toolchain.
|
||||
|
||||
xipfs-nxflat
|
||||
------------
|
||||
|
||||
|
|
|
|||
|
|
@ -182,9 +182,16 @@
|
|||
|
||||
/* The PIC register is usually R10. It can be R9 is stack checking is enabled
|
||||
* or if the user changes it with -mpic-register on the GCC command line.
|
||||
*
|
||||
* The ARM FDPIC ABI mandates R9, and GCC hardcodes it for -mfdpic, so an
|
||||
* FDPIC build has no choice in the matter.
|
||||
*/
|
||||
|
||||
#define REG_PIC REG_R10
|
||||
#ifdef CONFIG_FDPIC
|
||||
# define REG_PIC REG_R9
|
||||
#else
|
||||
# define REG_PIC REG_R10
|
||||
#endif
|
||||
|
||||
/* CONTROL register */
|
||||
|
||||
|
|
|
|||
|
|
@ -188,9 +188,16 @@
|
|||
|
||||
/* The PIC register is usually R10. It can be R9 is stack checking is enabled
|
||||
* or if the user changes it with -mpic-register on the GCC command line.
|
||||
*
|
||||
* The ARM FDPIC ABI mandates R9, and GCC hardcodes it for -mfdpic, so an
|
||||
* FDPIC build has no choice in the matter.
|
||||
*/
|
||||
|
||||
#define REG_PIC REG_R10
|
||||
#ifdef CONFIG_FDPIC
|
||||
# define REG_PIC REG_R9
|
||||
#else
|
||||
# define REG_PIC REG_R10
|
||||
#endif
|
||||
|
||||
/* CONTROL register */
|
||||
|
||||
|
|
|
|||
|
|
@ -206,6 +206,24 @@
|
|||
#define R_ARM_THM_TLS_DESCSEQ16 129 /* Thumb16 */
|
||||
#define R_ARM_THM_TLS_DESCSEQ32 130 /* Thumb32 */
|
||||
|
||||
/* FDPIC relocations.
|
||||
*
|
||||
* Under the FDPIC ABI each PT_LOAD segment is placed independently, so a
|
||||
* function pointer cannot be a bare code address: it has to carry the data
|
||||
* base its callee will need. A "function descriptor" is that pair, and
|
||||
* these relocations are how the loader is asked to build and reference
|
||||
* them. Values are from the ARM FDPIC ABI as implemented by binutils
|
||||
* (include/elf/arm.h).
|
||||
*/
|
||||
|
||||
#define R_ARM_GOTFUNCDESC 161 /* Data GOT entry holding a descriptor */
|
||||
#define R_ARM_GOTOFFFUNCDESC 162 /* Data GOT-relative descriptor */
|
||||
#define R_ARM_FUNCDESC 163 /* Data Address of a descriptor */
|
||||
#define R_ARM_FUNCDESC_VALUE 164 /* Data The descriptor itself: {code, GOT} */
|
||||
#define R_ARM_TLS_GD32_FDPIC 165 /* Data */
|
||||
#define R_ARM_TLS_LDM32_FDPIC 166 /* Data */
|
||||
#define R_ARM_TLS_IE32_FDPIC 167 /* Data */
|
||||
|
||||
/* Processor specific values for the Phdr p_type field. */
|
||||
|
||||
#define PT_ARM_EXIDX (PT_LOPROC + 1) /* ARM unwind segment. */
|
||||
|
|
|
|||
|
|
@ -612,11 +612,42 @@ CELFFLAGS = $(filter-out --fixed-r10,$(CFLAGS)) -fvisibility=hidden \
|
|||
CXXELFFLAGS = $(filter-out --fixed-r10,$(CXXFLAGS)) -fvisibility=hidden \
|
||||
-mlong-calls
|
||||
|
||||
ifeq ($(CONFIG_FDPIC),y)
|
||||
# An FDPIC module reaches its own data through r9, and that register has
|
||||
# to survive a call into the base firmware. A base firmware routine that
|
||||
# calls back into module code -- qsort() with a module comparison
|
||||
# function is the standard case -- must arrive there with the module's
|
||||
# GOT still in r9, which it will not if the compiler was free to allocate
|
||||
# r9 here.
|
||||
#
|
||||
# This goes in ARCHCPUFLAGS rather than CFLAGS because almost every board
|
||||
# Make.defs assigns
|
||||
#
|
||||
# CFLAGS := $(ARCHCFLAGS) $(ARCHOPTIMIZATION) $(ARCHCPUFLAGS) ...
|
||||
#
|
||||
# with ':=' after including this file, which discards anything added to
|
||||
# CFLAGS here but re-expands ARCHCPUFLAGS. The failure is silent and the
|
||||
# symptom is remote from the cause: the build succeeds and only a callback
|
||||
# from firmware into module code misbehaves, reading its data through a
|
||||
# register the firmware has since reused.
|
||||
#
|
||||
# CPICFLAGS derives from CFLAGS, so this reaches module compiles too.
|
||||
# That is harmless: GCC only rejects --fixed-rN when it names the same
|
||||
# register as -mpic-register, and PIC modules use r10.
|
||||
|
||||
ARCHCPUFLAGS += --fixed-r9
|
||||
endif
|
||||
|
||||
ifeq ($(CONFIG_PIC),y)
|
||||
ifneq ($(CONFIG_FDPIC),y)
|
||||
# ARCHCFLAGS, not CFLAGS: board Make.defs reassign CFLAGS with ':='
|
||||
# after including this file, which would discard the flag.
|
||||
#
|
||||
# FDPIC reserves r9 above instead; reserving both would cost a register
|
||||
# for nothing.
|
||||
|
||||
ARCHCFLAGS += --fixed-r10
|
||||
endif
|
||||
CELFFLAGS += $(PICFLAGS) -mpic-register=r10
|
||||
CXXELFFLAGS += $(PICFLAGS) -mpic-register=r10
|
||||
|
||||
|
|
|
|||
|
|
@ -61,6 +61,10 @@ if(CONFIG_NXFLAT)
|
|||
list(APPEND SRCS nxflat.c)
|
||||
endif()
|
||||
|
||||
if(CONFIG_FDPIC)
|
||||
list(APPEND SRCS fdpic.c)
|
||||
endif()
|
||||
|
||||
# Builtin application interfaces
|
||||
|
||||
if(CONFIG_BUILTIN)
|
||||
|
|
|
|||
|
|
@ -46,6 +46,55 @@ if NXFLAT
|
|||
source "binfmt/libnxflat/Kconfig"
|
||||
endif
|
||||
|
||||
config FDPIC
|
||||
bool "Enable the FDPIC ELF Binary Format"
|
||||
default n
|
||||
select BINFMT_LOADABLE
|
||||
select PIC
|
||||
depends on ARCH_ARM
|
||||
---help---
|
||||
Enable support for FDPIC ELF modules.
|
||||
|
||||
An FDPIC module has independently placeable read-only and writable
|
||||
segments, so its text can be mapped and executed directly out of
|
||||
flash while only the writable segment is copied to RAM -- once per
|
||||
running instance. This requires a filesystem that can expose the
|
||||
media, such as XIPFS or ROMFS, and an arm-uclinuxfdpiceabi
|
||||
toolchain to build the modules.
|
||||
|
||||
The base firmware must reserve the FDPIC register (r9 on ARM) or
|
||||
callbacks from the base firmware into module code will fail.
|
||||
|
||||
if FDPIC
|
||||
|
||||
config FDPIC_LIBPATH
|
||||
string "Directory searched for shared libraries"
|
||||
default "/mnt/xipfs"
|
||||
---help---
|
||||
Where the loader looks for the objects a module names in its
|
||||
DT_NEEDED entries. For execute-in-place this should be a
|
||||
filesystem that can expose its media, so a library's code is
|
||||
mapped rather than copied.
|
||||
|
||||
config FDPIC_STACKSIZE
|
||||
int "FDPIC module stack size"
|
||||
default 2048
|
||||
---help---
|
||||
Size of the stack given to a task created from an FDPIC module.
|
||||
|
||||
config FDPIC_ALLOW_COPY
|
||||
bool "Allow copying text to RAM when execute-in-place is impossible"
|
||||
default n
|
||||
---help---
|
||||
By default a module whose text cannot be mapped directly onto the
|
||||
media fails to load, because copying it to RAM silently forfeits
|
||||
the memory saving that FDPIC was chosen for.
|
||||
|
||||
Enable this during board bring-up to keep modules loading from a
|
||||
filesystem that does not support execute-in-place.
|
||||
|
||||
endif # FDPIC
|
||||
|
||||
config ELF
|
||||
bool "Enable the ELF Binary Format"
|
||||
default n
|
||||
|
|
|
|||
|
|
@ -58,6 +58,10 @@ ifeq ($(CONFIG_NXFLAT),y)
|
|||
CSRCS += nxflat.c
|
||||
endif
|
||||
|
||||
ifeq ($(CONFIG_FDPIC),y)
|
||||
CSRCS += fdpic.c
|
||||
endif
|
||||
|
||||
# Add configured binary modules
|
||||
|
||||
include libnxflat/Make.defs
|
||||
|
|
|
|||
|
|
@ -327,6 +327,38 @@ int nxflat_initialize(void);
|
|||
void nxflat_uninitialize(void);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_FDPIC
|
||||
/****************************************************************************
|
||||
* Name: fdpic_initialize
|
||||
*
|
||||
* Description:
|
||||
* FDPIC support is built unconditionally. However, in order to
|
||||
* use this binary format, this function must be called during system
|
||||
* initialization in order to register the FDPIC binary format.
|
||||
*
|
||||
* Returned Value:
|
||||
* This is a NuttX internal function so it follows the convention that
|
||||
* 0 (OK) is returned on success and a negated errno is returned on
|
||||
* failure.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int fdpic_initialize(void);
|
||||
|
||||
/****************************************************************************
|
||||
* Name: fdpic_uninitialize
|
||||
*
|
||||
* Description:
|
||||
* Unregister the FDPIC binary loader
|
||||
*
|
||||
* Returned Value:
|
||||
* None
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
void fdpic_uninitialize(void);
|
||||
#endif
|
||||
|
||||
#undef EXTERN
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,6 +74,14 @@ void binfmt_initialize(void)
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_FDPIC
|
||||
ret = fdpic_initialize();
|
||||
if (ret < 0)
|
||||
{
|
||||
berr("ERROR: fdpic_initialize failed: %d\n", ret);
|
||||
}
|
||||
#endif
|
||||
|
||||
UNUSED(ret);
|
||||
}
|
||||
|
||||
|
|
|
|||
1546
binfmt/fdpic.c
Normal file
1546
binfmt/fdpic.c
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,58 @@
|
|||
#
|
||||
# This file is autogenerated: PLEASE DO NOT EDIT IT.
|
||||
#
|
||||
# You can use "make menuconfig" to make any modifications to the installed .config file.
|
||||
# You can then do "make savedefconfig" to generate a new defconfig file that includes your
|
||||
# modifications.
|
||||
#
|
||||
# CONFIG_NSH_ARGCAT is not set
|
||||
# CONFIG_NSH_CMDOPT_HEXDUMP is not set
|
||||
# CONFIG_NSH_DISABLE_DATE is not set
|
||||
# CONFIG_NSH_DISABLE_LOSMART is not set
|
||||
# CONFIG_STANDARD_SERIAL is not set
|
||||
CONFIG_ARCH="arm"
|
||||
CONFIG_ARCH_BOARD="pimoroni-pico-2-plus"
|
||||
CONFIG_ARCH_BOARD_COMMON=y
|
||||
CONFIG_ARCH_BOARD_PIMORONI_PICO_2_PLUS=y
|
||||
CONFIG_ARCH_CHIP="rp23xx"
|
||||
CONFIG_ARCH_CHIP_RP23XX=y
|
||||
CONFIG_ARCH_RAMVECTORS=y
|
||||
CONFIG_ARCH_STACKDUMP=y
|
||||
CONFIG_BOARDCTL_RESET=y
|
||||
CONFIG_BOARD_LOOPSPERMSEC=10450
|
||||
CONFIG_BUILTIN=y
|
||||
CONFIG_DEBUG_FULLOPT=y
|
||||
CONFIG_DEBUG_SYMBOLS=y
|
||||
CONFIG_EXAMPLES_FDPICXIP=y
|
||||
CONFIG_EXAMPLES_HELLO=y
|
||||
CONFIG_EXECFUNCS_HAVE_SYMTAB=y
|
||||
CONFIG_EXECFUNCS_SYSTEM_SYMTAB=y
|
||||
CONFIG_FDPIC=y
|
||||
CONFIG_FS_PROCFS=y
|
||||
CONFIG_FS_PROCFS_REGISTER=y
|
||||
CONFIG_FS_XIPFS=y
|
||||
CONFIG_FS_XIPFS_FAULT_INJECT=y
|
||||
CONFIG_INIT_ENTRYPOINT="nsh_main"
|
||||
CONFIG_INIT_STACKSIZE=16384
|
||||
CONFIG_LIBC_EXECFUNCS=y
|
||||
CONFIG_MTD=y
|
||||
CONFIG_NFILE_DESCRIPTORS_PER_BLOCK=6
|
||||
CONFIG_NSH_BUILTIN_APPS=y
|
||||
CONFIG_NSH_READLINE=y
|
||||
CONFIG_RAM_SIZE=532480
|
||||
CONFIG_RAM_START=0x20000000
|
||||
CONFIG_READLINE_CMD_HISTORY=y
|
||||
CONFIG_RP23XX_FLASH_MTD=y
|
||||
CONFIG_RR_INTERVAL=200
|
||||
CONFIG_SCHED_HPWORK=y
|
||||
CONFIG_SCHED_WAITPID=y
|
||||
CONFIG_SIG_EVTHREAD=y
|
||||
CONFIG_START_DAY=9
|
||||
CONFIG_START_MONTH=2
|
||||
CONFIG_START_YEAR=2021
|
||||
CONFIG_SYSLOG_CONSOLE=y
|
||||
CONFIG_SYSTEM_NSH=y
|
||||
CONFIG_SYSTEM_XIPFS=y
|
||||
CONFIG_TESTING_FS_XIPFS=y
|
||||
CONFIG_TESTING_FS_XIPFS_MTD="/dev/rpflash"
|
||||
CONFIG_UART0_SERIAL_CONSOLE=y
|
||||
|
|
@ -278,6 +278,10 @@
|
|||
#define DT_TEXTREL 22 /* d_un=ignored */
|
||||
#define DT_JMPREL 23 /* d_un=d_ptr */
|
||||
#define DT_BINDNOW 24 /* d_un=ignored */
|
||||
#define DT_INIT_ARRAY 25 /* d_un=d_ptr */
|
||||
#define DT_FINI_ARRAY 26 /* d_un=d_ptr */
|
||||
#define DT_INIT_ARRAYSZ 27 /* d_un=d_val */
|
||||
#define DT_FINI_ARRAYSZ 28 /* d_un=d_val */
|
||||
#define DT_LOPROC 0x70000000 /* d_un=unspecified */
|
||||
#define DT_HIPROC 0x7fffffff /* d_un= unspecified */
|
||||
|
||||
|
|
|
|||
296
include/nuttx/binfmt/fdpic.h
Normal file
296
include/nuttx/binfmt/fdpic.h
Normal file
|
|
@ -0,0 +1,296 @@
|
|||
/****************************************************************************
|
||||
* include/nuttx/binfmt/fdpic.h
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership. The
|
||||
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef __INCLUDE_NUTTX_BINFMT_FDPIC_H
|
||||
#define __INCLUDE_NUTTX_BINFMT_FDPIC_H
|
||||
|
||||
/****************************************************************************
|
||||
* Included Files
|
||||
****************************************************************************/
|
||||
|
||||
#include <nuttx/config.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <elf.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <nuttx/fs/fs.h>
|
||||
#include <nuttx/sched.h>
|
||||
|
||||
/****************************************************************************
|
||||
* Pre-processor Definitions
|
||||
****************************************************************************/
|
||||
|
||||
/* A function descriptor is the pair that makes FDPIC work: a callee cannot
|
||||
* find its own data from its code address alone, because the two segments
|
||||
* are placed independently, so every function pointer carries the data base
|
||||
* to install alongside the entry point.
|
||||
*/
|
||||
|
||||
#define FDPIC_DESC_WORDS 2
|
||||
|
||||
/* Shared libraries a single object may name in DT_NEEDED */
|
||||
|
||||
#define FDPIC_MAX_NEEDED 8
|
||||
|
||||
/* How deep a chain of libraries depending on libraries may go.
|
||||
*
|
||||
* A cycle cannot run away -- an object joins the load's list before its own
|
||||
* dependencies are walked, so coming back around finds it already there --
|
||||
* but a chain of distinct names has nothing to stop it. The walk recurses
|
||||
* once per link with a path buffer on each frame, so without a bound a
|
||||
* malformed module set overflows the stack of whichever task called the
|
||||
* loader rather than being rejected.
|
||||
*/
|
||||
|
||||
#define FDPIC_MAX_DEPTH 8
|
||||
|
||||
/****************************************************************************
|
||||
* Public Types
|
||||
****************************************************************************/
|
||||
|
||||
struct fdpic_desc_s
|
||||
{
|
||||
uintptr_t entry; /* Address of the code */
|
||||
uintptr_t got; /* Data/GOT base to install in the FDPIC register */
|
||||
};
|
||||
|
||||
/* One loaded object: the module itself, or a shared library it needs.
|
||||
*
|
||||
* A load produces a list of these -- the module first, then whatever its
|
||||
* DT_NEEDED entries pulled in. Text is mapped and therefore shared between
|
||||
* every instance; the writable segment is private to this load, so two
|
||||
* tasks using the same library each get their own copy of its data, which
|
||||
* is the behaviour a library with state has to have.
|
||||
*/
|
||||
|
||||
struct fdpic_loadinfo_s
|
||||
{
|
||||
FAR struct fdpic_loadinfo_s *flink; /* Next object in this load */
|
||||
char name[32]; /* For diagnostics and DT_NEEDED */
|
||||
|
||||
struct file file; /* The module being loaded */
|
||||
Elf32_Ehdr ehdr; /* Copy of the ELF header */
|
||||
|
||||
/* Read-only segment. This is mapped, not copied: on a filesystem that
|
||||
* can expose the media directly this pointer is the flash address and
|
||||
* the text is never in RAM at all.
|
||||
*/
|
||||
|
||||
uintptr_t textaddr; /* Where the RX segment actually is */
|
||||
uintptr_t textvaddr; /* p_vaddr it was linked at */
|
||||
size_t textsize;
|
||||
bool textmapped; /* True if it came from mmap */
|
||||
|
||||
/* Read-write segment. Always a private RAM copy, one per instance. */
|
||||
|
||||
uintptr_t dataaddr; /* Where the RW segment was placed */
|
||||
uintptr_t datavaddr; /* p_vaddr it was linked at */
|
||||
size_t datafilesz; /* Bytes to read from the file */
|
||||
size_t datamemsz; /* Including .bss */
|
||||
size_t dataalloc; /* Total allocation incl. descriptors */
|
||||
|
||||
uintptr_t gotaddr; /* Runtime address of the GOT */
|
||||
uintptr_t entry; /* Runtime entry point */
|
||||
|
||||
/* Reference counted container the scheduler installs into the FDPIC
|
||||
* register on every context switch. Allocated here; freed by
|
||||
* sched_releasetcb() once the last thread using it is gone.
|
||||
*/
|
||||
|
||||
FAR struct dspace_s *dspace;
|
||||
|
||||
/* Pool used to satisfy R_ARM_FUNCDESC, which asks the loader to
|
||||
* manufacture a descriptor and hand back its address.
|
||||
*/
|
||||
|
||||
uintptr_t descpool;
|
||||
uint16_t ndesc; /* Capacity */
|
||||
uint16_t usedesc; /* Next free slot */
|
||||
|
||||
/* Dynamic information, all as link-time virtual addresses */
|
||||
|
||||
/* DT_NEEDED entries, as offsets into DT_STRTAB. Resolved to names once
|
||||
* the read-only segment holding the string table is mapped.
|
||||
*/
|
||||
|
||||
uint32_t needed[FDPIC_MAX_NEEDED];
|
||||
uint8_t nneeded;
|
||||
|
||||
uintptr_t relvaddr;
|
||||
size_t relsize;
|
||||
|
||||
/* The PLT relocation table. Separate from DT_REL only by where the
|
||||
* linker chose to put an entry: without -z now the imported function
|
||||
* descriptors land here instead, and the loader binds both tables
|
||||
* eagerly rather than requiring one particular layout.
|
||||
*/
|
||||
|
||||
uintptr_t pltrelvaddr;
|
||||
size_t pltrelsize;
|
||||
|
||||
/* Constructors and destructors. Both arrays live in the writable
|
||||
* segment and their entries arrive as link-time addresses carrying an
|
||||
* R_ARM_RELATIVE relocation, so they are real code addresses -- Thumb
|
||||
* bit included -- only after fdpic_bind() has run.
|
||||
*/
|
||||
|
||||
uintptr_t initvaddr;
|
||||
size_t initsize;
|
||||
uintptr_t finivaddr;
|
||||
size_t finisize;
|
||||
|
||||
uintptr_t symtabvaddr;
|
||||
uintptr_t strtabvaddr;
|
||||
};
|
||||
|
||||
/****************************************************************************
|
||||
* Inline Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: fdpic_callback
|
||||
*
|
||||
* Description:
|
||||
* Resolve a function pointer that arrived from a caller which may be an
|
||||
* FDPIC module.
|
||||
*
|
||||
* This exists because the two sides disagree about what a function
|
||||
* pointer *is*. Base firmware is not built FDPIC, so to it a function
|
||||
* pointer is a code address and it simply branches to it. An FDPIC
|
||||
* module passes the address of a two-word descriptor instead. A base
|
||||
* firmware routine that takes a callback -- qsort() and bsearch() are the
|
||||
* ones that matter -- therefore branches straight into the module's data
|
||||
* segment and faults.
|
||||
*
|
||||
* The caller is identified by the FDPIC register: a module task runs with
|
||||
* its GOT there, and a plain kernel task runs with zero, because
|
||||
* up_initial_state() only installs a value when the task has a D-Space.
|
||||
*
|
||||
* Only the entry point is taken from the descriptor. The data base is
|
||||
* already correct in the register: the base firmware is built with that
|
||||
* register reserved, so the module's GOT survives the call in.
|
||||
*
|
||||
* Input Parameters:
|
||||
* fn - The pointer as it was received.
|
||||
*
|
||||
* Returned Value:
|
||||
* An address that can be branched to directly.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#if defined(CONFIG_FDPIC) && defined(__thumb__)
|
||||
static inline FAR void *fdpic_callback(FAR void *fn)
|
||||
{
|
||||
uintptr_t base;
|
||||
|
||||
__asm__ __volatile__ ("mov %0, r9" : "=r"(base));
|
||||
|
||||
if (base != 0 && fn != NULL)
|
||||
{
|
||||
return (FAR void *)((FAR struct fdpic_desc_s *)fn)->entry;
|
||||
}
|
||||
|
||||
return fn;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: fdpic_base
|
||||
*
|
||||
* Description:
|
||||
* The FDPIC data base of the calling context, taken from the FDPIC
|
||||
* register. Non-zero means the caller is an FDPIC module; zero means base
|
||||
* firmware. This is the same test fdpic_callback() makes, exposed for a
|
||||
* caller that must decide whether a pointer it was handed is a descriptor
|
||||
* before it stores it somewhere the register will no longer be correct --
|
||||
* a callback that will run on a work-queue thread, for instance.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static inline uintptr_t fdpic_base(void)
|
||||
{
|
||||
uintptr_t base;
|
||||
|
||||
__asm__ __volatile__ ("mov %0, r9" : "=r"(base));
|
||||
|
||||
return base;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: fdpic_invoke
|
||||
*
|
||||
* Description:
|
||||
* Call a resolved module entry point with the module's data base installed
|
||||
* in the FDPIC register, and restore the caller's base afterwards.
|
||||
*
|
||||
* This is for the one case the register cannot already be correct: a
|
||||
* callback that a module registered but that runs on a shared thread --
|
||||
* the signal-notification work queue -- which carries no module's base.
|
||||
* The base is captured at registration (fdpic_base(), in the module's
|
||||
* context) and installed here around the call. Everywhere else the
|
||||
* callback runs in a task that inherited the module's data space and only
|
||||
* the entry point has to be resolved; there fdpic_callback() is enough.
|
||||
*
|
||||
* A context switch or interrupt during the call is safe: the FDPIC
|
||||
* register is REG_PIC in the saved register context, so it is preserved
|
||||
* and restored across a switch, and base firmware is built with it
|
||||
* reserved so no interrupt handler disturbs it.
|
||||
*
|
||||
* Input Parameters:
|
||||
* entry - The code address to enter (already resolved from the
|
||||
* descriptor).
|
||||
* arg - The single word argument, passed in r0.
|
||||
* got - The module data base to install in the FDPIC register.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static inline void fdpic_invoke(uintptr_t entry, uintptr_t arg,
|
||||
uintptr_t got)
|
||||
{
|
||||
register uintptr_t r0v __asm__ ("r0") = arg;
|
||||
|
||||
/* arg is pinned in r0 (the first argument and the call's scratch), so the
|
||||
* asm needs registers only for entry and got -- kept deliberately few so
|
||||
* the allocator has room on builds that reserve a frame pointer. r9 is
|
||||
* saved on the stack rather than in a scratch register; r4 rides along
|
||||
* only to keep the push 8-byte aligned and is restored untouched.
|
||||
*/
|
||||
|
||||
__asm__ __volatile__
|
||||
(
|
||||
"push {r4, r9}\n" /* Save the caller's FDPIC register */
|
||||
"mov r9, %[got]\n" /* Install the module's data base */
|
||||
"blx %[entry]\n" /* Enter the module */
|
||||
"pop {r4, r9}\n" /* Restore the caller's FDPIC register */
|
||||
: "+r" (r0v)
|
||||
: [entry] "r" (entry), [got] "r" (got)
|
||||
: "r1", "r2", "r3", "r12", "lr", "cc", "memory"
|
||||
);
|
||||
}
|
||||
#else
|
||||
# define fdpic_callback(fn) (fn)
|
||||
# define fdpic_base() (0)
|
||||
# define fdpic_invoke(entry, arg, got) \
|
||||
((void)(got), (((CODE void (*)(uintptr_t))(uintptr_t)(entry))(arg)))
|
||||
#endif
|
||||
|
||||
#endif /* __INCLUDE_NUTTX_BINFMT_FDPIC_H */
|
||||
|
|
@ -68,6 +68,11 @@ struct sigwork_s
|
|||
struct work_s work; /* Work queue structure */
|
||||
union sigval value; /* Data passed with notification */
|
||||
sigev_notify_function_t func; /* Notification function */
|
||||
#ifdef CONFIG_FDPIC
|
||||
uintptr_t got; /* FDPIC data base of a module callback, or
|
||||
* zero. Captured at registration, installed
|
||||
* around the call on the worker thread. */
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
|
|
|||
|
|
@ -31,6 +31,10 @@
|
|||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef CONFIG_FDPIC
|
||||
# include <nuttx/binfmt/fdpic.h>
|
||||
#endif
|
||||
|
||||
#include "libc.h"
|
||||
|
||||
/* The scandir() function is not appropriate for use within the kernel in its
|
||||
|
|
@ -91,6 +95,19 @@ int scandir(FAR const char *path, FAR struct dirent ***namelist,
|
|||
* the original errno value to be able to restore it in case of success.
|
||||
*/
|
||||
|
||||
#ifdef CONFIG_FDPIC
|
||||
/* An FDPIC module passes the address of a function descriptor, not a code
|
||||
* address. Resolve the filter, which is called from the loop below.
|
||||
*
|
||||
* compar is deliberately NOT resolved here. It is handed to qsort(),
|
||||
* whose public entry point resolves it, and resolving it twice would
|
||||
* treat an already-resolved code address as a descriptor.
|
||||
*/
|
||||
|
||||
filter = (CODE int (*)(FAR const struct dirent *))
|
||||
fdpic_callback((FAR void *)filter);
|
||||
#endif
|
||||
|
||||
errsv = get_errno();
|
||||
|
||||
dirp = opendir(path);
|
||||
|
|
|
|||
|
|
@ -30,6 +30,10 @@
|
|||
|
||||
#include <nuttx/pthread.h>
|
||||
|
||||
#ifdef CONFIG_FDPIC
|
||||
# include <nuttx/binfmt/fdpic.h>
|
||||
#endif
|
||||
|
||||
/****************************************************************************
|
||||
* Private Functions
|
||||
****************************************************************************/
|
||||
|
|
@ -88,6 +92,20 @@ static void pthread_startup(pthread_startroutine_t entry,
|
|||
int pthread_create(FAR pthread_t *thread, FAR const pthread_attr_t *attr,
|
||||
pthread_startroutine_t pthread_entry, pthread_addr_t arg)
|
||||
{
|
||||
#ifdef CONFIG_FDPIC
|
||||
/* An FDPIC module passes the address of a function descriptor, not a code
|
||||
* address. Resolve it here, once, in the public entry point.
|
||||
*
|
||||
* The new thread inherits the creator's D-Space -- nxtask_dup_dspace()
|
||||
* runs before up_initial_state() installs it in the FDPIC register -- so
|
||||
* it starts with the module's own data base already in place, and needs
|
||||
* only the code address.
|
||||
*/
|
||||
|
||||
pthread_entry = (pthread_startroutine_t)
|
||||
fdpic_callback((FAR void *)pthread_entry);
|
||||
#endif
|
||||
|
||||
return nx_pthread_create(pthread_startup, thread, attr, pthread_entry,
|
||||
arg);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,10 @@
|
|||
#include <nuttx/mutex.h>
|
||||
#include <nuttx/debug.h>
|
||||
|
||||
#ifdef CONFIG_FDPIC
|
||||
# include <nuttx/binfmt/fdpic.h>
|
||||
#endif
|
||||
|
||||
/****************************************************************************
|
||||
* Public Functions
|
||||
****************************************************************************/
|
||||
|
|
@ -73,6 +77,21 @@ int pthread_once(FAR pthread_once_t *once_control,
|
|||
return EINVAL;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_FDPIC
|
||||
/* An FDPIC module passes the address of a function descriptor, not a code
|
||||
* address. Resolve it here, in the public entry point.
|
||||
*
|
||||
* init_routine() runs on this thread, so the module's data base is
|
||||
* already in the FDPIC register and only the code address is needed. The
|
||||
* resolved value is a local copy and is never stored, so a later call
|
||||
* through the same once_control resolves the caller's descriptor afresh
|
||||
* rather than re-resolving a code address.
|
||||
*/
|
||||
|
||||
init_routine = (CODE void (*)(void))
|
||||
fdpic_callback((FAR void *)init_routine);
|
||||
#endif
|
||||
|
||||
if (!once_control->done)
|
||||
{
|
||||
pthread_mutex_lock(&once_control->mutex);
|
||||
|
|
|
|||
|
|
@ -71,6 +71,14 @@ _sa_handler_t signal(int signo, _sa_handler_t func)
|
|||
|
||||
DEBUGASSERT(func != SIG_ERR && func != SIG_HOLD);
|
||||
|
||||
/* An FDPIC module passes the address of a function descriptor rather than
|
||||
* a code address, but it is not resolved here: sigaction() then
|
||||
* nxsig_action() resolves the handler in the innermost common code, which
|
||||
* covers both this path and a module that calls sigaction() directly.
|
||||
* Resolving here as well would resolve it twice and branch through a code
|
||||
* address as if it were a descriptor.
|
||||
*/
|
||||
|
||||
/* Initialize the sigaction structure */
|
||||
|
||||
act.sa_handler = func;
|
||||
|
|
|
|||
|
|
@ -37,6 +37,10 @@
|
|||
****************************************************************************/
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef CONFIG_FDPIC
|
||||
# include <nuttx/binfmt/fdpic.h>
|
||||
#endif
|
||||
#include <assert.h>
|
||||
|
||||
/****************************************************************************
|
||||
|
|
@ -114,6 +118,13 @@ FAR void *bsearch(FAR const void *key, FAR const void *base, size_t nel,
|
|||
DEBUGASSERT(base != NULL || nel == 0);
|
||||
DEBUGASSERT(compar != NULL);
|
||||
|
||||
#ifdef CONFIG_FDPIC
|
||||
/* See qsort(): an FDPIC caller passes a descriptor, not a code address */
|
||||
|
||||
compar = (CODE int (*)(FAR const void *, FAR const void *))
|
||||
fdpic_callback((FAR void *)compar);
|
||||
#endif
|
||||
|
||||
for (lim = nel, lower = (const char *)base; lim != 0; lim >>= 1)
|
||||
{
|
||||
middle = lower + (lim >> 1) * width;
|
||||
|
|
|
|||
|
|
@ -45,6 +45,10 @@
|
|||
#include <sys/param.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef CONFIG_FDPIC
|
||||
# include <nuttx/binfmt/fdpic.h>
|
||||
#endif
|
||||
|
||||
/****************************************************************************
|
||||
* Pre-processor Definitions
|
||||
****************************************************************************/
|
||||
|
|
@ -156,8 +160,9 @@ static inline FAR char *med3(FAR char *a, FAR char *b, FAR char *c,
|
|||
*
|
||||
****************************************************************************/
|
||||
|
||||
void qsort(FAR void *base, size_t nel, size_t width,
|
||||
CODE int(*compar)(FAR const void *, FAR const void *))
|
||||
static void qsort_internal(FAR void *base, size_t nel, size_t width,
|
||||
CODE int(*compar)(FAR const void *,
|
||||
FAR const void *))
|
||||
{
|
||||
FAR char *pa;
|
||||
FAR char *pb;
|
||||
|
|
@ -277,7 +282,7 @@ loop:
|
|||
|
||||
if ((r = pb - pa) > width)
|
||||
{
|
||||
qsort(base, r / width, width, compar);
|
||||
qsort_internal(base, r / width, width, compar);
|
||||
}
|
||||
|
||||
if ((r = pd - pc) > width)
|
||||
|
|
@ -289,3 +294,31 @@ loop:
|
|||
goto loop;
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: qsort
|
||||
*
|
||||
* Description:
|
||||
* Public entry point. Resolves the comparison function once and then
|
||||
* hands an ordinary pointer to the implementation.
|
||||
*
|
||||
* The split matters: qsort_internal() recurses, and resolving on every
|
||||
* entry would treat an already-resolved code address as a descriptor the
|
||||
* second time round and branch somewhere meaningless.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
void qsort(FAR void *base, size_t nel, size_t width,
|
||||
CODE int(*compar)(FAR const void *, FAR const void *))
|
||||
{
|
||||
#ifdef CONFIG_FDPIC
|
||||
/* An FDPIC module passes the address of a function descriptor, not a
|
||||
* code address.
|
||||
*/
|
||||
|
||||
compar = (CODE int (*)(FAR const void *, FAR const void *))
|
||||
fdpic_callback((FAR void *)compar);
|
||||
#endif
|
||||
|
||||
qsort_internal(base, nel, width, compar);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,10 @@
|
|||
#include <nuttx/irq.h>
|
||||
#include <nuttx/sched.h>
|
||||
|
||||
#if defined(CONFIG_FDPIC) && defined(CONFIG_SIG_EVTHREAD)
|
||||
# include <nuttx/binfmt/fdpic.h>
|
||||
#endif
|
||||
|
||||
#include "sched/sched.h"
|
||||
#include "mqueue/mqueue.h"
|
||||
|
||||
|
|
@ -156,6 +160,19 @@ int mq_notify(mqd_t mqdes, FAR const struct sigevent *notification)
|
|||
sizeof(struct sigevent));
|
||||
|
||||
msgq->ntpid = rtcb->pid;
|
||||
|
||||
#if defined(CONFIG_FDPIC) && defined(CONFIG_SIG_EVTHREAD)
|
||||
/* If a module registered a SIGEV_THREAD callback, capture its data
|
||||
* base now, while this runs in the module's context. The callback
|
||||
* fires later on a work-queue worker that has no base of its own;
|
||||
* nxsig_notification() reads this to install it around the call.
|
||||
*/
|
||||
|
||||
msgq->ntwork.got =
|
||||
(fdpic_base() != 0 &&
|
||||
(notification->sigev_notify & SIGEV_THREAD) != 0) ?
|
||||
fdpic_base() : 0;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,10 @@
|
|||
#include <nuttx/signal.h>
|
||||
#include <nuttx/spinlock.h>
|
||||
|
||||
#ifdef CONFIG_FDPIC
|
||||
# include <nuttx/binfmt/fdpic.h>
|
||||
#endif
|
||||
|
||||
#include "sched/sched.h"
|
||||
#include "group/group.h"
|
||||
#include "signal/signal.h"
|
||||
|
|
@ -326,6 +330,27 @@ int nxsig_action(int signo, FAR const struct sigaction *act,
|
|||
|
||||
handler = act->sa_handler;
|
||||
|
||||
#ifdef CONFIG_FDPIC
|
||||
/* An FDPIC module passes the address of a function descriptor, not a code
|
||||
* address. Resolve it here, in the innermost common code, so a module
|
||||
* that calls sigaction() directly is covered as well as one that goes
|
||||
* through signal(), and each exactly once -- signal() passes its argument
|
||||
* through unresolved for that reason.
|
||||
*
|
||||
* The dispositions have to be excluded by hand. SIG_ERR, SIG_IGN,
|
||||
* SIG_DFL and SIG_HOLD are the small integers -1, 0, 1 and 2 rather than
|
||||
* addresses, and fdpic_callback() only declines to dereference NULL.
|
||||
* sa_handler and sa_sigaction are a union, so this one resolution serves
|
||||
* both handler forms.
|
||||
*/
|
||||
|
||||
if (handler != SIG_ERR && handler != SIG_IGN && handler != SIG_DFL &&
|
||||
handler != SIG_HOLD)
|
||||
{
|
||||
handler = (_sa_handler_t)fdpic_callback((FAR void *)handler);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_SIG_DEFAULT
|
||||
/* If the caller is setting the handler to SIG_DFL, then we need to
|
||||
* replace this with the correct, internal default signal action handler.
|
||||
|
|
|
|||
|
|
@ -34,6 +34,10 @@
|
|||
|
||||
#include <nuttx/signal.h>
|
||||
|
||||
#ifdef CONFIG_FDPIC
|
||||
# include <nuttx/binfmt/fdpic.h>
|
||||
#endif
|
||||
|
||||
#include "sched/sched.h"
|
||||
#include "signal/signal.h"
|
||||
|
||||
|
|
@ -70,7 +74,24 @@ static void nxsig_notification_worker(FAR void *arg)
|
|||
|
||||
/* Perform the callback */
|
||||
|
||||
work->func(work->value);
|
||||
#ifdef CONFIG_FDPIC
|
||||
/* A module's callback runs here on a shared worker thread, which does not
|
||||
* carry the module's data base. Install the base captured at
|
||||
* registration around the call so the callback can reach its own globals;
|
||||
* work->func has already been resolved to the code address. A non-module
|
||||
* callback has a zero base and is called directly.
|
||||
*/
|
||||
|
||||
if (work->got != 0)
|
||||
{
|
||||
fdpic_invoke((uintptr_t)work->func, (uintptr_t)work->value.sival_ptr,
|
||||
work->got);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
work->func(work->value);
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* CONFIG_SIG_EVTHREAD */
|
||||
|
|
@ -157,6 +178,22 @@ int nxsig_notification(pid_t pid, FAR struct sigevent *event,
|
|||
work->value = event->sigev_value;
|
||||
work->func = event->sigev_notify_function;
|
||||
|
||||
#ifdef CONFIG_FDPIC
|
||||
/* When the callback is a module's, work->got was set at registration
|
||||
* to the module's data base (this runs at send or expiry time, whose
|
||||
* context is not the module's, so it cannot be read here). The
|
||||
* callback is a descriptor: resolve it to the code address now --
|
||||
* reading the descriptor is just a memory access and needs no base --
|
||||
* and the worker installs the base around the call.
|
||||
*/
|
||||
|
||||
if (work->got != 0)
|
||||
{
|
||||
work->func = (sigev_notify_function_t)
|
||||
((FAR struct fdpic_desc_s *)event->sigev_notify_function)->entry;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Then queue the work */
|
||||
|
||||
return work_queue(SIG_EVTHREAD_WORK, &work->work,
|
||||
|
|
|
|||
|
|
@ -37,6 +37,10 @@
|
|||
#include <nuttx/kthread.h>
|
||||
#include <nuttx/fs/fs.h>
|
||||
|
||||
#ifdef CONFIG_FDPIC
|
||||
# include <nuttx/binfmt/fdpic.h>
|
||||
#endif
|
||||
|
||||
#include "sched/sched.h"
|
||||
#include "group/group.h"
|
||||
#include "task/task.h"
|
||||
|
|
@ -202,8 +206,23 @@ int task_create_with_stack(FAR const char *name, int priority,
|
|||
FAR void *stack_addr, int stack_size,
|
||||
main_t entry, FAR char * const argv[])
|
||||
{
|
||||
int ret = nxtask_create(name, priority, stack_addr,
|
||||
stack_size, entry, argv, NULL);
|
||||
int ret;
|
||||
|
||||
#ifdef CONFIG_FDPIC
|
||||
/* An FDPIC module passes the address of a function descriptor, not a code
|
||||
* address. Resolving it here covers task_create() too, which is a plain
|
||||
* forwarder -- and covers it exactly once, which matters: resolving twice
|
||||
* would treat an already-resolved code address as a descriptor.
|
||||
*
|
||||
* The new task inherits the creator's D-Space, so it starts with the
|
||||
* module's own data base installed and needs only the code address.
|
||||
*/
|
||||
|
||||
entry = (main_t)fdpic_callback((FAR void *)entry);
|
||||
#endif
|
||||
|
||||
ret = nxtask_create(name, priority, stack_addr,
|
||||
stack_size, entry, argv, NULL);
|
||||
if (ret < 0)
|
||||
{
|
||||
set_errno(-ret);
|
||||
|
|
|
|||
|
|
@ -38,6 +38,10 @@
|
|||
#include <nuttx/kthread.h>
|
||||
#include <nuttx/spawn.h>
|
||||
|
||||
#ifdef CONFIG_FDPIC
|
||||
# include <nuttx/binfmt/fdpic.h>
|
||||
#endif
|
||||
|
||||
#include "sched/sched.h"
|
||||
#include "group/group.h"
|
||||
#include "task/spawn.h"
|
||||
|
|
@ -335,6 +339,17 @@ int task_spawn(FAR const char *name, main_t entry,
|
|||
pid_t pid = INVALID_PROCESS_ID;
|
||||
int ret;
|
||||
|
||||
#ifdef CONFIG_FDPIC
|
||||
/* An FDPIC module passes the address of a function descriptor, not a code
|
||||
* address. Resolve it here, once, in the public entry point.
|
||||
*
|
||||
* The new task inherits the creator's D-Space, so it starts with the
|
||||
* module's own data base installed and needs only the code address.
|
||||
*/
|
||||
|
||||
entry = (main_t)fdpic_callback((FAR void *)entry);
|
||||
#endif
|
||||
|
||||
sinfo("name=%s entry=%p file_actions=%p attr=%p argv=%p\n",
|
||||
name, entry, file_actions, attr, argv);
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,10 @@
|
|||
#include <nuttx/kmalloc.h>
|
||||
#include <nuttx/spinlock.h>
|
||||
|
||||
#if defined(CONFIG_FDPIC) && defined(CONFIG_SIG_EVTHREAD)
|
||||
# include <nuttx/binfmt/fdpic.h>
|
||||
#endif
|
||||
|
||||
#include "sched/sched.h"
|
||||
#include "timer/timer.h"
|
||||
|
||||
|
|
@ -196,6 +200,19 @@ int timer_create(clockid_t clockid, FAR struct sigevent *evp,
|
|||
/* Yes, copy the entire struct sigevent content */
|
||||
|
||||
memcpy(&ret->pt_event, evp, sizeof(struct sigevent));
|
||||
|
||||
#if defined(CONFIG_FDPIC) && defined(CONFIG_SIG_EVTHREAD)
|
||||
/* If a module registered a SIGEV_THREAD callback, capture its
|
||||
* data base now, while this runs in the module's context. The
|
||||
* callback fires later on a work-queue worker with no base of
|
||||
* its own; nxsig_notification() installs this around the call.
|
||||
*/
|
||||
|
||||
ret->pt_work.got =
|
||||
(fdpic_base() != 0 &&
|
||||
(evp->sigev_notify & SIGEV_THREAD) != 0) ?
|
||||
fdpic_base() : 0;
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
61
tools/fdpic/README.md
Normal file
61
tools/fdpic/README.md
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
# FDPIC module build tooling
|
||||
|
||||
Everything needed to build an FDPIC module out of tree: a module is an ELF
|
||||
shared object whose read-only segment the target maps straight out of flash
|
||||
and executes in place, while its writable segment is copied to RAM once per
|
||||
running instance. It links against nothing -- libc and everything else are
|
||||
imported from the firmware's exported symbol table at load time.
|
||||
|
||||
The loader that consumes these is `binfmt/fdpic.c`, enabled by `CONFIG_FDPIC`.
|
||||
The full description of the format, the toolchain and the load-time contract
|
||||
is in `Documentation/components/fdpic.rst`.
|
||||
|
||||
## Contents
|
||||
|
||||
| File | Purpose |
|
||||
| --- | --- |
|
||||
| `nuttx-fdpic.mk` | the module build itself; include it from a two-line makefile |
|
||||
| `fdpic-verify.sh` | checks a built module's imports resolve against the firmware |
|
||||
| `nuttx-exports.sh` | turns `libs/libc/exec_symtab.c` into a symbol list |
|
||||
| `fdpic-embed.py` | turns a built module into a C header, for carrying one in an image |
|
||||
| `build-binutils.sh` | builds the `arm-uclinuxfdpiceabi` binutils, the one from-source dependency |
|
||||
|
||||
## Building a module
|
||||
|
||||
A whole module is three lines of makefile beside the source. Taking
|
||||
`apps/examples/fdpicxip/modules/qsorter.c`, which is a module in its own
|
||||
right, as the source:
|
||||
|
||||
MODULE = qsorter
|
||||
SRCS = qsorter.c
|
||||
|
||||
include /path/to/nuttx/tools/fdpic/nuttx-fdpic.mk
|
||||
|
||||
Then:
|
||||
|
||||
make NUTTX_DIR=/path/to/nuttx
|
||||
CC qsorter.c
|
||||
LD qsorter.fdpic
|
||||
OK qsorter.fdpic: FDPIC, entry 0x2a1, 4 imports resolved
|
||||
|
||||
`NUTTX_DIR` has to be a configured, built tree: the compile needs its headers
|
||||
and the verify step needs the export table generated into
|
||||
`libs/libc/exec_symtab.c`.
|
||||
|
||||
Two toolchains are involved. The stock `arm-none-eabi` compiler does the
|
||||
compiling -- it emits perfectly good FDPIC objects for both C and C++ -- and
|
||||
`arm-uclinuxfdpiceabi` **binutils** does the linking, because
|
||||
`arm-none-eabi-ld` cannot produce an FDPIC object at all. So the from-source
|
||||
dependency is binutils alone, which `build-binutils.sh` builds in about a
|
||||
minute.
|
||||
|
||||
Verification runs as part of the default target on purpose: a module that
|
||||
imports a symbol the firmware does not export links perfectly happily and
|
||||
fails only once it is on the target, as a bare `-ENOENT` that names nothing.
|
||||
|
||||
## Modules carried inside an image
|
||||
|
||||
`fdpic-embed.py` exists for apps that have to load a module before there is any
|
||||
way to put files on the target, so they embed one and write it out at run
|
||||
time. `apps/examples/fdpicxip/modules/` uses it that way, and is the worked
|
||||
example of driving this tooling for several modules at once.
|
||||
80
tools/fdpic/build-binutils.sh
Executable file
80
tools/fdpic/build-binutils.sh
Executable file
|
|
@ -0,0 +1,80 @@
|
|||
#!/bin/bash
|
||||
############################################################################
|
||||
# tools/fdpic/build-binutils.sh
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership. The
|
||||
# ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance with the
|
||||
# License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
############################################################################
|
||||
|
||||
# Build arm-uclinuxfdpiceabi binutils -- the only part of the module
|
||||
# toolchain that has to be built from source.
|
||||
#
|
||||
# The compiling is done by the stock Arm bare-metal toolchain, which emits
|
||||
# correct FDPIC objects for both C and C++. What it cannot do is *link*
|
||||
# them: arm-none-eabi-ld is configured with the `armelf` emulation alone, so
|
||||
# it produces an object marked "UNIX - System V" that the loader refuses.
|
||||
# The FDPIC linker carries armelf_linux_fdpiceabi, and that is the whole of
|
||||
# the gap.
|
||||
#
|
||||
# So this builds binutils and nothing else: about a minute, roughly 23 MB.
|
||||
# An FDPIC GCC is not needed for any of this.
|
||||
#
|
||||
# Usage: build-binutils.sh <workdir> [install-prefix]
|
||||
#
|
||||
# Then add <install-prefix>/bin to PATH.
|
||||
|
||||
set -e
|
||||
|
||||
WORK="${1:?usage: build-binutils.sh <workdir> [prefix]}"
|
||||
PREFIX="${2:-$WORK/toolchain}"
|
||||
TARGET=arm-uclinuxfdpiceabi
|
||||
BINUTILS=binutils-2.43
|
||||
J="$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 4)"
|
||||
|
||||
mkdir -p "$WORK/src" "$WORK/build"
|
||||
|
||||
cd "$WORK/src"
|
||||
[ -d "$BINUTILS" ] || {
|
||||
curl -fL -O "https://ftp.gnu.org/gnu/binutils/$BINUTILS.tar.xz"
|
||||
tar xf "$BINUTILS.tar.xz"
|
||||
}
|
||||
|
||||
rm -rf "$WORK/build/binutils"
|
||||
mkdir -p "$WORK/build/binutils"
|
||||
cd "$WORK/build/binutils"
|
||||
|
||||
# --with-system-zlib because the bundled copy does not compile against the
|
||||
# macOS SDK headers. Harmless elsewhere.
|
||||
|
||||
"$WORK/src/$BINUTILS/configure" \
|
||||
--target="$TARGET" \
|
||||
--prefix="$PREFIX" \
|
||||
--disable-nls \
|
||||
--disable-werror \
|
||||
--with-system-zlib
|
||||
|
||||
make -j"$J"
|
||||
make install
|
||||
|
||||
echo
|
||||
echo "Installed to $PREFIX/bin"
|
||||
echo
|
||||
"$PREFIX/bin/$TARGET-ld" -V | head -8
|
||||
echo
|
||||
echo "armelf_linux_fdpiceabi in the list above is the one that matters."
|
||||
echo "Add to PATH: export PATH=$PREFIX/bin:\$PATH"
|
||||
114
tools/fdpic/fdpic-embed.py
Executable file
114
tools/fdpic/fdpic-embed.py
Executable file
|
|
@ -0,0 +1,114 @@
|
|||
#!/usr/bin/env python3
|
||||
############################################################################
|
||||
# tools/fdpic/fdpic-embed.py
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership. The
|
||||
# ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance with the
|
||||
# License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
############################################################################
|
||||
|
||||
#
|
||||
# fdpic-embed.py -- turn a built module into a C header the firmware can carry.
|
||||
#
|
||||
# The demo apps have to load a module before there is any way to put files
|
||||
# on the target, so they embed one and write it to the filesystem at run
|
||||
# time. This generates that header.
|
||||
#
|
||||
# fdpic-embed.py libshape.so g_libshape > libshape_bin.h
|
||||
#
|
||||
# The second argument is the symbol base: the array is <base> and its
|
||||
# length is <base>_len.
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
LICENSE = """\
|
||||
/****************************************************************************
|
||||
* %s
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership. The
|
||||
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
****************************************************************************/
|
||||
"""
|
||||
|
||||
|
||||
def main(argv):
|
||||
if len(argv) not in (3, 4):
|
||||
sys.stderr.write("usage: fdpic-embed.py <module> <symbol> [header]\n")
|
||||
return 1
|
||||
|
||||
path = argv[1]
|
||||
symbol = argv[2]
|
||||
|
||||
# Pass the header's own path relative to the repository root
|
||||
# ("apps/testing/fs/xipfs/foo_bin.h") as the third argument when the
|
||||
# result is committed: that is what nxstyle wants on line 2, and the
|
||||
# module's basename -- the default -- fails the check.
|
||||
|
||||
header = argv[3] if len(argv) > 3 else os.path.basename(path)
|
||||
|
||||
with open(path, "rb") as f:
|
||||
blob = f.read()
|
||||
|
||||
out = sys.stdout
|
||||
out.write(LICENSE % header)
|
||||
out.write(
|
||||
"\n/* Generated from %s -- do not edit.\n *\n"
|
||||
" * An FDPIC module, embedded so the demo has something to "
|
||||
"load without\n"
|
||||
" * needing a filesystem populated from the host first.\n */\n"
|
||||
% os.path.basename(path)
|
||||
)
|
||||
out.write(
|
||||
"\n/*****************************************************"
|
||||
"***********************\n"
|
||||
" * Public Data\n"
|
||||
" ****************************************************"
|
||||
"************************/\n\n"
|
||||
)
|
||||
|
||||
# static, because this is a header that defines data. More than one app
|
||||
# embeds the same module, and with external linkage the two copies
|
||||
# collide at link time as soon as both are enabled.
|
||||
|
||||
out.write("static const unsigned char %s[] =\n{\n" % symbol)
|
||||
for i in range(0, len(blob), 12):
|
||||
row = ", ".join("0x%02x" % b for b in blob[i : i + 12])
|
||||
out.write(" %s%s\n" % (row, "," if i + 12 < len(blob) else ""))
|
||||
out.write("};\n\n")
|
||||
out.write("static const unsigned int %s_len = %d;\n" % (symbol, len(blob)))
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv))
|
||||
99
tools/fdpic/fdpic-verify.sh
Executable file
99
tools/fdpic/fdpic-verify.sh
Executable file
|
|
@ -0,0 +1,99 @@
|
|||
#!/bin/sh
|
||||
############################################################################
|
||||
# tools/fdpic/fdpic-verify.sh
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership. The
|
||||
# ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance with the
|
||||
# License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
############################################################################
|
||||
|
||||
# Check that a built module is loadable before it ever reaches the target.
|
||||
#
|
||||
# Two things go wrong quietly:
|
||||
# - the object is not actually FDPIC (wrong toolchain, or -shared omitted),
|
||||
# which the loader rejects with a generic -ENOEXEC;
|
||||
# - it imports a symbol the firmware does not export, which the loader
|
||||
# reports as -ENOENT with no indication of which symbol.
|
||||
#
|
||||
# Usage: fdpic-verify.sh <module.fdpic> [exports-file] [lib.so ...]
|
||||
#
|
||||
# Any shared libraries the module links against are passed after the
|
||||
# exports file; the symbols they define count as satisfied, exactly as they
|
||||
# will at load time when the loader resolves DT_NEEDED.
|
||||
|
||||
set -e
|
||||
MOD="${1:?usage: fdpic-verify.sh <module> [exports-file] [libs...]}"
|
||||
EXPORTS="$2"
|
||||
shift 2 2>/dev/null || shift $#
|
||||
LIBS="$*"
|
||||
READELF="${READELF:-arm-uclinuxfdpiceabi-readelf}"
|
||||
|
||||
fail=0
|
||||
|
||||
osabi=$("$READELF" -h "$MOD" | sed -n 's/.*OS\/ABI:[[:space:]]*//p')
|
||||
case "$osabi" in
|
||||
*"ARM FDPIC"*) ;;
|
||||
*)
|
||||
echo "FAIL not an FDPIC object (OS/ABI: ${osabi:-unknown})"
|
||||
echo " check the toolchain is arm-uclinuxfdpiceabi and that"
|
||||
echo " the link used -Wl,-shared"
|
||||
fail=1
|
||||
;;
|
||||
esac
|
||||
|
||||
etype=$("$READELF" -h "$MOD" | sed -n 's/.*Type:[[:space:]]*\([A-Z]*\).*/\1/p')
|
||||
[ "$etype" = "DYN" ] || { echo "FAIL e_type is $etype, expected DYN"; fail=1; }
|
||||
|
||||
nload=$("$READELF" -lW "$MOD" | grep -c '^ LOAD' || true)
|
||||
[ "$nload" -ge 2 ] || {
|
||||
echo "FAIL expected 2 LOAD segments (RX + RW), found $nload"; fail=1; }
|
||||
|
||||
# Undefined dynamic symbols are the module's imports
|
||||
imports=$("$READELF" --dyn-syms -W "$MOD" \
|
||||
| awk '$7 == "UND" && $8 != "" { print $8 }' | sort -u)
|
||||
|
||||
if [ -n "$EXPORTS" ] && [ -f "$EXPORTS" ]; then
|
||||
tmp=$(mktemp)
|
||||
avail=$(mktemp)
|
||||
echo "$imports" > "$tmp"
|
||||
cat "$EXPORTS" > "$avail"
|
||||
|
||||
# Symbols the linked libraries define are resolved at load time
|
||||
for lib in $LIBS; do
|
||||
[ -f "$lib" ] || continue
|
||||
"$READELF" --dyn-syms -W "$lib" \
|
||||
| awk '$7 != "UND" && $4 != "SECTION" && $8 != "" { print $8 }' \
|
||||
>> "$avail"
|
||||
done
|
||||
|
||||
sort -u "$avail" -o "$avail"
|
||||
missing=$(comm -23 "$tmp" "$avail" || true)
|
||||
rm -f "$tmp" "$avail"
|
||||
if [ -n "$missing" ]; then
|
||||
echo "FAIL imports the firmware does not export:"
|
||||
echo "$missing" | sed 's/^/ /'
|
||||
fail=1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$fail" -eq 0 ]; then
|
||||
entry=$("$READELF" -h "$MOD" | sed -n 's/.*Entry point address:[[:space:]]*//p')
|
||||
nimp=$(echo "$imports" | grep -c . || true)
|
||||
echo "OK $(basename "$MOD"): FDPIC, entry $entry, $nimp imports resolved"
|
||||
fi
|
||||
|
||||
exit $fail
|
||||
102
tools/fdpic/nuttx-exports.sh
Executable file
102
tools/fdpic/nuttx-exports.sh
Executable file
|
|
@ -0,0 +1,102 @@
|
|||
#!/bin/sh
|
||||
############################################################################
|
||||
# tools/fdpic/nuttx-exports.sh
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership. The
|
||||
# ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance with the
|
||||
# License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
############################################################################
|
||||
|
||||
# Print the symbols the firmware exports to modules, one per line.
|
||||
#
|
||||
# A module links with -shared, so undefined symbols are permitted and the
|
||||
# link succeeds even for a symbol the firmware does not provide. The
|
||||
# failure then surfaces at load time as a bare -ENOENT. This list is what
|
||||
# lets that be caught at build time instead.
|
||||
#
|
||||
# exec_symtab.c is generated by NuttX's tools/mksymtab from the libc, libm
|
||||
# and syscall CSVs, and most of its entries sit behind #if defined(CONFIG_...)
|
||||
# guards taken from a condition column in those files. The table NuttX
|
||||
# compiles is therefore correct; it is reading the *source* that is not.
|
||||
#
|
||||
# So the file is run through the C preprocessor before the names are pulled
|
||||
# out. Anything else gets it wrong in one direction or the other:
|
||||
#
|
||||
# * plain sed over the source ignores the guards and offers every symbol
|
||||
# that could be exported rather than the ones that were. On one
|
||||
# ordinary configuration that was 127 of 668 -- dlopen and the rest of
|
||||
# dlfcn, the socket calls, alarm, the aio family. A module importing one
|
||||
# linked cleanly, passed this check, and failed at load with a bare
|
||||
# -ENOENT.
|
||||
#
|
||||
# * intersecting with nm on the linked firmware is closer but still wrong
|
||||
# both ways. It offers symbols that are in the binary yet absent from
|
||||
# the table (flockfile, task_testcancel), and withholds crc32, which is
|
||||
# a macro: the table entry reads { "crc32", crc32full }, so the name
|
||||
# resolves at load time even though no symbol called crc32 exists.
|
||||
#
|
||||
# What the loader matches is the name string in the table, so the
|
||||
# preprocessed table is exactly the right question to ask. Membership in it
|
||||
# is also sufficient: if a name is there, the link had to resolve its value,
|
||||
# so the firmware has it.
|
||||
#
|
||||
# Usage: nuttx-exports.sh <nuttx-dir>
|
||||
#
|
||||
# CC may be set to override the compiler used to preprocess.
|
||||
|
||||
set -e
|
||||
NUTTX="${1:?usage: nuttx-exports.sh <nuttx-dir>}"
|
||||
SYMTAB="$NUTTX/libs/libc/exec_symtab.c"
|
||||
|
||||
if [ ! -f "$SYMTAB" ]; then
|
||||
echo "nuttx-exports.sh: $SYMTAB not found." >&2
|
||||
echo " Build NuttX with CONFIG_EXECFUNCS_SYSTEM_SYMTAB=y first." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$CC" ]; then
|
||||
for candidate in arm-none-eabi-gcc cc gcc; do
|
||||
if command -v "$candidate" >/dev/null 2>&1; then
|
||||
CC="$candidate"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ -z "$CC" ]; then
|
||||
echo "nuttx-exports.sh: no compiler found; set CC." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# The guards read CONFIG_* out of nuttx/config.h, which the configure step
|
||||
# generates into the tree, so no -D flags are needed beyond the include path.
|
||||
|
||||
if ! "$CC" -E -P -I "$NUTTX/include" -D__NuttX__ "$SYMTAB" \
|
||||
> "${TMPDIR:-/tmp}/nuttx-exports.$$" 2>"${TMPDIR:-/tmp}/nuttx-exports-err.$$"
|
||||
then
|
||||
echo "nuttx-exports.sh: failed to preprocess $SYMTAB with $CC:" >&2
|
||||
head -5 "${TMPDIR:-/tmp}/nuttx-exports-err.$$" >&2
|
||||
echo " A configured and built tree is required." >&2
|
||||
rm -f "${TMPDIR:-/tmp}/nuttx-exports.$$" \
|
||||
"${TMPDIR:-/tmp}/nuttx-exports-err.$$"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sed -n 's/^[[:space:]]*{[[:space:]]*"\([^"]*\)".*/\1/p' \
|
||||
"${TMPDIR:-/tmp}/nuttx-exports.$$" | sort -u
|
||||
|
||||
rm -f "${TMPDIR:-/tmp}/nuttx-exports.$$" "${TMPDIR:-/tmp}/nuttx-exports-err.$$"
|
||||
221
tools/fdpic/nuttx-fdpic.mk
Normal file
221
tools/fdpic/nuttx-fdpic.mk
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
############################################################################
|
||||
# tools/fdpic/nuttx-fdpic.mk
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership. The
|
||||
# ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance with the
|
||||
# License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
############################################################################
|
||||
|
||||
############################################################################
|
||||
# tools/fdpic/nuttx-fdpic.mk
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership. The
|
||||
# ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance with the
|
||||
# License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
############################################################################
|
||||
|
||||
# nuttx-fdpic.mk -- build out-of-tree FDPIC modules for NuttX
|
||||
#
|
||||
# A module is an ELF shared object whose read-only segment the target maps
|
||||
# straight out of flash and executes in place, and whose writable segment is
|
||||
# copied to RAM once per running instance. It links against nothing: libc
|
||||
# and everything else are imported from the firmware's exported symbol table
|
||||
# at load time.
|
||||
#
|
||||
# Usage -- a whole module is this:
|
||||
#
|
||||
# MODULE = hello
|
||||
# SRCS = hello.c
|
||||
# include /path/to/nuttx/tools/fdpic/nuttx-fdpic.mk
|
||||
#
|
||||
# C++ sources go in CXXSRCS instead of SRCS.
|
||||
#
|
||||
# Two toolchains are involved, and the split is the whole trick:
|
||||
#
|
||||
# * the stock Arm bare-metal compiler does the compiling. It emits
|
||||
# perfectly good FDPIC objects for both C and C++.
|
||||
# * arm-uclinuxfdpiceabi *binutils* does the linking, because
|
||||
# arm-none-eabi-ld is built with only the `armelf` emulation and cannot
|
||||
# produce an FDPIC object at all -- it silently marks the output
|
||||
# "UNIX - System V" and the loader refuses it.
|
||||
#
|
||||
# So the from-source dependency is binutils alone, which takes about a
|
||||
# minute to build. No FDPIC GCC is needed. See build-binutils.sh beside
|
||||
# this file, and Documentation/components/fdpic.rst.
|
||||
#
|
||||
# Required:
|
||||
# NUTTX_DIR a configured, built NuttX tree (headers + export list)
|
||||
#
|
||||
# Optional:
|
||||
# ARM_TOOLCHAIN prefix of the bare-metal compiler; default arm-none-eabi
|
||||
# FDPIC_TOOLCHAIN prefix of the FDPIC binutils; default
|
||||
# arm-uclinuxfdpiceabi
|
||||
# CPU default cortex-m33
|
||||
# ENTRY entry symbol; default main, use 0 for a library
|
||||
# OPT default -Os
|
||||
# LIBS shared libraries to link against
|
||||
# BINDNOW '-z now' by default; set empty to leave imported
|
||||
# descriptors in the lazy binding table (DT_JMPREL)
|
||||
# EXTRA_CFLAGS EXTRA_CXXFLAGS EXTRA_LDFLAGS
|
||||
#
|
||||
# EXTRA_LDFLAGS is passed straight to ld, not through a compiler driver, so
|
||||
# it takes bare linker flags: `-soname libfoo.so`, not `-Wl,-soname,libfoo.so`.
|
||||
|
||||
MODULE ?= module
|
||||
SRCS ?=
|
||||
CXXSRCS ?=
|
||||
CPU ?= cortex-m33
|
||||
ENTRY ?= main
|
||||
OPT ?= -Os
|
||||
|
||||
ARM_TOOLCHAIN ?= arm-none-eabi
|
||||
FDPIC_TOOLCHAIN ?= arm-uclinuxfdpiceabi
|
||||
|
||||
FDPIC_DIR := $(patsubst %/,%,$(dir $(abspath $(lastword $(MAKEFILE_LIST)))))
|
||||
|
||||
ifeq ($(NUTTX_DIR),)
|
||||
$(error Set NUTTX_DIR to a configured, built NuttX tree)
|
||||
endif
|
||||
|
||||
# make has built-in defaults for CC and CXX, so ?= never fires for them and
|
||||
# the host compiler silently gets the job. Test the origin instead.
|
||||
|
||||
ifeq ($(origin CC),default)
|
||||
CC := $(ARM_TOOLCHAIN)-gcc
|
||||
endif
|
||||
|
||||
ifeq ($(origin CXX),default)
|
||||
CXX := $(ARM_TOOLCHAIN)-g++
|
||||
endif
|
||||
|
||||
LD := $(FDPIC_TOOLCHAIN)-ld
|
||||
READELF := $(FDPIC_TOOLCHAIN)-readelf
|
||||
|
||||
# Common compile flags.
|
||||
#
|
||||
# -mfdpic is stated rather than assumed, so a mis-set toolchain fails loudly
|
||||
# instead of quietly producing a plain ELF the loader will refuse.
|
||||
#
|
||||
# -fPIC is not optional and not implied. -mfdpic alone does not turn on PIC
|
||||
# under the bare-metal compiler, and without it the link emits TEXTREL --
|
||||
# text relocations -- which cannot work when the text is executed in place
|
||||
# out of read-only flash.
|
||||
#
|
||||
# -fno-builtin keeps GCC from open-coding calls into libc routines the
|
||||
# module is supposed to import from the firmware.
|
||||
#
|
||||
# __STDC_NO_ATOMICS__ steers NuttX's <nuttx/atomic.h> away from the branch
|
||||
# that includes <stdatomic.h> and then redefines its macros. The effect is
|
||||
# that a module using C11 atomics gets NuttX's implementation -- the same one
|
||||
# the firmware uses -- rather than the compiler's.
|
||||
|
||||
MODCOMMON = -mcpu=$(CPU) -mthumb -mfdpic -fPIC $(OPT) \
|
||||
-fno-builtin -Wall -Wa,--noexecstack \
|
||||
-D__STDC_NO_ATOMICS__ -D__NuttX__
|
||||
|
||||
MODCFLAGS = $(MODCOMMON) -I$(NUTTX_DIR)/include $(EXTRA_CFLAGS)
|
||||
|
||||
# C++ adds three flags, none of them optional.
|
||||
#
|
||||
# -fno-use-cxa-atexit, because the default registers each static object's
|
||||
# destructor with __cxa_atexit(dtor, obj, &__dso_handle), and __dso_handle
|
||||
# comes from crtbegin, which a module does not link. The link fails
|
||||
# outright with "hidden symbol `__dso_handle' isn't defined". Turning it off
|
||||
# also puts the destructors in .fini_array, which is what the loader walks on
|
||||
# unload -- so the flag that makes the link work is also the flag that makes
|
||||
# destructors run.
|
||||
#
|
||||
# -fno-exceptions -fno-rtti, because both need libsupc++, which a module
|
||||
# linking against nothing cannot reach.
|
||||
|
||||
MODCXXFLAGS = $(MODCOMMON) \
|
||||
-fno-exceptions -fno-rtti -fno-use-cxa-atexit \
|
||||
-I$(NUTTX_DIR)/include/cxx -I$(NUTTX_DIR)/include \
|
||||
$(EXTRA_CXXFLAGS)
|
||||
|
||||
# Link flags, passed straight to ld.
|
||||
#
|
||||
# -shared is load bearing and easy to get wrong. It is what preserves
|
||||
# R_ARM_FUNCDESC_VALUE relocations for imported symbols. Linking as a PIE
|
||||
# with --unresolved-symbols=ignore-all also appears to work, but silently
|
||||
# degrades every import to R_ARM_NONE, and the module then branches to zero
|
||||
# on its first call out.
|
||||
#
|
||||
# The emulation has to be named because this ld supports four.
|
||||
#
|
||||
# -z now keeps imported function descriptors in DT_REL rather than the lazy
|
||||
# binding table DT_JMPREL. The loader binds both, so this is a default rather
|
||||
# than a requirement: it keeps built modules on the layout that has had the
|
||||
# most hardware exposure, and leaves the lazymod fixture, which empties
|
||||
# BINDNOW, a distinct case rather than what everything does.
|
||||
|
||||
BINDNOW ?= -z now
|
||||
|
||||
MODLDFLAGS = -m armelf_linux_fdpiceabi -shared $(BINDNOW) -e $(ENTRY) \
|
||||
$(EXTRA_LDFLAGS)
|
||||
|
||||
OBJS := $(SRCS:.c=.o) $(CXXSRCS:.cpp=.o)
|
||||
TARGET := $(MODULE).fdpic
|
||||
EXPORTS := .nuttx-exports
|
||||
|
||||
.PHONY: all clean verify exports
|
||||
|
||||
all: verify
|
||||
|
||||
$(EXPORTS): $(NUTTX_DIR)/libs/libc/exec_symtab.c
|
||||
@$(FDPIC_DIR)/nuttx-exports.sh $(NUTTX_DIR) > $@
|
||||
|
||||
exports: $(EXPORTS)
|
||||
@echo "$(shell wc -l < $(EXPORTS)) symbols exported by the firmware"
|
||||
|
||||
%.o: %.c
|
||||
@echo " CC $<"
|
||||
@$(CC) $(MODCFLAGS) -c $< -o $@
|
||||
|
||||
%.o: %.cpp
|
||||
@echo " CXX $<"
|
||||
@$(CXX) $(MODCXXFLAGS) -c $< -o $@
|
||||
|
||||
$(TARGET): $(OBJS)
|
||||
@echo " LD $@"
|
||||
@$(LD) $(MODLDFLAGS) -o $@ $(OBJS) $(LIBS)
|
||||
|
||||
# Verification is part of the default build on purpose. A module that
|
||||
# imports a symbol the firmware does not export links perfectly happily and
|
||||
# only fails once it is on the target, as a bare -ENOENT with no indication
|
||||
# of which symbol was at fault.
|
||||
|
||||
verify: $(TARGET) $(EXPORTS)
|
||||
@READELF=$(READELF) $(FDPIC_DIR)/fdpic-verify.sh \
|
||||
$(TARGET) $(EXPORTS) $(LIBS)
|
||||
|
||||
clean:
|
||||
@rm -f $(OBJS) $(TARGET) $(MODULE).so $(EXPORTS)
|
||||
Loading…
Add table
Add a link
Reference in a new issue