zephyr/doc/services/binary_descriptors/index.rst

169 lines
7.2 KiB
ReStructuredText

.. _binary_descriptors:
Binary Descriptors
##################
Binary Descriptors are constant data objects storing information about the binary executable.
Unlike "regular" constants, binary descriptors are linked to a known offset in the binary, making
them accessible to other programs, such as a different image running on the same device or a host tool.
A few examples of constants that would make useful binary descriptors are: kernel version, app version,
build time, compiler version, environment variables, compiling host name, etc.
Binary descriptors are created by using the ``DEFINE_BINDESC_*`` macros. For example:
.. code-block:: c
#include <zephyr/bindesc.h>
BINDESC_STR_DEFINE(my_string, 2, "Hello world!"); // Unique ID is 2
``my_string`` could then be accessed using:
.. code-block:: c
printk("my_string: %s\n", BINDESC_GET_STR(my_string));
But it could also be retrieved by ``west bindesc``:
.. code-block:: bash
$ west bindesc custom_search STR 2 build/zephyr/zephyr.bin
"Hello world!"
Internals
*********
Binary descriptors are implemented with a TLV (tag, length, value) header linked
to a known offset in the binary image. This offset may vary between architectures,
but generally the descriptors are linked as close to the beginning of the image as
possible. In architectures where the image must begin with a vector table (such as
ARM), the descriptors are linked right after the vector table. The reset vector points
to the beginning of the text section, which is after the descriptors. In architectures
where the image must begin with executable code (e.g. x86), a jump instruction is injected at
the beginning of the image, in order to skip over the binary descriptors, which are right
after the jump instruction.
Each tag is a 16 bit unsigned integer, where the most significant nibble (4 bits) is the type
(currently uint, string or bytes), and the rest is the ID. The ID is globally unique to each
descriptor. For example, the ID of the app version string is ``0x800``, and a string
is denoted by 0x1, making the app version tag ``0x1800``. The length is a 16 bit
number equal to the length of the data in bytes. The data is the actual descriptor
value. All binary descriptor numbers (magic, tags, uints) are laid out in memory
in the endianness native to the SoC. ``west bindesc`` assumes little endian by default,
so if the image belongs to a big endian SoC, the appropriate flag should be given to the
tool.
The binary descriptor header starts with the magic number ``0xb9863e5a7ea46046``. It's followed
by the TLVs, and ends with the ``DESCRIPTORS_END`` (``0xffff``) tag. The tags are
always aligned to 32 bits. If the value of the previous descriptor had a non-aligned
length, zero padding will be added to ensure that the current tag is aligned.
Putting it all together, here is what the example above would look like in memory
(of a little endian SoC):
.. code-block::
46 60 a4 7e 5a 3e 86 b9 02 10 0d 00 48 65 6c 6c 6f 20 77 6f 72 6c 64 21 00 00 00 00 ff ff 00 00
| magic | tag |length| H e l l o w o r l d ! | pad | end |
Usage
*****
Binary descriptors are always created by the ``BINDESC_*_DEFINE`` macros. As shown in
the example above, a descriptor can be generated from any string or integer, with any
ID. However, it is recommended to comply with the standard tags defined in
``include/zephyr/bindesc.h``, as that would have the following benefits:
1. The ``west bindesc`` tool would be able to recognize what the descriptor means and
print a meaningful tag
2. It would enforce consistency between various apps from various sources
3. It allows upstream-ability of descriptor generation (see Standard Descriptors)
To define a descriptor with a standard tag, just use the tags included from ``bindesc.h``:
.. code-block:: c
#include <zephyr/bindesc.h>
BINDESC_STR_DEFINE(app_version, BINDESC_ID_APP_VERSION_STRING, "1.2.3");
Standard Descriptors
====================
Some descriptors might be trivial to implement, and could therefore be implemented
in a standard way in upstream Zephyr. These could then be enabled via Kconfig, instead
of requiring every user to reimplement them. These include build times, kernel version,
and host info. For example, to add the build date and time as a string, the following
configs should be enabled:
.. code-block:: kconfig
# Enable binary descriptors
CONFIG_BINDESC=y
# Enable definition of binary descriptors
CONFIG_BINDESC_DEFINE=y
# Enable default build time binary descriptors
CONFIG_BINDESC_DEFINE_BUILD_TIME=y
CONFIG_BINDESC_BUILD_DATE_TIME_STRING=y
To avoid collisions with user defined descriptors, the standard descriptors were allotted
the range between ``0x800-0xfff``. This leaves ``0x000-0x7ff`` to users.
For more information read the ``help`` sections of these Kconfig symbols.
By convention, each Kconfig symbol corresponds to a binary descriptor whose
name is the Kconfig name (with ``CONFIG_BINDESC_`` removed) in lower case. For example,
``CONFIG_BINDESC_KERNEL_VERSION_STRING`` creates a descriptor that can be
accessed using ``BINDESC_GET_STR(kernel_version_string)``.
Reading Descriptors
===================
It's also possible to read and parse binary descriptors from an application.
This can be useful both for an image trying to read its own descriptors, and for
an image trying to read another image's descriptors. Reading can be performed through
one of three backends:
#. RAM - assuming the descriptors have been copied to RAM (e.g. by a bootloader), they
can be read from the buffer they reside in.
#. Memory mapped flash - If the flash where the image to be read resides in flash and is
accessible through the program's address space, it can be read directly from flash.
This option uses the least amount of RAM, but will not work if the flash is not memory mapped,
and is not recommended to read a bootloader's descriptors for security concerns.
#. Flash - Using an internal buffer, the descriptors are read one by one using the flash API,
and given to the user while they're in the buffer.
To enable reading descriptors, enable :kconfig:option:`CONFIG_BINDESC_READ`. The three backends are
enabled by these Kconfig symbols, respectively: :kconfig:option:`CONFIG_BINDESC_READ_RAM`,
:kconfig:option:`CONFIG_BINDESC_READ_MEMORY_MAPPED_FLASH`, and :kconfig:option:`CONFIG_BINDESC_READ_FLASH`.
To read the descriptors, a handle to the descriptors should first be initialized:
.. code-block:: c
struct bindesc_handle handle;
/* Assume buffer holds a copy of the descriptors */
bindesc_open_ram(&handle, buffer);
The ``bindesc_open_*`` functions are the only functions concerned with the backend used.
The rest of the API is agnostic to where the data is. After the handle has been initialized,
it can be used with the rest of the API:
.. code-block:: c
char *version;
bindesc_find_str(&handle, BINDESC_ID_KERNEL_VERSION_STRING, &version);
printk("Kernel version: %s\n", version);
west bindesc tool
=================
``west`` is able to parse and display binary descriptors from a given executable image.
For more information refer to ``west bindesc --help`` or the :ref:`documentation<west-bindesc>`.
API Reference
*************
.. doxygengroup:: bindesc_define
.. doxygengroup:: bindesc_read