Skip to content

The dynamic loader

Benoît Thébaudeau edited this page Sep 23, 2019 · 7 revisions

To facilitate reprogramming and code swapping, Contiki permits dynamic loading of code modules. There are two programming interfaces for loading program: one uses the Executable Linkable Format (ELF); the other uses the native executable format of the host system when running a native platform. We will mainly describe the API for loading ELF modules in the following text, but we will also cover the native loader in a section.

ELF is a standard format that is used not only in systems such as Linux, Solaris, and FreeBSD but also in a range of embedded systems. Contiki can load either a freestanding program or a code module. The dynamic loader links, relocates ELF objects files into the Contiki system image.

An ELF file consists of a header followed by a set of sections which typically include at least a section for binary code (.text), a section for statically allocated data with pre-assigned values (.data), and a section for zero-initialized data .bss.) Additionally, each symbol is represented in a symbol table (.symtab), and strings are stored in a string table (.strtab). For a file to be accepted by Contiki's ELF loader, it must contain at least the sections listed above.

Table of Contents

Loading ELF Modules

Programs can load modules by using the dynamic loader API that is shown below. The API is defined in core/loader/elfloader.h.

int elfloader_init(void); // Initialize the dynamic loader library. 
int elfloader_load(int fd); // Load and relocate an object file. 
int dlloader_load(char *path, char *arg); // Loads a module in native mode. 

elfloader_load() is the only function that is needed to use the dynamic loader. This function loads and relocates an object file into the system image. To load a file, elfloader_load() must be called with a CFS file descriptor of the open file as the only argument.

Note that this function modifies the file referred to by the file descriptor. Backup this file if it must be intact, such as when the original file is supposed to be disseminated to others.

If the dynamic loading succeeds, elfloader_load() returns ELFLOADER_OK. If the module contains a Contiki process, the elfloader_loaded_process variable points to that process. Upon failure, elfloader_load() returns one of the other codes shown below. These return values have the following meaning.

The possible return values from elfloader_load():

 #define ELFLOADER_OK                  0
 #define ELFLOADER_BAD_ELF_HEADER      2
 #define ELFLOADER_NO_SYMTAB           3
 #define ELFLOADER_NO_STRTAB           4
 #define ELFLOADER_NO_TEXT             4
 #define ELFLOADER_SYMBOL_NOT_FOUND    5
 #define ELFLOADER_SEGMENT_NOT_FOUND   6
 #define ELFLOADER_NO_STARTPOINT       7
  • ELFLOADER_OK : The loading succeeded.
  • ELFLOADER_BAD_ELF_HEADER : The file lacks a valid ELF header.
  • ELFLOADER_NO_SYMTAB : The file has no symbol table.
  • ELFLOADER_NO_STRTAB : The file has no string table.
  • ELFLOADER_NO_TEXT : The file has no .text segment.
  • ELFLOADER_SYMBOL_NOT_FOUND : A specific symbol could not be found. The global variable elfloader_unknown contains the name of the symbol that was missing.
  • ELFLOADER_SEGMENT_NOT_FOUND : A required ELF segment is missing.
  • ELFLOADER_NO_STARTPOINT : There is no starting point in the file.
The other available function, elfloader_init() is typically called at system startup time. This function simply initializes an empty list of loaded processes (elfloader_autostart_processes).

Preparing a Firmware for ELF Loading

By default, Contiki includes an empty symbol table so that the code size is not expanded unnecessarily. Therefore, the symbol table must be generated and included within firmware that intends to load ELF modules. The commands needed to generate such a firmware are shown below. The target specification for the firmware is omitted in the example.

The firmware must be prepared with a symbol table to able to load ELF modules dynamically. This three-step process ensures that all available symbols in the firmware are also visible in the symbol table, along with a pointer to their address.

   make <firmware-name>
   make CORE=<firmware-name> <firmware-name>
   make CORE=<firmware-name> <firmware-name>

Creating ELF Modules

Contiki's build system includes an easy method to create modules that can be loaded into a running Contiki system. Simply compile the program with the suffix .ce, as shown below. The suffix instructs the make program to compile an ELF file from a C source file. The object module is stripped from unneeded symbols. The .co suffix works similarly, but does keeps unneeded symbols in the ELF file.

Creating a loadable module for the Sky platform.

   cd example/hello-world
   make TARGET=sky hello-world.ce

ELF Loader Internals

Each CPU architecture in Contiki that supports ELF loading implements a set of architecture-dependent functions. The table shows the API that needs to be implemented in order to support ELF loading. These include functions allocate RAM for data (elfloader_arch_allocate_ram()), allocate read-only memory (ROM) for code (elfloader_arch_allocate_rom()), write to the allocated ROM (elfloader_arch_write_rom()), and relocate addresses in the ROM (elfloader_arch_relocate().) The relocation information is stored in objects of type struct elf32_rela, which is defined below. In this structure, r_offset indicates the location to be relocated, r_info specifies the relocation type and symbol index, and r_addend is the value to add when relocating an address in elfloader_arch_relocate().

The architecture-dependent functions in the ELF loader

elfloader_arch_allocate_ram(int size); // Allocate RAM. 
elfloader_arch_allocate_rom(int size); // Allocate ROM. 
elfloader_arch_write_rom(int fd, unsigned short textoff, unsigned int size, char *mem); // Program ROM. 
elfloader_arch_allocate_relocate(int fd, unsigned sectionoffset, char *sectionaddress, struct elf32_rela *rela, char *addr); // Relocate addresses in ROM. 

The 32-bit ELF relocation structure.

 struct elf32_rela {
   elf32_addr	r_offset;
   elf32_word	r_info;
   elf32_sword	r_addend;
 };

Native Dynamic Linker

In addition to implementing a full dynamic linker, Contiki includes a module that can load software within systems based on either of the platforms minimal-net, native, and netsim. The file core/loader/dlloader.c uses the dynamic linker provided by the host operating system. The functions that the native dynamic linker uses are dlopen(), dlsym(), and dlclose(), which are provided in a variety of operating systems partly complying to POSIX.

The dlloader has a slightly different interface than the ELF loader has. Contiki programs compiled in native object format can be loaded by using the dlloader_load() functions. The path argument specifies which file to load. The file must be in a valid object format of the host operating system. The arg argument is an opaque pointer to data that will be supplied as an argument to the Contiki process located in the loaded module as soon as it is started.

Unlike the ELF loader implementation in Contiki, the dlloader automatically starts processes that have been defined in the autostart_processes variable of the loaded module. The consequence of this is that such modules must have the autostart_processes variable defined.

Conclusions

We have described the two programming interfaces for dynamically linking and loading modules in Contiki. One of the interfaces is for loading ELF modules, whereas the other is for using the host operating system's methods for loading modules of its native format. We also explained the format of ELF files, and the internals Contiki's of dynamic linker and loader.

Clone this wiki locally