CYGPKG_OBJLOADER — Extending the Object Loader


The Object Loader package has a number of features that allow it to be extended. To support a new CPU architecture a new relocator needs to be written. If ELF files are to be read from a source that differs from those currently supported, then a new loader needs to be written. Finally, the mechanism by which the loader allocates the memory used to store loaded sections can be redirected by the application.

Adding New Relocators

When the loader loads a new module some locations in it must be adjusted to account for the address at which is it loaded. References to external symbols must also be installed. The location and nature of these modifications are described by one or more sections in the ELF file which contain a sequence of relocation records. The exact meaning of the relocations that these records define is architecture specific and is usually described as part of the ABI for that CPU type.

To define a new relocator for a CPU, it is necessary to add an extra definition to the objloader.cdl file, and add a header and source file to the package. To support the XYZ CPU the following must be added to the CYGPKG_OBJLOADER_ARCHITECTURE component:

  display       "Support loading on XYZ processors"
  calculated    CYGPKG_HAL_XYZ
  define_proc {
     puts $::cdl_header "#include <cyg/objloader/relocate_xyz.h>"
  compile relocate_xyz.c

The relocate_xyz.h file needs to define some macros to customize the loader:

This defines the value that the e_machine machine type field of the ELF header. If this does not match, the module load will fail.
This defines the value that the EI_DATA byte of the e_ident field of the ELF header. It should be either ELFDATA2LSB or ELFDATA2MSB. If this does not match, the module load will fail. Architectures that are bi-endian need to test either a compiler or a HAL definition to select the correct endianness for the current build.
This macro is used to combine the section address of a symbol with information from its symbol table entry. The addr argument is the base address of the section in which the symbol is defined. The sym is the symbol table entry; this is not a pointer, so fields should be accessed using the "." operator, not the "->" operator. The return value should be of type void *. This is an optional macro, if it is not defined here then a default definition will be used which simply adds the st_value field of the symbol table entry to the address.

In addition to these, it may also contain definitions that are useful to the relocator. Typically the relocation record types and any support macros may be defined here.

The relocate_xyz.c file contains two functions:

void cyg_ldr_flush_cache(void)
This function is called to perform any cache flushing needed. The loader modifies code in memory that will subsequently be executed. It does this using data accesses, so it is essential that these updates are flushed from the data cache, and that stale entries are flushed from the instruction cache. This function must call appropriate HAL cache operations to ensure that this is done.
cyg_int32 cyg_ldr_relocate(cyg_int32 rel_type, cyg_uint32 flags, cyg_uint32 mem, cyg_int32 sym_value)

The loader will call this function for each relocation record in each relocation section found in the module.

The rel_type argument defines the relocation record type and will be one of the relocation types defined for the architecture. Most architecture ABIs define a large number of relocations, not all of which will be relevant to the use that eCos makes of the object file format. In general only a small subset of relocation types need to be handled which can usually be determined by inspecting the object files generated by compiling eCos.

The flags argument contains flags that provide additional information about the relocation record. At present only one flag is defined: CYG_LDR_FLAG_RELA which is set when the relocation is from a RELA record, otherwise it comes from a REL record.

The mem argument contains the address of the location in memory to be relocated. It is constructed from the base address of the segment targeted by the relocation section, plus the r_offset field from the relocation record.

The sym_value argument contains the address of any symbol associated with the relocation record. If it was a RELA record, then the contents of the r_addend field will have been added.

Adding new Loaders

The Object Loader package needs to fetch a module from some source to load it into memory. This is the job of a loader. A loader consists of an open function plus read, seek and close functions.

The loader open function is supplied as the first parameter to cyg_ldr_open(). It is called with the second argument as a parameter. On success it returns a pointer to a CYG_LDR_ELF_OBJECT object. On failure it returns NULL.

The open function has a number of duties, best described by an annotated example for the ABC loader:

__externC CYG_LDR_ELF_OBJECT *cyg_ldr_open_abc(CYG_ADDRWORD arg)
    // Allocate a CYG_LDR_ELF_OBJECT
    CYG_LDR_ELF_OBJECT * obj = (CYG_LDR_ELF_OBJECT *)cyg_ldr_malloc(sizeof(CYG_LDR_ELF_OBJECT));

    // Allocate a private data descriptor. Depending on the nature of
    // the loader this may not be necessary.
    struct abc_desc *desc = cyg_ldr_malloc(sizeof(struct abc_desc));

    // Check that the memory allocations worked
    if( obj == NULL || desc == NULL )
        if( obj != NULL ) free(obj);
        if( desc != NULL ) free(desc);
        cyg_ldr_last_error = "ERROR IN MALLOC";
        return NULL;

    // Perform any operations to enable access to the ELF file and
    // fill in the descriptor. If this fails then free both descriptor
    // and ELF object, set cyg_ldr_last_error and return NULL.

    // Clear the CYG_LDR_ELF_OBJECT
    memset( obj, 0, sizeof(CYG_LDR_ELF_OBJECT));

    // Install private data pointer
    obj->ptr    = (CYG_ADDRWORD)desc;

    // Install pointers to read, seek and close functions
    obj->read   = cyg_ldr_abc_read;
    obj->seek   = cyg_ldr_abc_seek;
    obj->close  = cyg_ldr_abc_close;

    // Return completed object
    return obj;

The read function will be called via the pointer in the ELF object whenever the Object Loader needs to read data from the file. It has the following definition:

static size_t cyg_ldr_abc_read(struct CYG_LDR_ELF_OBJECT* obj, size_t size, void* buf)

The obj argument is the ELF object returned from the open function. The size argument gives the number of bytes to be read and buf points to a location to store them. The function returns the number of bytes read.

The seek function will be called via the pointer in the ELF object whenever the Object Loader needs to reposition the point in the file at which the next read will occur. It has the following definition:

static cyg_int32 cyg_ldr_abc_seek(struct CYG_LDR_ELF_OBJECT* obj, cyg_uint32 offset)

The obj argument is the ELF object returned from the open function. The offset argument gives the number of bytes from the start of the file to which the read point should be moved. The function returns the new read offset. Some sources may not be able to reposition the read pointer backwards, and may only be capable of advancing it. If the reposition fails then this function should return -1.

The close function will be called via the pointer in the ELF object when the Object Loader has finished with the file. It has the following definition:

static cyg_int32 cyg_ldr_abc_close(struct CYG_LDR_ELF_OBJECT* obj)

The obj argument is the ELF object returned from the open function. This function should close down access to the file, free the private data descriptor if necessary and set the obj->ptr field to zero. It should not free the ELF object itself, the Object Loader will do this itself later. If the close succeeds then this function should return zero, and -1 if it fails.

Redirecting Memory Allocation

All memory allocation in the Object Loader is made via the cyg_ldr_malloc() function and it is freed via the cyg_ldr_free() function. These have the following prototypes:

__externC void *cyg_ldr_malloc(size_t) CYGBLD_ATTRIB_WEAK;

__externC void cyg_ldr_free(void *) CYGBLD_ATTRIB_WEAK;

These functions by default simply call the standard malloc() and free() heap functions. However, they are defined with the weak linker attribute. This means that the application can redefine these functions to provide an alternative allocation and free mechanism if, for example, the standard heap support has been omitted.