Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migration of unmigrated content due to installation of a new plugin

Table of Contents

Types of shared object

In our refactoring of RTEMS we've created several different types of shared objects. All are ELF dynamic objects in overall structure but each plays a different role:

  • RTEMS object (rtems.so). This contains most of RTEMS, newlib and other basic runtime support. It's unique in that it's not loaded by the dynamic linker; instead it's loaded by U-Boot at a fixed location, the start of the Run-Time Support Region of memory. U-Boot loads each segment of rtems.so at  the so-called physical address of the segment so we have to use a special linker script to set those properly. The RTEMS object is permanently resident, a.k.a. installed.
  • Symbol-Value Tables (*.svt).The dynamic symbol table of this type of object is used as a read-only lookup table whose content is defined by C source code. Each symbol's value is the location of a data object, generally a string or a struct. There can be up to 32 SVTs numbered 0-31, with number 31, the System table sys.svt, and number 30, the Application table app.svt, being loaded at system startup.
  • Ordinary shared objects, a.k.a. shareables (*.so). Basically add-on libraries that are installed when needed, e.g., nfs.so and console.so.
  • Tasks (*.exe). A task is roughly equivalent to a main program. The loader and dynamic linker cooperate to load a task and, when necessary, to load and install any shareables it needs. A task also has an associated thread which executes the task code starting at its entry point, always present and always named Task_Entry(). The properties of the task such as name, priority, etc., are usually looked up in an SVT. One or more tasks are automatically chosen, using more information stored in SVTs, to be loaded and run at system startup.
  • Device drivers (*.drv). These objects perform device initialization and may register RTEMS device drivers.

All types of objects except rtems.so:

  • Are loaded into storage that is allocated dynamically from the RTS Region.
  • May have a function called lnk_prelude() which is called after the object is fully linked and initialized but before the entry point, if any, is called. For instance driver objects have no entry point but do their work in their lnk_prelude() functions.
  • May contain a 32-bit word named lnk_options which is a bit-mask of dynamic linker options defined in tool/elf/linker.h:
    • LNK_INSTALL. Install this shared object.
    • LNK_USE_PREFERENCES. Find a preferences structure for the object and pass its address to lnk_prelude().

The lnk_prelude() function

Consider this the last stage of the construction of the object's image in memory. The image should not be considered complete before this function is run, so it's a programming error to have the image used by other images before lnk_prelude() has finished running.

lnk_prelude() takes two arguments and returns a standard facility/error code. The arguments are two void-pointers:

  1. prefs. Either NULL or the address of a preferences structure for the image. The function must cast the pointer to the type that's correct for the image.
  2. elfAddr. The address of the ELF file header.

Loadable segments

A shared object may have any number of loadable segments. One of them must contain the ELF file header and the program headers. Since the file header starts at the offset zero in the file then the segment that contains it will also have an file offset of zero and will usually have the lowest address as well. After loading the access permissions of the memory containing a segment are changed to match the permissions recorded in the segment. Permissions can change only at 4K boundaries in the RTS region so segments must be aligned tho 4K boundaries. We recommend the following set of segments:

  1. Read-only data containing the file header and the program header table.
  2. Executable, read-only text.
  3. Read-write data.

Versions of ld available

Target platformld versionNotes
Native Intel RHEL52.17Doesn't support -l:filename
Native Intel RHEL62.20 
ARM RTEMS 4.112.23+ 
ARM XIlinx Linux GNU EABI2.23+ 
ARM Xilinx EABI2.23+ 

We need the syntax -l:filename if we find shared libraries using -L and want to use arbitrary file names for them. I list the Intel linkers here in case someone wants to try, for example, a quick test of new linker script techniques without setting up the cross-compilation environment.

...

Required command-line

...

options

Driver control

OptionNotes

-B ${RTEMS_ROOT}/tgt/arm-rtems4.11/bsp-variant/lib

-specs bsp_specs

-qrtems

Compile and/or assemble and/or link for RTEMS.
-nostdlibDon't make the linker search standard language or system libraries.
-nostartfilesDon't tell the linker to link in the standard shared-object startup files.
-o output-filePut the output in the given file.
-c (optional)Compile and assemble only, producing relocatable object code.
-S (optional)

Compile only, producing assembler source code.

-E (optional)Preprocess only, producing pre-processed source code.

C/C++ compilation

OptionNotes
-fPICShared objects for ARM must use position-independent code. ld will check to make sure you've used it.
-Wno-psabiPrevents warnings about the implementation of stdarg.
-WallEnables all other warnings.

