Name

CYGPKG_OBJLOADER — eCos Support for Dynamic Module Loading

Synopsis

#include <cyg/objloader/objload.h>
      

void *cyg_ldr_open(cyg_ldr_open_stream *open_stream, CYG_ADDRWORD data);

void cyg_ldr_close(void *handle);

char *cyg_ldr_error(void);

void *cyg_ldr_find_symbol(void *handle, char *symbol);

Description

[Note]Note

The Object Loader package does not support all processor architectures at present.

The Object Loader package provides support for dynamically loading executable modules into an eCos system. Modules may be loaded into memory from a variety of sources, linked in to the running system and entry points invoked to execute the code of the module. When the module is no longer required, it may be unloaded and the memory reused for other purposes or other modules.

This system is modelled most closely on the Linux kernel module mechanism, rather than Windows DLLs or Unix shared objects. As a result, it has a number of restrictions:

  • Only modules written in C are supported. The Object Loader does not currently provide support for invoking static constructors and destructors, C++ exceptions, RTTI and other parts of the C++ runtime system.
  • Automatic symbol resolution only works for references from a module into the main executable. References between modules are not supported, and resolution of unresolved symbols in the main executable to module symbols is not supported.
  • Loaded modules need to be built using the same, or similar, configuration to the main system.
  • Loaded modules should be built with the same or compatible compiler flags as the main system. There is one important exception. Some architectures including MIPS and Nios II implement a global pointer register. Small global variables are placed in an area of memory up to 64K. The gp register points at this area of memory, allowing the variables to be accessed directly using a single instruction instead of the two or more instructions that would otherwise be required. This technique cannot be used for a dynamically loaded module. Hence the use of gp-relative addressing must be suppressed with a compiler flag, typically -G0.

Creating Loadable Modules

Modules can be just object files as generated by the compiler. In a Makefile including the $(INSTALL_DIR)/include/pkgconf/ecos.mak definitions file, the entry to build module.o might be:

module.o: module.c
        $(ECOS_COMMAND_PREFIX)gcc -c -I$(INSTALL_DIR)/include $(ECOS_GLOBAL_CFLAGS) -o $@ $<
        $(ECOS_COMMAND_PREFIX)strip -g $@

The compile line generates a .o file. The -I option allows includes to be fetched from the eCos installation. The command prefix and global flags are stored in the ecos.mak file by the eCos build process. If the compile flags include -g or some other debug option then to save memory and maybe load time it is useful to pass the finished module through strip to limit the file contents to just the loadable ELF sections.

It is possible to create a module out of several object files by using the linker's ability to perform a partial link:

module.o : file1.o file2.o file3.o
        $(ECOS_COMMAND_PREFIX)gcc $(subst --gc-sections,-r,$(ECOS_GLOBAL_LDFLAGS)) -L$(PREFIX)/lib \
            -Tmodule.ld -o $@ $^
        $(ECOS_COMMAND_PREFIX)strip -g $@

The module.ld linker script is defined by the Object Loader package and is copied out to the install lib directory. It should be used when combining multiple files, or when advanced features such as HAL tables are used in a single object file.

If the module makes use of float, double, long long and some long arithmetic operations, then it should be partially linked against libgcc before loading. This can be done with the following makefile fragments:

# Single source file module…
module.o: module.c
        $(ECOS_COMMAND_PREFIX)gcc -c -I$(INSTALL_DIR)/include $(ECOS_GLOBAL_CFLAGS) -o $@.tmp $<
        $(ECOS_COMMAND_PREFIX)gcc $(subst --gc-sections,-r,$(ECOS_GLOBAL_LDFLAGS)) \
            -L`dirname \`$(ECOS_COMMAND_PREFIX)gcc $(ECOS_GLOBAL_CFLAGS) \
            -print-libgcc-file-name\`` -L$(PREFIX)/lib -Tmodule.ld -o $@ $@.tmp -lgcc
        $(ECOS_COMMAND_PREFIX)strip -g $@

