Dynamo: Dynamic code loading and memory management for eCos/ARM9

$Id: dynamo.html,v 1.3 2007-12-11 18:10:29 idjelic Exp $


Install  |  User guide  |  Developer guide  | 

1. Install

This package aims to allow safe dynamic code loading and execution. It does so by enriching eCos with features such as virtual memory management (using ARM MMU), dynamic code loading and relocation, etc.

To install the package, simply add it (CYGPKG_DYNAMO) to your eCos configuration. Note that it will require the following packages:

Adding package dynamo

You will also need to define a few basic options:

2. User Guide

Once dynamo is added to your configuration, you may compile and run your eCos application as usual. It will run in a privileged mode: the ARM SYSTEM mode. Please note that a standard eCos application (i.e. without dynamo) runs in ARM SVC mode, which is also a privileged mode.

Building a shared library for dynamo

In order to safely run a dynamically loaded application, you will first need to build a shared library: this library will then be loaded and relocated at run-time. Compiling and linking a shared library should be done using the following compilation and link flags:

# Dynamic code generation flags

DYN_CFLAGS = -mcpu=$(CPU) -Wall -Wpointer-arith -Wstrict-prototypes -Winline -Wundef -g -ffunction-sections -fdata-sections -fno-exceptions -I$(INSTALL)/include -fno-common -fPIC -fvisibility=hidden

DYN_LDFLAGS = -mcpu=$(CPU) -nostdlib -Wl,--warn-common -nostartfiles -Wl,-Map -Wl,map -Wl,-shared -Wl,-dy -Wl,-Bsymbolic -Wl,--cref -Wl,--unresolved-symbols=report-all

Please note how these flags differ from the flags usually used to compile and link eCos applications. The compilation flag -fvisibility=hidden is of special importance: it means that no symbol internally defined in the shared library will be visible from the outside world (the standard eCos application). This is generally a good idea because:

On the other hand, our shared library needs to define at least a few externally visible symbols, otherwise it will be close to useless. We shall see below how a symbol can be made externally visible.

And what about eCos symbols ? Our library will certainly need to use eCos functions such as cyg_thread_create(), cyg_mutex_lock() or libc and libm calls, etc.

In order to allow a shared library to use those functions, dynamo defines what we shall call an export API: a list of all eCos functions/objects available to a shared library (this does not necessarily include all eCos functions). The export API is defined in dynamo/current/export/export.h.

When eCos is compiled, the export API is available as a special shared library generated in ecos_install/lib/libexport.so. This shared library is an empty shell, it does not contain any code; it is only provided to help making sure that a user shared library properly uses the export API (i.e. does not refer to a function/object outside the API).

Example:

Here is an example in which we build a shared library consisting of two files: module.c and modulelib.c.

First we compile the source files:

arm-elf-gcc -c $(DYN_CFLAGS) module.c
arm-elf-gcc -c $(DYN_CFLAGS) modulelib.c

Then we link the resulting objects:

arm-elf-gcc -o module.so $(DYN_LDFLAGS) -Wl,-soname=module.so module.o modulelib.o -Lecos_install/lib -lexport

Note how we link our objects with libexport.so (-lexport) despite the fact that libexport.so does not contain any code. That way, the linker will tell us when we refer to a function or object not available in the export API.

Now suppose that our shared library defines a function that we want to use as an entry point, or that we just want to make visible from eCos. We can achieve this using the visibility attribute of GCC: Say our function is named module_entry:

void module_entry(cyg_addrword_t data) __attribute__((visibility("default")));

By changing the visibility attribute from hidden to default, we make the object/function visible from eCos.

Now that we have built a shared library, we shall be able to load and execute it inside a dynamo process:

Creating a dynamo process

A dynamo process basically consists of:

A process can be created and started the same way an eCos thread is created and started, using the following API:

#include <cyg/dynamo/process.h>

Cyg_ErrNo cyg_process_create(
   cyg_addrword_t sched_info,
   const char *name,
   const char *file,
   const char *entry,
   cyg_uint32 domain,
   cyg_uint32 max_threads,
   cyg_uint32 max_mem,
   cyg_handle_t *handle,
   cyg_process *this );

void cyg_process_resume(cyg_handle_t process);

When a process has been successfully created, it can be started by calling cyg_process_resume().

Note that functions cyg_process_create and cyg_process_resume can only be called from SYSTEM code, not from USER code.

Example:

In order to load and run the shared library built in our previous example, we could use the following code:

#include <cyg/dynamo/process.h>

cyg_process process;

void run_module(void) {

  int status;
  cyg_handle_t handle;

  dynamo_init();

  fprintf(stderr, "Executing module.so...\n" );

  status = cyg_process_create(
    10,  // process priority
    "My module", // process name
    "/module.so", // shared library file
    "module_entry", // entry symbol
    1,  // run in ARM domain 1
    16,  // no more than 16 threads
    1024 << 10, // do not allocate more than 10 MB
    &handle,
    &process );

  if ( status == ENOERR ) {
    cyg_process_resume(handle);
  }
  else {
    fprintf( stderr, "cyg_process_create() returned %d\n", status );
  }
}

Debugging with shared libraries

Debugging dynamically loaded code with system calls can be somewhat tricky and confusing:

When a privilege violation occurs, the faulty thread is terminated, and an error message like the following is displayed:

dynamo: data abort @ pc = 0x620045b0 (section permission fault)
dynamo: [trying to access 0x10 in domain 0]
dynamo: exiting thread "My thread" (handle = 0x145254, domain = 1)

Dynamic linking loader API

In addition to its process API, dynamo implements a programming interface to its dynamic linking loader. The API is very similar to the classic dlopen interface:

void *dlopen( const char *filename, int flag, int *dlerr );
char *dlerror(int dlerr);
void *dlsym(void *handle, const char *symbol);
int dlclose(void *handle);

3. Developer Guide

TODO...