-march=armv7-a

-mtune=cortex-a9

Produce assembly code for the ARM Cortex-A9.
-DEXPORT='__attribute__((visibility("default")))'Use this macro in declarations in order to put the corresponding symbols into the dynamic symbol table.

Assembly code

OptionNotes
-x assembler-with-cppAllow preprocessor directives in the input source code.
-POmit #line directives in the preprocessor output, the assembler doesn't accept them.

 

Static linker

Some If given to a compiler driver instead of directly to ld some of these options need to be prefaced with "-Wl,". However a series of options can be given following a single preface, e.g., "-Wl,-soname=foo,--hash-style=gnu,-zcombreloc".

...

 

OptionNeeds -Wl,Notes
-shared To make a shared object.
-fPIC Shared objects for ARM must use position-independent code. This is actually a compiler option, ld will check to make sure you've used it.-e or --entry Specifies the entry point (use 0 if there is none).
-soname(tick)Make sure SO names are used in needed lists.
-zcombreloc(tick)Combine relocation entries into one or two tables and sort them by the index of the symbol referred to. This allows symbol lookups to be cached by the dynamic linker for efficiency.
--zmax-page-size=4096(tick)This is the page size used in the Run Time Support Region, the part of memory into which shared objects are loaded. Controls the alignment of segments.
--hash-style=gnu(tick)Provides more efficient symbol lookup.
-l:libname 

Refer to a shared library libname that's to be found using the search path established using -L options.

-L dirname Add dirname to the search path for ld, which uses the path for two purposes: to find libraries named using -l: and to find linker scripts named in include statements in other linker scripts (and named using -T).
-T scriptname(tick)Use scriptname as the main linker script.
--no-undefined(tick)Makes it an error if the resulting shared object has undefined references remaining after ld has finished making it. It allows needed objects to contain undefined references but presumably these too have been constructed using --no-undefined.

...

All the direct dependencies of the shared object being built should be searched or included as inputs, and undefined references should cause the build to fail (--no-undefined). Normally one would Almost always you should name only the objects to which the one being built makes symbolic references, as ld by default puts every object so named on the needed list. However, If you have to you can get ld to filter out those objects that don't always depend on symbolic references to tell which objects are needed. For example, suppose object A uses the /dev/foo device whose driver is installed by object B. Then A needs B even though A never makes symbolic references to B. If it weren't for situations like that you could just use the satisfy symbolic references by using the option --as-needed option on early in the ld command line and just mention all the other objects that the new object might need; the option ensures that only objects which satisfy symbolic references go on the needed list. Instead you'll have to .  You can turn this mode off in the rare cases in which you really need an object that doesn't satisfy some symbolic reference: use --no-as-needed in front of objects that you know will be needed and --as-needed in front of those that might be needed. For example. If you need to build an object a.so that might make symbolic references to b.so, c.so or d.so, and which doesn't make such references to e.so but still needs it, you command line would look something like this:

arm-rtemsx.yy-ld g++ -shared -o a.so -soname A ... a.o --no-as-needed b.so c.so d.so --no-as-needed c.so de.so ...

Dynamic symbol table

  1. Every shared object must have a GNU-style hash table (--hash-style=gnu). System V hash tables will be ignored and should not be generated.
  2. Certain symbols are used for data and functions used by the dynamic linker. Currently these are:
    1. "lnk_preferences" which labels object preference data to be passed to lnk_prelude().
    2. "lnk_prelude" which labels a function to be called by the dynamic linker just after the functions pointed to by the .init_array have been run.

...

The ARM cross-compilers use .init_array to hold pointers to compiler-generated functions that run constructors for statically allocated C++ objects. Therefore .init_array is processed before calling lnk_prelude(). However, lnk_prelude() can't itself be called using the .init_array mechanism because it takes arguments, it returns a status value and there's no way to control the ordering of entries in .init_array.

RTEMS shared object

The shared object containing RTEMS, newlib and other run-time support is unique in that it has to be loaded at a fixed location, the start of the RTS Region. The loading is done by U-Boot which looks at the physical addresses of the loadable segments in order to determine where to put them. In order to set the physical addresses properly the linker script for the RTEMS object assigns all output to a region of memory, defined using the MEMORY directive, whose origin is at the required location. This has the side effect of giving the lowest loadable segment in the shared object a non-zero starting virtual address which has to be taken into account when using symbols defined by the object. All other shared objects are created with virtual addresses starting at zero.