Dynamo: Dynamic code loading and memory management for eCos/ARM9
$Id: dynamo.html,v 1.3 2007-12-11 18:10:29 idjelic Exp $
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:
- CYGPKG_UTILS_MMU: required for virtual memory management
You will also need to define a few basic options:
-
CYGOPT_DYNAMO_VERBOSITY_LEVEL:
Debugging messages verbosity level
This option controls the verbosity of debugging messages. The
default level (1) shows package initialisation diagnostic
messages. Level 2 adds status messages as various operations
are undertaken. Level 3 is only used for internal debugging
purposes. Setting this level to 0 will completely disable debugging
messages.
-
CYGOPT_DYNAMO_RAM_SIZE:
Memory buffer size
This option allows you to control how much memory should be
allocated to dynamo for virtual memory management. This value is
expressed in kBytes and represents the maximum total amount of
memory consumed by dynamo-managed processes.
-
CYGOPT_DYNAMO_KSTACK_SIZE:
Default kernel stack size for user threads
Each user thread uses a separate kernel stack for system calls, so
that the system is immune to user stack corruption. This option
controls the default size of this kernel stack.
-
CYGOPT_DYNAMO_DACR_MASK:
Domain Access Control Register mask
This option is used to control the subset of domains that are used
by the dynamo package. The MMU Domain Access Control Register
(DACR) allows to modify access permissions with a fast single write
operation. This option provides a 32-bit mask M which restricts
the way a value V is written to the DACR:
DACR := ( DACR & ~M )|( V & M ). For instance, a mask of 0x3ffffffc
allows dynamo to change access permissions for all domains except
domains 15 and 0. The default value, 0x00ffffff, leaves out domain
15,14,13,12 which can potentially be used by the generic FLASH
driver when Erase/Suspend features are enabled.
-
CYGDBG_MEMALLOC_ALLOCATOR_DLMALLOCUSER_DEBUG:
Debug build
This option enables Doug Lea's malloc internal checkings in order
to verify the operation and consistency of the allocator. This
results in a substantial overhead. It is implemented with
userland assertions : in case of a failing assertion, only the
faulting thread is terminated.
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:
- In most cases, the host eCos system is not aware of the shared library
internal symbols, and should not be (to allow independant code evolutions)
- It reduces the shared library symbol table size
- It drastically improves code efficiency and load time
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 RO (read-only) memory segment:
This segment is used for constants, read-only data, etc.
- A RW (read/write) memory segment:
This segment contains code (modified during relocation), data, bss areas
- A heap memory segment:
This is used for private dynamic memory allocation
(malloc, free, etc.)
- A collection of threads running in USER mode.
-
Each thread has a read/write access to its code, data, stack, etc. It
also has the same access privileges to all other threads in the same process.
In other words, all threads in a process share access to their code, data,
heap,
stack, etc.
-
Each thread also has a read-only access to eCos (including application
code) code, data, heap, etc. It has no access
to all other memory areas (I/O registers, unmapped memory, etc.).
- A collection of file descriptors.
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);
|
- sched_info:
Process priority. All threads belonging to this process will have a priority
greater or equal to this value.
- name: Process name.
- file:
The process shared library file name. When the process is created, this shared
library is loaded and relocated into the process memory space.
- entry:
The process shared library entry point. When the shared library has been
successfully loaded and relocated, a symbol with name entry is looked
up and jumped to if it was found. If entry is NULL then the default
entry "main" is used.
- domain:
The process ARM domain (1-15). Two processes with the same domain share access
to their memory segments.
- max_threads:
The maximum number of threads belonging to this process.
- max_mem:
The maximum amount of memory allocated to this process.
- handle:
Process handle. All further references to this process should be done using
this handle.
- this:
The process object. The process structure will be stored in this object.
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);
|
- dlopen:
The function dlopen() loads the dynamic library file named by the
null-terminated string filename
and returns an opaque "handle" for the
dynamic library. Parameter flag is ignored. An error code is returned
into dlerr upon failure.
- dlerror:
The function
dlerror() returns a human readable string corresponding to its
argument dlerr.
- dlsym:
The function dlsym() takes a "handle" of a dynamic library returned by
dlopen() and the null-terminated symbol name, returning the
address
where that symbol is loaded into memory. If the symbol is not found,
in the specified library or any of the libraries that were previously
loaded by dlopen(), dlsym() returns NULL.
- dlclose:
The function dlclose() unloads the dynamic library handle
handle.
TODO...