Purity (Req-Purity)

The linker code will rely as far as possible on standard C++ compiler and library support (the corrected 2003 standard prior to TR1). If there is a strong need, source code from approved open source projects may be added to the source code of the linker. Wholesale incorporation of third-party libraries is discouraged.

Modularity (Req-Mods)

Application code will be divisible into modules. A module has a name, a version, and a branch of development. The module and branch names conform to the syntax for C++ variable names. The version consists of two non-negative integers; a major and a minor version number normally printed in the form "%u.%u" (major first). Each module's object code can be loaded and relocated independently.

An application's modules may be presented to the linker in any order, either one at a time or in groups. When all of an application's modules have been so presented the linker may then be requested to bind the modules together, that is, resolve symbolic references between modules. This will reveal which modules depend on which; this information in turn will allow modules to be initialized in the proper order.

ELF image (Req-Elf)

A module's object code is presented to the linker as a single ELF object in the target system's memory, e.g., using a pointer to the base address of the object. The ELF object must conform to the format for 32-bit PowerPC so that the content can be read without byte swapping. A file containing the module code on the development host must be able to be handled by the cross-development tools, e.g., ld, readelf and objcopy.

In situ relocation (Req-Reloc)

Each module's ELF image must be relocatable and runnable at the location at which it was loaded. No copying of the image content should be needed nor should any storage need to be allocated for the uninitialized variables.

This requirement forces the use of module ELF objects that are shared objects. Shared objects are the only kind of ELF object that can be relocated in situ at runtime because:

  1. They have had their storage laid out; many disjoint "sections" of content have been coalesced into a few contiguous "segments". Segment offsets within the object have the same alignments as required at runtime; one just needs to load the entire object at a suitable boundary.
  2. They still have the required relocation information, symbol tables and symbol hash tables.
  3. The special ".dynamic" section allows quick access to the symbol and relocation information. It also contains the "needed" list of other modules (actually sonames) required by the containing module.

Even so the uninitialized variables require special handling. Although the C++ standard requires that their storage be set to zeros those zeros are not put in any ELF section by the compiler (the sections emitted have the "NOBITS" attribute). A special section containing a suitable number of contiguous zero bytes will have to be inserted into the ELF image in order to give the uninitialized variables a place to exist.

Strict one-definition rule (Req-OneDef)

An application may consist of a number of modules but no module is considered as the "main" module or in any way superior to other modules as far as symbol resolution goes. No module's definition of a symbol may supersede the same definition in another; the presence of duplicate exported definitions is an error. This is different from the situation under Linux where a definition in the main program can override one in a shared library and where one can use the LD_PRELOAD mechanism.

Special handling is required to avoid the violation of this rule by compiler-generated code: default constructors/destructors/assignment operators, placement new, non-inlined inline functions, type information, virtual function tables, template instantiations, etc. These are emitted as "weak" definitions in each module that uses the relevant header files. The dynamic linker will ignore all weak definitions when binding modules.

With GCC 4.0 and later there is an alternate method for handling such definitions: compile with the option -fvisibility=hidden. Then only variables, classes, and functions marked for export (given "default" visibility) will be registed in the symbol table used by the dynamic linker; compiler-generated code will not be so marked. See the discussion of this topic in section 2.2 of Drepper's HOWTO.

Standard host tools (Req-HostTools)

The ELF object for each module should be constructed using the standard GNU cross-development tools in the RTEMS BSP for the RCE boards; the source code for the tools should not have to be modified. That said, the customization features offered by the toolchain should be exploited, e.g., special compilation options and custom linker scripts. One may also manipulate the ELF object with tools such as objcopy if needed in order to satisfy other requirements. Calls to standard tools may hidden inside wrapper scripts which construct command lines in canonical ways.

Cross-module references (Req-Xref)

Modules must be able to use classes, functions and variables defined in other modules. One envisages a system similar to that used under Linux to develop shared libraries: When using ld to create the module ELF object on the development host one has it search the symbol tables of the modules it depends on. Final resolution of undefined references is delayed until runtime.

The address of the dynamic section of the ELF object containing the dynamic linker will be presented to the linker's constructor; ld sets the value of the symbol _DYNAMIC to this address. The linker will use the symbol table of this "system core" object to resolve symbolic references but the linker will never modify the core object. As for modules weak definitions are ignored.

Runtime initialization (Req-Init)

Each module has an initialization routine that will have to be called before any other code in the module is run. The init routine is accessible in some standard way, e.g., a standard location or symbol. For most modules the init routine is stereotyped, calling static constructors, registering static destructors and registering call-frame description information.

Running any module code, even initializers, may alter data in arbitrary ways. In fact since modules are bound together even data in other modules may be altered. The linker will have no way to restore the modules to their pristine states. If you need that you'll have to drop the modules, load fresh copies and call relocate() again.

The order in which modules are initialized is partially determined by the inter-module dependencies; all modules that module X depends on will have to be initialized before X. The linker keeps track of dependencies and use a topological sort to determine a suitable initialization order. There must obviously be no dependency cycles.

A module may call atexit() or __cxa_atexit() in an attempt to register code to be run when the application stops. Each module will be provided with special versions of such functions that instead register the code to be run at the time the module is dropped.

This changes the meaning of the code. Module writers will have to bear in mind that a module is initialized only once and is finalized only once. The module's other code may be called any number of times in between.

Module dropping (Req-Drop)

It should be possible to "drop" modules individually or in groups. As for Req-Init, the module dependency graph must be taken into account; dropping any module X implies first dropping in the correct order all modules that depend on X (actually, the transitive closure of the set of all modules that depend on X). Dropping here means running the finalization code then removing a module's image from the set known to the linker. The image's storage is not deallocated since the linker did not allocate it.

  • No labels