mirror of
https://github.com/apache/nuttx.git
synced 2025-01-13 06:18:40 +08:00
b02a0758a7
Some checks are pending
Build Documentation / build-html (push) Waiting to run
Add a migration warning to the update release system and the elf programs documentation pages. This is just to add a papertrail from where the documentation originate in case of error during the migration process. Also fix a bit of formatting in the pages
538 lines
16 KiB
ReStructuredText
538 lines
16 KiB
ReStructuredText
===============================
|
||
ELF Programs – No Symbol Tables
|
||
===============================
|
||
|
||
.. warning::
|
||
Migrated from:
|
||
https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=139629542
|
||
|
||
You can easily extend the firmware in your released, embedded system using ELF
|
||
programs provided via a file system (for example, an SD card or downloaded into
|
||
on-board SPI FLASH). In order to support such post-release updates, your
|
||
released firmware would have to support execution of fully linked, relocatable
|
||
ELF programs loaded into RAM (see, for example, ``apps/examples/elf``).
|
||
|
||
The files shown in this Wiki page can be downloaded `here <https://cwiki.apache.org/confluence/download/attachments/139629402/elfprog-nosymtab.tar.gz?version=1&modificationDate=1576735520000&api=v2>`_.
|
||
|
||
Alan Carvalho de Assis has also made a video based on this example in the
|
||
YouTube `NuttX Channel <https://www.youtube.com/watch?v=oL6KAgkTb8M>`_.
|
||
|
||
Creating the Export Package
|
||
===========================
|
||
|
||
At the time that you release the firmware, you should create and save an
|
||
export package. The export package is all that you need to create
|
||
post-release, add-on modules for your embedded system. Let's illustrate this
|
||
using the ``STM32F4-Discovery`` networking ``NSH`` configuration with the
|
||
``STM32F4DIS-BB`` baseboard. (This demonstration assumes that you also have
|
||
support for some externally modifiable media in the board configuration, such
|
||
as removable media like an SD card, or a USB FLASH stick, an internal file
|
||
system remotely accessible via USB MSC, FTP, or any remote file system (NFS).
|
||
The networking ``NSH`` configuration uses the SD card on the STM32 baseboard
|
||
for this demonstration. Other ``NSH`` configurations could be used, provided
|
||
that you supply the necessary file system support in some fashion.)
|
||
|
||
(No baseboard? You can add file system support to the basic ``STM32F4-Discovery``
|
||
board by following these instructions:
|
||
`USB FLASH drive <https://www.youtube.com/watch?v=5hB5ZXpRoS4>`_
|
||
or `SD card <https://www.youtube.com/watch?v=H28t4RbOXqI>`_.)
|
||
|
||
.. code-block:: shell
|
||
|
||
$ make distclean
|
||
$ tools/configure.sh -c stm32f4discovery:netnsh
|
||
$ make menuconfig
|
||
|
||
Your released firmware would have to have been built with a few important
|
||
configuration settings:
|
||
|
||
1. Disable networking (Only because it is not needed in this example):
|
||
|
||
.. code-block:: shell
|
||
|
||
# CONFIG_NET is not set
|
||
|
||
2. Enable basic ELF binary support with no built-in symbol table support:
|
||
|
||
.. code-block:: shell
|
||
|
||
CONFIG_ELF=y
|
||
CONFIG_LIBC_EXECFUNCS=y
|
||
# CONFIG_EXECFUNCS_HAVE_SYMTAB is not set
|
||
|
||
3. Enable PATH variable support:
|
||
|
||
.. code-block:: shell
|
||
|
||
CONFIG_BINFMT_EXEPATH=y
|
||
CONFIG_PATH_INITIAL="/bin"
|
||
# CONFIG_DISABLE_ENVIRON not set
|
||
|
||
4. Enable execution of ELF files from the ``NSH`` command line:
|
||
|
||
.. code-block:: shell
|
||
|
||
CONFIG_NSH_FILE_APPS=y
|
||
|
||
.. note::
|
||
|
||
You must enable some application that uses ``printf()``. This is necessary
|
||
to assure that the symbol ``printf()`` is included in the base system.
|
||
Here we assume that you include the "Hello, World!" example from
|
||
``apps/examples/hello``:
|
||
|
||
.. code-block:: shell
|
||
|
||
CONFIG_EXAMPLES_HELLO=y
|
||
|
||
Then we can build the NuttX firmware image and the export package:
|
||
|
||
.. code-block:: shell
|
||
|
||
$ make
|
||
$ make export
|
||
|
||
When ``make export`` completes, you will find a ZIP'ed package in the top-level
|
||
NuttX directory called ``nuttx-export-x.y.zip`` (for version ``x.y``). The
|
||
version is determined by the ``.version`` file in the same directory. The
|
||
content of this ZIP file is the following directory structure:
|
||
|
||
.. code-block:: shell
|
||
|
||
nuttx-export-x.x
|
||
|- arch/
|
||
|- build/
|
||
|- include/
|
||
|- libs/
|
||
|- startup/
|
||
|- System.map
|
||
`- .config
|
||
|
||
The Add-On Build Directory
|
||
==========================
|
||
|
||
In order to create the add-on ELF program, you will need (1) the export
|
||
package, (2) the program build ``Makefile``, (3) a linker script used by the
|
||
``Makefile``, and (4) a Bash script to create a linker script. That
|
||
``Makefile`` and Bash Script are discussed in the following paragraphs.
|
||
|
||
.. note::
|
||
|
||
These example files implicitly assume a GNU tool chain is used and, in at
|
||
least one place, that the target is an ARMv7-M platform. A non-GNU tool
|
||
chain would probably require a significantly different ``Makefile`` and
|
||
linker script. There is at least one ARMv7-M specific change that would
|
||
have to be made for other platforms in the script that creates the linker
|
||
script (``mkdefines.sh``).
|
||
|
||
Hello Example
|
||
=============
|
||
|
||
To keep things manageable, let's use a concrete example. Suppose the ELF
|
||
program that we wish to add to the release code is the single source file
|
||
``hello.c``:
|
||
|
||
.. code-block:: c
|
||
|
||
#include <stdio.h>
|
||
|
||
int main(int argc, char **argv)
|
||
{
|
||
printf("Hello from Add-On Program!\n");
|
||
return 0;
|
||
}
|
||
|
||
Let's say that we have a directory called ``addon`` and it contains the
|
||
``hello.c`` source file, a ``Makefile`` that will create the ELF program, and a
|
||
Bash script called ``mkdefines.sh`` that will create a linker script.
|
||
|
||
Building the ELF Program
|
||
========================
|
||
|
||
The first step in creating the ELF program is to unzip the Export Package. We
|
||
start with our ``addon`` directory containing the following:
|
||
|
||
.. code-block:: shell
|
||
|
||
$ cd addon
|
||
$ ls
|
||
gnu-elf.ld hello.c Makefile mkdefines.sh nuttx-export-7.25.zip
|
||
|
||
Where:
|
||
|
||
- ``gnu-elf.ld`` is the linker script.
|
||
- ``hello.c`` is our example source file.
|
||
- ``Makefile`` will build our ELF program and symbol table.
|
||
- ``mksymtab.h`` is the Bash script that will create the symbol table for the
|
||
ELF program.
|
||
- ``nuttx-export-7.25.zip`` is the Export Package for NuttX-7.25.
|
||
|
||
We unzip the Export Package like:
|
||
|
||
.. code-block:: shell
|
||
|
||
$ unzip nuttx-export-7.25.zip
|
||
|
||
Then we have a new directory called ``nuttx-export-7.25`` that contains all of
|
||
the content from the released NuttX code that we need to build the ELF
|
||
program.
|
||
|
||
The Makefile
|
||
============
|
||
|
||
The ELF program is created simply as:
|
||
|
||
.. code-block:: shell
|
||
|
||
$ make
|
||
|
||
This uses the following ``Makefile`` to generate several files:
|
||
|
||
- ``hello.o``: The compiled ``hello.c`` object.
|
||
- ``hello.r``: A "partially linked" ELF object that still has undefined
|
||
symbols.
|
||
- ``hello``: The fully linked, relocatable ELF program.
|
||
- ``linker.ld``: A linker script created by ``mkdefines.sh``.
|
||
|
||
Only the resulting ``hello`` is needed.
|
||
|
||
Below is the ``Makefile`` used to create the ELF program:
|
||
|
||
.. code-block:: shell
|
||
|
||
include nuttx-export-7.25/build/Make.defs
|
||
|
||
# Long calls are need to call from RAM into FLASH
|
||
|
||
ARCHCFLAGS += -mlong-calls
|
||
ARCHWARNINGS = -Wall -Wstrict-prototypes -Wshadow -Wundef
|
||
ARCHOPTIMIZATION = -Os -fno-strict-aliasing -fno-strength-reduce -fomit-frame-pointer
|
||
ARCHINCLUDES = -I. -isystem nuttx-export-7.25/include
|
||
|
||
CFLAGS = $(ARCHCFLAGS) $(ARCHWARNINGS) $(ARCHOPTIMIZATION) $(ARCHINCLUDES) -pipe
|
||
|
||
CROSSDEV = arm-none-eabi-
|
||
CC = $(CROSSDEV)gcc
|
||
LD = $(CROSSDEV)ld
|
||
STRIP = $(CROSSDEV)strip --strip-unneeded
|
||
|
||
# Setup up linker command line options
|
||
|
||
LDRELFLAGS = -r
|
||
|
||
LDELFFLAGS = -r -e main
|
||
LDELFFLAGS += -T defines.ld -T gnu-elf.ld
|
||
|
||
# This might change in a different environment
|
||
|
||
OBJEXT ?= .o
|
||
|
||
# This is the generated ELF program
|
||
|
||
BIN = hello
|
||
REL = hello.r
|
||
|
||
# These are the sources files that we use
|
||
|
||
SRCS = hello.c
|
||
OBJS = $(SRCS:.c=$(OBJEXT))
|
||
|
||
# Build targets
|
||
|
||
all: $(BIN)
|
||
.PHONY: clean
|
||
|
||
$(OBJS): %$(OBJEXT): %.c
|
||
$(CC) -c $(CFLAGS) -o $@ $<
|
||
|
||
System.map: nuttx-export-7.25/System.map
|
||
cat nuttx-export-7.25/System.map | sed -e "s/\r//g" >System.map
|
||
|
||
$(REL): $(OBJS)
|
||
$(LD) $(LDRELFLAGS) -o $@ $<
|
||
|
||
defines.ld: System.map $(REL)
|
||
./mkdefines.sh System.map "$(REL)" >defines.ld
|
||
|
||
$(BIN): defines.ld $(REL)
|
||
$(LD) $(LDELFFLAGS) -o $@ $(REL)
|
||
$(STRIP) $(REL)
|
||
|
||
clean:
|
||
rm -f $(BIN)
|
||
rm -f $(REL)
|
||
rm -f defines.ld
|
||
rm -f System.map
|
||
rm -f *.o
|
||
|
||
The Linker Script
|
||
=================
|
||
|
||
Two linker scripts are used. One is a normal file (we'll call it the main
|
||
linker script), and the other, ``defines.ld``, is created on-the-fly as
|
||
described in the next section.
|
||
|
||
The main linker script, ``gnu-elf.ld``, contains the following:
|
||
|
||
.. code-block:: shell
|
||
|
||
SECTIONS
|
||
{
|
||
.text 0x00000000 :
|
||
{
|
||
_stext = . ;
|
||
*(.text)
|
||
*(.text.*)
|
||
*(.gnu.warning)
|
||
*(.stub)
|
||
*(.glue_7)
|
||
*(.glue_7t)
|
||
*(.jcr)
|
||
_etext = . ;
|
||
}
|
||
|
||
.rodata :
|
||
{
|
||
_srodata = . ;
|
||
*(.rodata)
|
||
*(.rodata1)
|
||
*(.rodata.*)
|
||
*(.gnu.linkonce.r*)
|
||
_erodata = . ;
|
||
}
|
||
|
||
.data :
|
||
{
|
||
_sdata = . ;
|
||
*(.data)
|
||
*(.data1)
|
||
*(.data.*)
|
||
*(.gnu.linkonce.d*)
|
||
_edata = . ;
|
||
}
|
||
|
||
.bss :
|
||
{
|
||
_sbss = . ;
|
||
*(.bss)
|
||
*(.bss.*)
|
||
*(.sbss)
|
||
*(.sbss.*)
|
||
*(.gnu.linkonce.b*)
|
||
*(COMMON)
|
||
_ebss = . ;
|
||
}
|
||
|
||
/* Stabs debugging sections. */
|
||
|
||
.stab 0 : { *(.stab) }
|
||
.stabstr 0 : { *(.stabstr) }
|
||
.stab.excl 0 : { *(.stab.excl) }
|
||
.stab.exclstr 0 : { *(.stab.exclstr) }
|
||
.stab.index 0 : { *(.stab.index) }
|
||
.stab.indexstr 0 : { *(.stab.indexstr) }
|
||
.comment 0 : { *(.comment) }
|
||
.debug_abbrev 0 : { *(.debug_abbrev) }
|
||
.debug_info 0 : { *(.debug_info) }
|
||
.debug_line 0 : { *(.debug_line) }
|
||
.debug_pubnames 0 : { *(.debug_pubnames) }
|
||
.debug_aranges 0 : { *(.debug_aranges) }
|
||
}
|
||
|
||
Creating the ``defines.ld`` Linker Script
|
||
=========================================
|
||
|
||
The additional linker script ``defines.ld`` is created through a three-step
|
||
process:
|
||
|
||
1. The ``Makefile`` generates a partially linked ELF object, ``hello.r``.
|
||
2. The ``Makefile`` then invokes the ``mkdefines.sh`` script, which generates
|
||
the ``defines.ld`` linker script that provides values for all of the
|
||
undefined symbols.
|
||
3. Finally, the ``Makefile`` produces the fully linked, relocatable ``hello``
|
||
ELF object using the ``defines.ld`` linker script.
|
||
|
||
Below is the version of ``mkdefines.sh`` used in this demo:
|
||
|
||
.. code-block:: bash
|
||
|
||
#!/bin/bash
|
||
|
||
usage="Usage: $0 <system-map> <relprog>"
|
||
|
||
# Check for the required path to the System.map file
|
||
|
||
sysmap=$1
|
||
if [ -z "$sysmap" ]; then
|
||
echo "ERROR: Missing <system-map>"
|
||
echo ""
|
||
echo $usage
|
||
exit 1
|
||
fi
|
||
|
||
# Check for the required partially linked file
|
||
|
||
relprog=$2
|
||
if [ -z "$relprog" ]; then
|
||
echo "ERROR: Missing <program-list>"
|
||
echo ""
|
||
echo $usage
|
||
exit 1
|
||
fi
|
||
|
||
# Verify the System.map and the partially linked file
|
||
|
||
if [ ! -r "$sysmap" ]; then
|
||
echo "ERROR: $sysmap does not exist"
|
||
echo ""
|
||
echo $usage
|
||
exit 1
|
||
fi
|
||
|
||
if [ ! -r "$relprog" ]; then
|
||
echo "ERROR: $relprog does not exist"
|
||
echo ""
|
||
echo $usage
|
||
exit 1
|
||
fi
|
||
|
||
# Extract all of the undefined symbols from the partially linked file and create a
|
||
# list of sorted, unique undefined variable names.
|
||
|
||
varlist=`nm $relprog | fgrep ' U ' | sed -e "s/^[ ]*//g" | cut -d' ' -f2 | sort - | uniq`
|
||
|
||
# Now output the linker script that provides a value for all of the undefined symbols
|
||
|
||
for var in $varlist; do
|
||
map=`grep " ${var}$" ${sysmap}`
|
||
if [ -z "$map" ]; then
|
||
echo "ERROR: Variable $var not found in $sysmap"
|
||
echo ""
|
||
echo $usage
|
||
exit 1
|
||
fi
|
||
|
||
varaddr=`echo ${map} | cut -d' ' -f1`
|
||
echo "${var} = 0x${varaddr} | 0x00000001;"
|
||
done
|
||
|
||
This script uses the ``nm`` utility to find all of the undefined symbols in the
|
||
ELF object, then searches for the address of each undefined symbol in the
|
||
``System.map`` that was created when the released firmware was built. Finally,
|
||
it uses the symbol name and the symbol address to create each symbol table
|
||
entry.
|
||
|
||
.. note::
|
||
|
||
- For the ARMv7-M architecture, bit 0 of the address must be set to indicate
|
||
thumb mode. If you are using a different architecture that requires
|
||
normal aligned addresses, you will need to change the following line by
|
||
eliminating the ORed value:
|
||
|
||
.. code-block:: shell
|
||
|
||
echo "${var} = 0x${varaddr} | 0x00000001;"
|
||
|
||
- If the new ELF module uses a symbol that is not provided in the base
|
||
firmware and, hence, not included in the ``System.map`` file, this script
|
||
will fail. In that case, you will need to provide the missing logic
|
||
within the ELF program itself, if possible.
|
||
|
||
- The technique as described here is only valid in the FLAT build mode. It
|
||
could probably also be extended to work in the PROTECTED mode by
|
||
substituting ``User.map`` for ``System.map``.
|
||
|
||
Here is an example ``defines.ld`` created by ``mkdefines.sh``:
|
||
|
||
.. code-block:: shell
|
||
|
||
printf = 0x0800aefc | 0x00000001 ;
|
||
|
||
Replacing an NSH Built-In Function
|
||
==================================
|
||
|
||
Files can be executed by ``NSH`` from the command line by simply typing the
|
||
name of the ELF program. This requires:
|
||
|
||
1. That the feature be enabled with``CONFIG_NSH_FILE_APP=y``
|
||
2. That support for the PATH variable is enabled (``CONFIG_BINFMT_EXEPATH=y`` and
|
||
``CONFIG_PATH_INITIAL`` set to the mount point of the file system that
|
||
may contain ELF programs).
|
||
|
||
Suppose, for example, I have a built-in application called ``hello``. Before
|
||
installing the new replacement ``hello`` ELF program in the file system, this
|
||
is the version of ``hello`` that ``NSH`` will execute:
|
||
|
||
.. code-block:: shell
|
||
|
||
nsh> hello
|
||
Hello, World!
|
||
nsh>
|
||
|
||
In the above configuration, ``NSH`` will first attempt to run the program called
|
||
``hello`` from the file system. This will fail because we have not yet placed
|
||
our custom ``hello`` ELF program in the file system. So instead, ``NSH`` will
|
||
fall back and execute the built-in application called ``hello``.
|
||
|
||
In this way, any command known to ``NSH`` can be replaced by an ELF program
|
||
installed in a mounted file system directory that is found via the PATH
|
||
variable.
|
||
|
||
Now suppose that we do add our custom ``hello`` to the file system. When
|
||
``NSH`` attempts to run the program called ``hello`` from the file system, it
|
||
will run successfully. The built-in version will be ignored. It has been
|
||
replaced with the version in the file system:
|
||
|
||
.. code-block:: shell
|
||
|
||
nsh> mount -t vfat /dev/mmcsd0 /bin
|
||
nsh> hello
|
||
Hello from Add-On Program!
|
||
nsh>
|
||
|
||
Version Dependency
|
||
==================
|
||
|
||
.. note::
|
||
|
||
This technique generates ELF programs using fixed addresses from the
|
||
``System.map`` file of a versioned release. The generated ELF programs can
|
||
only be used with that specific firmware version. A crash will most likely
|
||
result if used with a different firmware version, because the addresses
|
||
from the ``System.map`` will not match the addresses in a different version
|
||
of the firmware.
|
||
|
||
The alternative approach using :doc:`Symbol Tables <fully_linked_elf>` is more
|
||
or less version independent.
|
||
|
||
Tightly Coupled Memories
|
||
========================
|
||
|
||
Most MCUs based on ARMv7-M family processors support some kind of Tightly
|
||
Coupled Memory (TCM). These TCMs have somewhat different properties for
|
||
specialized operations. Depending on the bus matrix of the processor, you may
|
||
not be able to execute programs from TCM. For instance, the ``STM32 F4``
|
||
supports Core Coupled Memory (CCM), but since it is tied directly to the D-bus,
|
||
it cannot be used to execute programs! On the other hand, the ``STM32F3`` has a
|
||
CCM that is accessible to both the D-Bus and the I-Bus, in which case it
|
||
should be possible to execute programs from this TCM.
|
||
|
||
.. image:: ./image/system_arch_stm32f42xx_and_f43xx.png
|
||
|
||
.. image:: ./image/system_arch_stm32f303xBC_and_f358xC.png
|
||
|
||
When ELF programs are loaded into memory, the memory is allocated from the
|
||
heap via a standard memory allocator. By default with the ``STM32 F4``, the
|
||
CCM is included in ``HEAP`` and will typically be allocated first. If CCM
|
||
memory is allocated to hold the ELF program, a hard-fault will occur
|
||
immediately when you try to execute the ELF program in memory.
|
||
|
||
Therefore, it is necessary on ``STM32 F4`` platforms to include the following
|
||
configuration setting:
|
||
|
||
.. code-block:: shell
|
||
|
||
CONFIG_STM32_CCMEXCLUDE=y
|
||
|
||
With that setting, the CCM memory will be excluded from the heap, and so will
|
||
never be allocated for ELF program memory.
|