Doc: loading ELF program
Migrate the Updating a release System with ELF programs with child pages to official wiki
This commit is contained in:
parent
f0a3c43a54
commit
04c7391162
6 changed files with 991 additions and 1 deletions
533
Documentation/guides/fully_linked_elf.rst
Normal file
533
Documentation/guides/fully_linked_elf.rst
Normal file
|
@ -0,0 +1,533 @@
|
|||
ELF Programs – No Symbol Tables
|
||||
===============================
|
||||
|
||||
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.
|
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
BIN
Documentation/guides/image/system_arch_stm32f42xx_and_f43xx.png
Normal file
BIN
Documentation/guides/image/system_arch_stm32f42xx_and_f43xx.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 68 KiB |
|
@ -57,3 +57,6 @@ Guides
|
|||
signaling_sem_priority_inheritance.rst
|
||||
smaller_vector_tables.rst
|
||||
port.rst
|
||||
updating_release_system_elf.rst
|
||||
partially_linked_elf.rst
|
||||
fully_linked_elf.rst
|
429
Documentation/guides/partially_linked_elf.rst
Normal file
429
Documentation/guides/partially_linked_elf.rst
Normal file
|
@ -0,0 +1,429 @@
|
|||
ELF Programs – With Symbol Tables
|
||||
=================================
|
||||
|
||||
Updating a Release System with ELF Programs – With Symbol Tables
|
||||
-----------------------------------------------------------------
|
||||
|
||||
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, perhaps,
|
||||
downloaded into on-board SPI FLASH.
|
||||
|
||||
In order to support such post-release updates, your released firmware must
|
||||
support execution of ELF programs loaded into RAM and symbol tables also
|
||||
provided via the file system (see `apps/examples/elf`).
|
||||
|
||||
The files shown in this Wiki page can be downloaded
|
||||
`here <https://cwiki.apache.org/confluence/download/attachments/139629402/elfprog-wsymtab.tar.gz?version=1&modificationDate=1576735523000&api=v2>`_
|
||||
|
||||
Creating a Symbol Table
|
||||
-----------------------
|
||||
|
||||
There are several ways to create an application symbol table. Only two are
|
||||
compatible with the example provided here:
|
||||
|
||||
1. **Board-specific Bring-up Logic**
|
||||
Build a symbol table into the base firmware and add it to your
|
||||
board-specific bring-up logic. This technique is typically used in kernel
|
||||
mode with ``CONFIG_USER_INITPATH=y``.
|
||||
|
||||
In this setup, the system does not initialize using a standard C call like
|
||||
``nsh_main()``. Instead, it starts with an ``init`` ELF program, similar to
|
||||
how Linux initializes. The configuration option
|
||||
``CONFIG_EXECFUNCS_SYMTAB_ARRAY`` initializes the system with a minimal set
|
||||
of symbols required by the ``init`` program. Once initialized, the ``init``
|
||||
program would typically call ``boardctl()`` to put the final symbol table in
|
||||
place.
|
||||
|
||||
To enable this method, you must:
|
||||
|
||||
- Set ``CONFIG_EXECFUNCS_HAVE_SYMTAB=y`` in your configuration.
|
||||
- Provide a symbol table with the global name ``CONFIG_EXECFUNCS_SYMTAB_ARRAY`` with the variable name ``CONFIG_EXECFUNCS_NSYMBOLS_VAR`` that holds the number of symbol entries. The default symbol table name is ``g_symtab``.
|
||||
|
||||
In this example, let's illustrate this using an STM32F4-Discovery
|
||||
configuration. We will assume that you have modified the
|
||||
``boards/arm/stm32/stm32fdiscovery/src/stm32_bringup.c`` file, adding the
|
||||
following:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
#include <stdio.h>
|
||||
#include <nuttx/binfmt/symtab.h>
|
||||
|
||||
const struct symtab_s g_symtab[] = {
|
||||
{"printf", (FAR void *)printf}
|
||||
};
|
||||
|
||||
int g_nsymbols = 1;
|
||||
|
||||
This is a simple symbol table containing only the symbol string "printf,"
|
||||
whose value is the address of the function ``printf()``.
|
||||
|
||||
There is, of course, a lot more that could be said about generating symbol
|
||||
tables. NuttX provides specialized tools in the ``tools/`` directory and
|
||||
instructions elsewhere for generating more extensive symbol tables. However,
|
||||
this example keeps things simple to focus on the core functionality.
|
||||
|
||||
2. **Application Logic**
|
||||
Alternatively, the symbol table can be provided dynamically by the
|
||||
application itself, using the ``boardctl()`` system interface. The specific
|
||||
``boardctl()`` command to use is ``BOARDIOC_APP_SYMTAB``. This command
|
||||
provides the symbol table in the same way as the board-specific logic but
|
||||
allows for application-level control.
|
||||
|
||||
To use this approach, you need to:
|
||||
- Enable the configurations ``CONFIG_LIB_BOARDCTL=y`` and ``CONFIG_BOARDCTL_APP_SYMTAB=y``.
|
||||
- Include application logic to provide the symbol table. If ``CONFIG_EXAMPLES_NSH_SYMTAB=y`` is set, NSH can handle this automatically.
|
||||
|
||||
Export Package
|
||||
--------------
|
||||
|
||||
At the time of firmware release, you should create and save an export package.
|
||||
This export package contains all the necessary files required to create
|
||||
post-release add-on modules for your embedded system.
|
||||
|
||||
For demonstration purposes, we use the STM32F4-Discovery with the network NSH
|
||||
configuration. This setup assumes that you have the STM32F4DIS-BB baseboard.
|
||||
The demonstration also requires support for externally modifiable media, such
|
||||
as:
|
||||
|
||||
- Removable media, like an SD card or USB flash drive.
|
||||
- An internal file system remotely accessible via USB MSC, FTP, or other
|
||||
protocols.
|
||||
- A remote file system, such as NFS.
|
||||
|
||||
In this demonstration, the networking NSH configuration uses the SD card on
|
||||
the STM32 baseboard. Other NSH configurations can also be used, provided they
|
||||
supply the necessary file system support.
|
||||
|
||||
(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>`_.)
|
||||
|
||||
Example for STM32F4-Discovery:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ make distclean
|
||||
$ tools/configure.sh -c stm32f4discovery:netnsh
|
||||
$ make menuconfig
|
||||
|
||||
Required configurations:
|
||||
|
||||
- Disable networking: ``# CONFIG_NET is not set``
|
||||
- Enable ELF binary support: ``CONFIG_ELF=y``, ``CONFIG_LIBC_EXECFUNCS=y``,
|
||||
``CONFIG_EXECFUNCS_HAVE_SYMTAB=y``, ``CONFIG_EXECFUNCS_SYMTAB_ARRAY="g_symtab"`` and
|
||||
``CONFIG_EXECFUNCS_NSYMBOLS_VAR="g_nsymbols"``
|
||||
- Enable PATH variable support: ``CONFIG_BINFMT_EXEPATH=y``,
|
||||
``CONFIG_PATH_INITIAL="/bin"``
|
||||
- Enable execution from NSH: ``CONFIG_NSH_FILE_APPS=y``
|
||||
|
||||
Then, build the NuttX firmware image and the export package:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ make
|
||||
$ make export
|
||||
|
||||
When ``make export`` completes, you will find a ZIP package in the top-level
|
||||
NuttX directory called ``nuttx-export-x.y.zip`` (where x.y corresponds to the
|
||||
version, determined by the .version file in the same directory). The contents
|
||||
of this ZIP file are organized as follows:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
nuttx-export-x.x
|
||||
|- arch/
|
||||
|- build/
|
||||
|- include/
|
||||
|- libs/
|
||||
|- startup/
|
||||
|- System.map
|
||||
`- .config
|
||||
|
||||
Add-On Build Directory
|
||||
-----------------------
|
||||
|
||||
In order to create the add-on ELF program, you will need:
|
||||
|
||||
1. The export package.
|
||||
2. A program build Makefile.
|
||||
3. A linker script used by the Makefile.
|
||||
|
||||
The example Makefile discussed below assumes the use of a GNU toolchain. Note
|
||||
that non-GNU toolchains would likely require a significantly different
|
||||
Makefile and linker script.
|
||||
|
||||
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 simple
|
||||
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`` that contains the following:
|
||||
|
||||
1. The ``hello.c`` source file.
|
||||
2. A Makefile to build the ELF program.
|
||||
3. A linker script called ``gnu-elf.ld`` needed by the Makefile.
|
||||
4. The export package ``nuttx-export-7.25.zip``.
|
||||
|
||||
|
||||
Building the ELF Program
|
||||
------------------------
|
||||
|
||||
The first step in creating the ELF program is to unzip the export
|
||||
package. Starting in the ``addon`` directory:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ cd addon
|
||||
$ ls
|
||||
gnu-elf.ld hello.c Makefile nuttx-export-7.25.zip
|
||||
|
||||
Where:
|
||||
- ``gnu-elf.ld`` is the linker script.
|
||||
- ``hello.c`` is the example source file.
|
||||
- ``Makefile`` builds the ELF program.
|
||||
- ``nuttx-export-7.25.zip`` is the export package from NuttX 7.25.
|
||||
|
||||
Unzip the export package as follows:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ unzip nuttx-export-7.25.zip
|
||||
|
||||
This creates a new directory called ``nuttx-export-7.25``, containing
|
||||
all the content from the released NuttX code required to build
|
||||
the ELF program.
|
||||
|
||||
|
||||
The Makefile
|
||||
------------
|
||||
|
||||
To build the ELF program, simply run:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ make
|
||||
|
||||
This uses the following Makefile to generate several files:
|
||||
- ``hello.o``: The compiled object file for ``hello.c``.
|
||||
- ``hello``: The linked ELF program.
|
||||
|
||||
Only the resulting ``hello`` file is needed.
|
||||
|
||||
The Makefile used to create the ELF program is as follows:
|
||||
|
||||
.. 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
|
||||
|
||||
LDELFFLAGS = -r -e main
|
||||
LDELFFLAGS += -T gnu-elf.ld
|
||||
|
||||
# This might change in a different environment
|
||||
|
||||
OBJEXT ?= .o
|
||||
|
||||
# This is the generated ELF program
|
||||
|
||||
BIN = hello
|
||||
|
||||
# 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 $@
|
||||
|
||||
$(BIN): $(OBJS)
|
||||
$(LD) $(LDELFFLAGS) -o $@ $^
|
||||
$(STRIP) $(BIN)
|
||||
|
||||
clean:
|
||||
rm -f $(BIN)
|
||||
rm -f *.o
|
||||
|
||||
The Linker Script
|
||||
-----------------
|
||||
|
||||
The linker script that I am using in this example, 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) }
|
||||
}
|
||||
|
||||
Replacing NSH Built-In Functions
|
||||
--------------------------------
|
||||
|
||||
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`` and (2) that support for the PATH variable is
|
||||
enabled with ``CONFIG_BINFMT_EXEPATH=y`` and ``CONFIG_PATH_INITIAL`` set to
|
||||
the mount point of the file system that may contain ELF programs.
|
||||
|
||||
In this example, there is no application in the base firmware called
|
||||
``hello``. So attempts to run ``hello`` will fail:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
nsh> hello
|
||||
nsh: hello: command not found
|
||||
nsh>
|
||||
|
||||
But if we mount the SD card containing the ``hello`` image that we created
|
||||
above, then we can successfully execute the ``hello`` command:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
nsh> mount -t vfat /dev/mmcsd0 /bin
|
||||
nsh> ls /bin
|
||||
/bin:
|
||||
System Volume Information/
|
||||
hello
|
||||
nsh> hello
|
||||
Hello from Add-On Program!
|
||||
nsh>
|
||||
|
||||
Here we showed how you can add a new command to NSH to a product without
|
||||
modifying the base firmware. We can also replace or update an existing
|
||||
built-in application in this way:
|
||||
|
||||
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 put
|
||||
our custom ``hello`` ELF program in the file system. So instead, NSH will
|
||||
fallback and execute the built-in application called ``hello``. In this way,
|
||||
any command known to NSH can be replaced from an ELF program installed in a
|
||||
mounted file system directory that can be found via the PATH variable.
|
||||
|
||||
After 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.
|
||||
|
||||
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 the 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 the heap and will typically be allocated first. If CCM memory
|
||||
is allocated to hold the ELF program in memory, then 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.
|
25
Documentation/guides/updating_release_system_elf.rst
Normal file
25
Documentation/guides/updating_release_system_elf.rst
Normal file
|
@ -0,0 +1,25 @@
|
|||
Updating a Release System with ELF Programs
|
||||
============================================
|
||||
|
||||
You can enhance the functionality of your released embedded system by adding
|
||||
ELF programs, which can be loaded from a file system. These programs can be
|
||||
stored on an SD card or downloaded into on-board SPI FLASH, allowing for
|
||||
easy updates or extensions to the system's firmware.
|
||||
|
||||
There are two ways you can accomplish this:
|
||||
|
||||
Partially linked
|
||||
----------------
|
||||
This describes building the partially linked, relocatable ELF program that
|
||||
depends on a symbol table provided by the base firmware in FLASH.
|
||||
|
||||
Reference:
|
||||
- See :doc:`Partially Linked ELF Programs <partially_linked_elf>`
|
||||
|
||||
Fully linked
|
||||
------------
|
||||
This describes building a fully linked, relocatable ELF program that does
|
||||
not depend on any symbol table information.
|
||||
|
||||
Reference:
|
||||
- See :doc:`Fully Linked ELF Programs <fully_linked_elf>`
|
Loading…
Reference in a new issue