# Combine multiple object files…
module.o : file1.o file2.o file3.o
        $(ECOS_COMMAND_PREFIX)gcc $(subst --gc-sections,-r,$(ECOS_GLOBAL_LDFLAGS)) \
            -L`dirname \`$(ECOS_COMMAND_PREFIX)gcc $(ECOS_GLOBAL_CFLAGS) \
            -print-libgcc-file-name\`` -L$(PREFIX)/lib -Tmodule.ld -o $@ $^ -lgcc
        $(ECOS_COMMAND_PREFIX)strip -g $@

Target Specific Considerations

There are a number of special considerations for particuar target architectures:

  • Modules compiled for Thumb may be loaded into targets compiled for either ARM32 or Thumb. Thumb builds of eCos that use the object loader should have the "-mlong-calls" compiler option set. ARM32 builds should have thumb interworking enabled if thumb modules are to be loaded (the object loader module does this automatically). Thumb modules should be compiled with "-mthumb -mthumb-interwork -mlong-calls" compiler options. However, some later ARM variants do not need the "-mthumb-interwork" option since this is implicit in the architecture. For such targets this option need not be given.
  • Modules compiled for ARM, Thumb or Thumb 2 may require the "-mlong-calls" compiler option if the module to be loaded will occupy a different region of the address space to the rest of the program. The most frequent scenario causing this to arise is if the main program runs from FLASH memory, but with the module loaded into RAM. If in doubt, use the option as it is always safe, and the only downside is a small code size and runtime execution penalty on function calls.
  • Modules compiled for the MIPS16 instruction set may be loaded into a MIPS target, so long as the processor supports the instruction set. To compile and link such a module, the "-mips16" compiler option must be substituted for "-mips32", along with "-fwritable-strings".
  • Modules compiled for NIOS II processors must be compiled with the "-G0" compiler option. This ensures that loaded modules do not make assumptions about the accessibility of small initialised data (".sdata") or small zero-initialised data (".sbss") relative to the address it was loaded at.

Loading Modules

The function cyg_ldr_open() is used to load a module into memory. It takes two arguments. The first argument defines a module loader, while the second argument is a generic data item whose value depends on the loader. If the load is successful, then a non-NULL handle will be returned. A NULL pointer will be returned on failure.

If there is an error in the loading process, then the function cyg_ldr_error() will return a string describing the last error that occurred. Note that this is not thread-safe since there is only a single last error recorded for all load operations.

At present the following loaders are implemented:

CYG_LDR_FILESYSTEM

This loader uses FILEIO operations to read an ELF file from a named file in a filesystem. For example, to read a module from the file "/lib/modules/module.o":

mod_handle = cyg_ldr_open( CYG_LDR_FILESYSTEM,
                           (CYG_ADDRWORD)"/lib/modules/module.o");

This loader is included by default if the CYGPKG_IO_FILEIO package is included, although it can be omitted by disabling CYGPKG_OBJLOADER_LOADER_FS.

CYG_LDR_MEMORY

This loader uses memory access primitives to read an ELF file from any addressable memory such as ROM, FLASH or RAM. For example to read a module from the location module_base:

mod_handle = cyg_ldr_open( CYG_LDR_MEMORY,
                           (CYG_ADDRWORD)&module_base );

This loader is included by default, although it can be omitted by disabling CYGPKG_OBJLOADER_LOADER_MEM.

Loaders CYG_LDR_FTP, CYG_LDR_TFTP, CYG_LDR_HTTP and CYG_LDR_FLASH are defined, but not currently implemented.

Unloading Modules

A module may be unloaded by calling cyg_ldr_close(), passing it the handle returned from cyg_ldr_open(). This will cause the memory occupied by the loader to be released. Any pointers into the code or data of the module will be rendered invalid and should not be used.

Referencing Module Symbols

When a module is loaded, a symbol table listing all the external symbols that it defines is loaded with it. The function cyg_ldr_find_symbol() searches this table and returns a pointer to the location defined by a symbol. For example, to create a thread running from a function in a module:

cyg_thread_entry_t *thread_entry;

thread_entry = cyg_ldr_find_symbol( handle, "thread_entry");

cyg_thread_create(THREAD_PRIORITY,
                  thread_entry,
                  0,
                  "Module Thread",
                  (void *)thread_stack,
                  THREAD_STACK_SIZE,
                  &thread_handle,
                  &thread_object);

Both functions and variables may be accessed in this way.

There is no mechanism for resolving dangling references in the main eCos application, or other modules, to symbols in a newly loaded module. The main eCos application must have all references resolved at link time. However, it is possible to simulate the effect of dynamic resolution by using function pointers. For example define a global function pointer to an initial dummy function:

typedef int module_fn_t(int a, int b);

module_fn_t dummy_fn;

module_fn_t *module_fn = dummy_fn;

int dummy_fn( int a, int b )
{
    return -1;
}

When the module is loaded the function pointer can be pointed at the function within the module, and pointed back to the dummy function when it is unloaded:

void *mod_handle;

void load_module(void)
{
    mod_handle = cyg_ldr_open( CYG_LDR_FILESYSTEM, (CYG_ADDRWORD)"/lib/modules/module.o");

    module_fn = cyg_ldr_find_symbol( mod_handle, "module_fn");
}

void unload_module(void)
{
    cyg_ldr_close( mod_handle );

    module_fn = dummy_fn;
}

One could even implement a form of demand loading by combining dummy_fn and load_module:

int dummy_fn( int a, int b )
{
    mod_handle = cyg_ldr_open( CYG_LDR_FILESYSTEM, (CYG_ADDRWORD)"/lib/modules/module.o");

    module_fn = cyg_ldr_find_symbol( mod_handle, "module_fn");

    return module_fn( a, b );
}

Module Open and Close Functions

When a module is loaded the Object Loader will look for a symbol with the name "module_open" and if found will call it with the following prototype:

void module_open( void );

Similarly, when cyg_ldr_close() is called, the Object Loader will look for a symbol named "module_close" and call it, with the same prototype.

External References

When a module is loaded, the Object Loader package performs any relocations it requires and resolves any unresolved symbol references it contains. The load only succeeds if all of these can be completed. For these symbols to be resolved it is necessary for the main eCos executable to contain a symbol table defining the symbols to be resolved. Normal eCos executables do not contain such a symbol table since it would occupy an unreasonably large amount of memory. There is also no mechanism to persuade the linker to include a loadable symbol table into the executable. Hence it is necessary for the application to explicitly define a symbol table that maps symbol names to addresses.

The loader provides an empty table; the user can then define additional entries required by any loadable modules. In order to keep the size of the table to a minimum, the user can selectively include only those functions that are expected to be used by the loader to resolve all references. There are several macros defined in objload.h for defining table entries:

CYG_LDR_TABLE_FUN( name )
This macro defines a table entry for a function with the given name.
CYG_LDR_TABLE_VAR( name )
This macro defines a table entry for a data variable with the given name.
CYG_LDR_TABLE_ENTRY( entry_name, symbol_name, address )
This is a low level macro that allow all aspects of a symbol table entry to be controlled. The entry_name argument defines the table entry object name (a C language requirement since anonymous objects are not permitted). The symbol_name argument is a string giving the symbol that will be matched by the loader. The address argument gives the memory location to which this symbol will resolve.

The objload.h file contains a number of macros that collect together groups of functions as a convenient way to include blocks of Kernel, C Library and FILEIO functionality. These include the following:

CYG_LDR_TABLE_KAPI_ALARM()
CYG_LDR_TABLE_KAPI_CLOCK()
CYG_LDR_TABLE_KAPI_COND()
CYG_LDR_TABLE_KAPI_COUNTER()
CYG_LDR_TABLE_KAPI_EXCEPTIONS()
CYG_LDR_TABLE_KAPI_FLAG()
CYG_LDR_TABLE_KAPI_INTERRUPTS()
CYG_LDR_TABLE_KAPI_MBOX()
CYG_LDR_TABLE_KAPI_MEMPOOL_FIX()
CYG_LDR_TABLE_KAPI_MEMPOOL_VAR()
CYG_LDR_TABLE_KAPI_MUTEX()
CYG_LDR_TABLE_KAPI_SCHEDULER()
CYG_LDR_TABLE_KAPI_SEMAPHORE()
CYG_LDR_TABLE_KAPI_THREAD()
CYG_LDR_TABLE_STRING()
CYG_LDR_TABLE_STDIO()
CYG_LDR_TABLE_INFRA_DIAG()
CYG_LDR_TABLE_FILEIO()
CYG_LDR_TABLE_NET()