3

Object-Module Loader



3.1    Introduction

The object-module loader is the component of the target server that inserts application modules into a remote target system while the target image is running. Loading and unloading object modules dynamically is a key service provided by the target server. The target server provides this and other services commonly required by the Tornado tools in addition to acting as a broker for the communication path to the target.

You can support additional object-module formats with Tornado by writing a new loader. The following sections provide an overview of loader usage and loader architecture, followed by a discussion (in general terms) of how to develop a reader for a new object-module format.

For an overview of the target server, the back end, and their components, see 1.3 The Target Server and the WTX Protocol and 2. Target Server Back End. This chapter provides an overall orientation to object-module loaders; throughout this chapter, refer to the associated reference entries for details.



3.2    Terminology

Although the following terms are in common usage, we review the distinctions among them:

object file
The unrelocated output of a compiler or assembler (conventionally produced with a compiler's -c option, and named with a .o suffix).

object module
The contents of an object file.

object module format (OMF)
The convention in which the data in an object module is organized. Several different OMF standards exist, such as a.out, COFF, and ELF. On the whole, OMFs are independent from the underlying architecture. However, the relocation information contained in object modules is architecture dependent.

relocatable file
An object file for which text and data sections are in a transitory form, where some addresses are not yet known. These sections must be modified (relocated or linked) when they are loaded in order to be executable. In a cross-development environment, information not known about an object module at compilation time includes the program execution address (also called the entry point) and the addresses of externally defined symbols (such as library routines). By default, the loader assumes that some module addresses are unknown and that relocation is required.

executable file
A file that is fully linked and ready to run at a specified address. For such files, no relocation stage is required. The loader is able to load files that do not require a relocation stage, but it must be told that the file type is fully linked.

object module header
The portion of an object module that holds information such as the type of object module (often called the magic number) and the sizes of the different sections.

raw data
The main part of an object module, corresponding to the program code and data. The host loader can handle four kinds of sections:

  • Text sections hold the program code.
  • Data sections hold the initialized program variables.
  • Bss sections hold the uninitialized program variables.
  • Literal sections hold the program constants and strings.

Depending on the OMF, an object module may contain several sections of each type. (This is true of both COFF and ELF.) It is also possible that the text and data sections may be the only two sections in the module. (The a.out format follows this model; however, a.out sections are typically referred to as segments.)

symbol entries
Information about every symbol defined (local symbols) or referred to (external symbols) in the object module. The term symbol refers to every variable, routine, or constant that is accessible from other parts of the program (global) as well as variables, routines, and constants that are accessible only from within the object module (local/private).

relocation entries
Information required to adapt the program to its execution environment. The relocation entries point to each place where a relocation is required, and indicate the type of operation required to perform it.

string table
A table containing all symbol strings. For a.out and ELF it holds all strings; for COFF it holds only strings larger than a certain size.

external
External symbols are public (visible to everyone both within and outside the file that declares them). The term external is not used in this document to mean "declared outside the file" (as opposed to "declared inside the file").



3.3    Loader Overview

Linking and Loading

The following steps are required to link and load a module onto the target:

  • Read and analyze the object file.
  • Process the object-module symbols.
  • Relocate the symbols.
  • Transfer the module code and data to the target environment.

These steps are apportioned between two internal parts of the loader: symbols are relocated by the relocation unit (RU - the portion of the loader that performs relocations) and all other steps are performed by the object-module-format manager (OMF manager).

Bootstrapping the Target Server Symbol Table

In addition loading modules to the target, the loader plays an additional role when the target server establishes a connection with a target agent. The loader attempts to obtain the pathname to the core file from the agent. The core file is the file on the host that corresponds to the runtime system initially executing on the target. This file is not downloaded to the target system because the core image is already present there (typically in PROM). If the core file is found, the loader reads this file in order to bootstrap the target server symbol table. The resulting symbol table contains the name, address, and type of every global symbol found in the VxWorks image at the time it is booted. (For more information, see the reference entry for symSynch( ).

If the checksums of the text regions do not agree, the loader prints an error message, because a symbol table built from this core file might not contain the correct addresses on the target.

If the core file is not known, the target server exits with an error message. Try connecting the target server again using the -c option to specify the core file path and name.



3.4    Using the Target Server Loader

Tornado provides access to the loader by several routes:

  • the Tornado shell
  • the WTX Tcl API
  • the WTX C API
  • the WTX Java API

3.4.1   Loader Usage from the Tornado Shell

The Tornado shell provides an interpretive environment that offers easy access to the loader. From the shell, access the loader through the ld( ) command:

-> ld symbolsVisibility, commonSymbolsPolicy, "objectFile"

The symbolsVisibility argument may be:

-1

0

1

The commonSymbolsPolicy argument may be:

0

1

2

For a complete discussion of loader behavior, see 3.5 Loader Architecture.

To call the loader from the shell, enter:

-> ld 0, 0, "/home/users/me/myFile.o"

You can also use input redirection:

-> ld </home/users/me/myFile.o

3.4.2   Loader Usage From wtxtcl

Another way to access the loader facilities of the target server is through the WTX Tcl binding to the WTX protocol. This environment offers the scripting advantages of Tcl and the ability to manipulate the WTX protocol directly. From the wtxtcl tool (see 4.4.2 Starting a wtxtcl Session), access the loader through the wtxObjModuleLoad routine. For more information, see the reference entry.

wtxtcl> wtxObjModuleLoad [option] object- file

The option argument is an optional parameter from the following list:


LOAD_GLOBAL_SYMBOLS1

LOAD_COMMON_MATCH_NONE*

LOAD_LOCAL_SYMBOLS

LOAD_COMMON_MATCH_USER

LOAD_NO_SYMBOLS

LOAD_COMMON_MATCH_ALL

LOAD_ALL_SYMBOLS

LOAD_HIDDEN_MODULE

LOAD_FULLY_LINKED

LOAD_BAL_OPTIM

LOAD_NO_DOWNLOAD

LOAD_FILE_OUTPUT

LOAD_CORE_FILE


1:  The default values are LOAD_GLOBAL_SYMBOLS and LOAD_COMMON_MATCH_NONE.

You can combine several options by linking them with the "|" character. For a detailed discussion of these parameters, see 3.5.4 Loader Options.

Examples:

wtxtcl> wtxObjModuleLoad /home/users/me/myFile.o 
wtxtcl> wtxObjModuleLoad LOAD_ALL_SYMBOLS /home/users/me/myFile.o 
wtxtcl> wtxObjModuleLoad LOAD_GLOBAL_SYMBOLS|LOAD_COMMON_MATCH_USER \ 
    /home/users/me/myFile.o

3.4.3   Loader Usage From the WTX C API

You can also call the loader from C programs on the host, using the WTX C language binding. For details about using wtxObjModuleLoad ( ), see the online reference material under Tornado API Reference>WTX C Library. For general information on using C, see 4.5 WTX C API.

3.4.4   Asynchronous Load Operation

The target-server loader implementation allows you to submit load operations asynchronously. Three APIs provide routines that support the loader: the Tcl, C and Java APIs. Each API provides three routines to handle this feature. The routines are listed below according to the Tcl convention:

wtxObjModuleLoadStart
Submit a load operation. This routine returns when the file has been written to the target server memory.

wtxObjModuleLoadCancel
Cancel a submitted load.

wtxObjModuleLoadProgressReport
Get information about the status of a submitted load. The returned information can be one of the following:

LOAD_PENDING
The load is in queue.

LOAD_INITIALIZED
The load is being processed.

LOAD_RELOCATING
The loader is relocating symbols.

LOAD_DOWNLOADING
The loader is downloading the segments on the target.

LOAD_COMPLETE
The load has been done.

LOAD_ABORTED
The load has been cancelled.



3.5    Loader Architecture

The loader has a three-layered structure. At the generic level is a library of routines that are common to all configurations. At the object-module format level are several OMF managers to handle different object-module formats. At the architecture level are several RUs (relocation units), each supporting a specific architecture. Figure 3-1 illustrates the relationship between the three levels.   

Figure 3-1:   Target Server Loader Architecture

 

3.5.1   Object-Module Configuration in Target Memory

Since the object-file sections hold a program (a set of routines), and since this set of routines must be installed in the real target memory, there is a strong relationship between section configuration in the file and in the target memory. How the loader locates the sections in memory depends on whether the file type is relocatable or fully linked.

Relocatable Files

The loader uses a three-segment model for loading relocatable modules. It coalesces the module sections into three segments in target memory: a text segment, a data segment, and a bss segment. It combines sections of the same or compatible type into one segment (see Figure 3-2). Note that the order of coalescence is strictly the order in which the text and literal sections exist in the file.

Figure 3-2:   Section Combination in Relocatable Files

Because the loader creates only three segments, it handles literal sections in the same way as text sections. If literal sections are present in a relocatable object module, the loader coalesces them with the text sections. The sections are similar in that contents of both types of sections must not be modified while the program is executing.


*      
NOTE: The loader also treats other constant data sections, such as ELF .rodata sections, as literal sections and coalesces them into the text segment.

Fully Linked Files

The loader retains the file configuration for fully linked files. Each section is represented separately in target memory. This allows an application to take advantage of a target system with non-contiguous RAM or with high-speed static RAM and dynamic RAM.

3.5.2   Module Management

The generic module management library keeps track of what modules have been loaded in memory, where their segments are, how much memory they use, and which symbols belong to which modules.

During loading, a temporary data structure, SEG_INFO, describes the contents of an object module. The SEG_INFO structure contains the following elements:

  • the text segment address
  • the data segment address
  • the bss segment address
  • the text segment size
  • the size of the text segment that is write-protected (if any)
  • the data segment size
  • the bss segment size
  • the text segment option for memory management
  • the data segment option for memory management
  • the bss segment option for memory management

When the loader finishes loading the object module, it transfers the SEG_INFO contents to a more permanent representation. Each module representation is a member of a linked list and is located by means of a MODULE_ID handle. The module representation contains the following information:

  • the name of the object file
  • the symbol group number
  • the format of the object module (a.out, COFF, ELF, and so on)
  • the target CPU type
  • the loader options (also known as load flags)
  • a list of the object-module undefined symbols (if any)
  • a linked list of the object-module segments

Each segment in the linked list has a representation that contains:

  • the segment address
  • the segment size
  • the segment type (text, data, or bss)
  • the memory management information
  • the segment checksum

Type definitions for both MODULE_ID and SEG_INFO are in module.h.

The ability to unload a module is an important outgrowth of module management. Unloading a module implies freeing the memory associated with the module segments and removing the module symbols from the target server symbol table.

When creating a new OMF manager, you can use the code from existing loaders as a model for providing module management.

3.5.3   Symbol Management

The loader processes the object-module symbols by reading the file symbol table. Part of this processing is required for relocation. Additional processing is necessary to add the module symbols to the target server symbol table.

The target server symbol table holds all the symbols related to the remote system, including symbols from the target agent and symbols from dynamically loaded user modules. Maintaining this table within the target server on the host saves memory on the target and gives host tools access to all public symbols (and local symbols, if desired) that are present on the target system. The target server symbol table holds one entry per symbol, with the following information:

  • the name of the symbol
  • the address of the symbol
  • the segment type of the symbol (text, data, or bss)
  • the object-file name from which the symbol comes

The target server symbol table is filled at target server start-up time with the core-file symbol table. (For more information on this process, see Bootstrapping the Target Server Symbol Table.)

Symbol Processing

When a relocatable file is loaded, its symbol table is analyzed and the symbols are processed depending on the categories they belong to:

These symbols refer to objects (routines, variables, or constants) that are present in the module text, data, or bss segments. They may be static (their scope limited to the module itself) or global (accessible to objects outside the module). Defined symbols need no relocation and may be added directly to the target server symbol table if desired.

These symbols are referred to in the module text or data but are not present in the module. They must be global. For such symbols, the loader must locate the symbol in the target server symbol table in order to relocate the module. If the symbol is not found, it is considered unknown and its name is added to a list that is returned to the tool requesting the load.


*      
WARNING: While a module containing undefined symbols is downloaded anyway, the consequence is that the module may be partly or totally unusable since no relocation can be done on the unknown symbols. This partial linkage permits testing pieces of an application during development as long as the tested pieces do not hold references to undefined symbols.

Consider the example below:

    #include <stdio.h> 
 
    int willBeCommon; 
 
    void main (void) {} 
        { 
        ... 
        }

The symbol willBeCommon is uninitialized, so it is technically an undefined symbol. Such a definition becomes common. ANSI C allows multiple files to use uninitialized symbols to refer to the same variable and expects the loader to resolve these references consistently. These symbols are handled as a special case.

It is often helpful in an incremental environment to be able to share a symbol among several files and to load the files in any order; however, it is also extremely risky to treat common variables in this way. When linked, a common definition could resolve to any defined symbol in the symbol table depending on what symbols are defined in already-loaded modules. The default for Tornado treats common symbols as undefined. However, you can set the loader options to permit common symbols to be loaded; then only the first instance of the symbol will be added to the symbol table and other instances will use the same address. For more information, see 3.5.4 Loader Options.


*      
NOTE: External symbols are public (visible to everyone both within and outside the file that declares them). The term external is not used in this document to mean "declared outside the file" (as opposed to "declared inside the file").

Symbol Type Definitions

The loader defines a set of symbol types (SYM_TYPE) in symbol.h in order to handle the symbols more easily. The loader uses the types to classify the module symbols. The symbol types defined by the loader are:

SYM_UNDEF

SYM_LOCAL

SYM_GLOBAL

SYM_ABS

SYM_TEXT

SYM_DATA

SYM_BSS

SYM_COMM

3.5.4   Loader Options

The loader behavior may be tuned by using options. Many of these options may be combined, although some are mutually exclusive. The option names given below are the names used internally by the loader. They are defined in loadlib.h.

Symbol Scope

As explained in 3.5.3 Symbol Management, any defined symbol may be added to the target server symbol table. Several options allow the user to specify exactly which symbols should be added to the table:

LOAD_LOCAL_SYMBOLS

LOAD_GLOBAL_SYMBOLS

LOAD_ALL_SYMBOLS

LOAD_NO_SYMBOLS


*      
WARNING: These options affect not only the visibility of the symbols but also the load operations that occur after the module is loaded. For instance, if you load a module with the LOAD_NO_SYMBOLS flag set, none of its global symbols is added to the target server symbol table. Not only are they hidden from the other modules, but they are also unreachable; no reference is possible to these symbols. If you load another object module that refers to a symbol (for example, a routine) in the previous module, the loader cannot find the reference and the symbol is considered unknown. It is also impossible to call a routine within such a module symbolically from the shell. The only way to call the routine is by using its address.

Module Visibility

When a module is loaded, the target server normally records it. Any tool can request and receive information related to the module. This default behavior can be changed by using the following option:

LOAD_HIDDEN_MODULE

Module Reload

Before processing a load request, the target server looks to see if a module with the same name exists in its module list. If so, the target server unloads the previous module instance. This behavior is different from the target loader, which keeps all module instances loaded.

LOAD_MODULE_INFO_ONLY

Module Type

The loader default considers all files relocatable object-modules requiring a relocation stage (see 3.5.1 Object-Module Configuration in Target Memory). To load files that do not require a relocation stage, the loader must be told that the file type is fully linked. Two options are available for fully linked files:

LOAD_FULLY_LINKED

LOAD_NO_DOWNLOAD

LOAD_CORE_FILE


*      
CAUTION: The loader does not allocate memory on the target system when a fully-linked file is loaded as it does for relocatable files. The file segments are located at the addresses described in the file header; this depends on the OMF.

Common Symbols

Three mutually exclusive options provide the ability to specify how the loader handles common symbols. They are listed in order of decreasing strictness:

LOAD_COMMON_MATCH_NONE

LOAD_COMMON_MATCH_USER

LOAD_COMMON_MATCH_ALL

If several matching symbols exist for options LOAD_COMMON_MATCH_USER and LOAD_COMMON_MATCH_ALL, the order of precedence is: symbols in the bss segment, then symbols in the data segment, then symbols in the text segment. If several matching symbols exist within a single segment type, the symbol most recently added to the target server symbol table is used.

Special Options

The loader accepts special options, with scope limited to specific cases:

LOAD_BAL_OPTIM (i960 targets only)

LOAD_FILE_OUTPUT



3.6    Memory Management

The loader manages memory allocated from two entirely different memory pools. The loader is a component of a host process, the target server, and is able to allocate and free memory from the host heap using standard routines such as malloc( ) and free( ). However, the loader also manipulates data that it then transfers to the target system. The transfer requires the loader to allocate and free memory on the remote system as well. The loader does this through an internal library that manages a region of target system memory (the target server memory pool). The memory manager on the host also implements a host cache for portions of the target memory; this minimizes the number of data transfers to and from the target.

To understand how the target server manages the target server memory pool, refer to the online reference material under Tornado API Reference>Target Server Internal Routines. The routines listed are ANSI-compliant implementations; they have unique names to avoid conflicting with the host-system C library.

The interface to target server cache management is tgtMemCacheSet( ). This routine specifies the attributes of a block of host-mapped target memory. Blocks defined through this routine must not overlap. You must not change block boundaries once a block is created; however, you may call this routine again on a previously defined block to change its attributes.

The memory attributes of the target server memory pool are the following:

MEM_NONE

MEM_NO_WRITE

MEM_HOST

MEM_TEXT

To access target memory, all memory transfer requests must go through the routines tgtMemRead( ) and tgtMemWrite( ). For more information, see the synopsis of tgtlib in the online reference material under Tornado API Reference>Target Server Internal Routines.

3.6.1   Memory Alignment

All memory obtained from the target server's target-memory manager is free from memory alignment problems because the target server memory manager always returns addresses that conform to the target alignment requirements. All tgtMemXxx( ) routines gracefully handle memory alignment.

However, alignment problems may occur when the object-module headers are processed by the loader. In general, the headers contain data that is packed (without padding) whereas the host architecture may require padding to represent the header information. For example, 16-bit quantities are often enlarged to 32-bit quantities by padding.

This can cause problems if the object module is not directly processed as a file but rather read entirely into memory first and subsequently processed. This is done to improve performance, but it can also lead to read-word operations at unaligned addresses. Some host architectures accept this, but others do not. Use the UNPACK_16 and UNPACK_32 macros (defined in host.h) to prevent portability problems. These macros offer the ability to read 16-bit (or 32-bit) data regardless of the alignment.

Another kind of alignment problem is related to the three-segment model imposed by the target server loader. In this model, sections of the same type are loaded together within a global area allocated for the coalesced segment. Each section is aligned according to the requirement specified in the object file, or, if none is specified, each is loaded at an address that satisfies the target architecture alignment requirement. The requirement specified in the object file cannot be larger than the target alignment requirement. Because of alignment requirements, two coalesced sections may require a "hole" between them (depending on their sizes) to assure proper alignment. Be sure to account for this detail when computing the size of the segment sufficient to hold the coalesced sections.

3.6.2   Target Server Memory Cache

The goal of the target server memory cache is to speed up object-module download and minimize the impact of module loading on target performance. It achieves this goal by performing all object-module relocations in host memory and then copying the final relocated module to the target.

In an ideal world, the target server memory cache would be the same size as the target server memory pool on the target. Then the cache could shadow the entire memory pool. In the real world, the size of the cache is limited by the size of host memory. The default for the target server cache is 1 MB. Use the -m cacheSize option to specify a larger size.

If the target server memory cache is smaller than the target server memory pool, the first object modules load quickly. However, once the cache is full, each relocation generates a memory-write transaction with the target. This slows the load process, especially if you are using a serial line.

When the target server memory pool is full and an additional module is loaded, the target server uses malloc( ) to request more memory. The target server always requests 1 KB more than it needs in order to limit the number of requests to the target for small chunks of memory.

Rules for sizing the cache and the target server memory pool:

  • Always try to make the target server memory cache large enough to hold all the object modules to be loaded:

target -m bigEnoughSize

  • If you cannot allocate that much memory, make the cache size as large as your host can accommodate without swapping.

  • If possible, install additional memory on the development host.

  • Obtain optimum efficiency by making the WDB_POOL_SIZE in config.h at least as large as required to load all the object modules. However, this is not essential since it is extended as necessary on the fly.

3.6.3   Target Virtual Memory and Cache Management

Although the loader has memory attributes that can prevent corruption of target memory by the host (MEM_NO_WRITE), it cannot prevent the target from corrupting its own memory. Memory on the target side can be protected by the optional product VxVMI, which utilizes the CPU memory management unit (MMU) to designate regions of memory as read-only. The loader has access to this product (if it is installed) through the routine tgtMemProtect( ). Such protection can be useful when text segments are loaded; they represent program code that must not be corrupted by the execution of another program.

The target instruction cache, if present, creates problems for the loader if it is not updated when modules are downloaded. If incoherence exists between the instruction cache and the target memory, incorrect instructions can be executed from the cache. To prevent this, the loader updates the target instruction cache by calling tgtCacheTextUpdate( ) each time it loads a module.

3.6.4   Type Abstractions and Address Manipulation

Type abstraction is a way to avoid dependencies on the host definition of the size of various types. Tornado uses types with explicit sizes whenever information from the object modules must be manipulated. This information includes, for instance, the header-field contents or any other structural information such as relocation entries.

Type abstractions can be found in host.h and loadlib.h.

The type void * is used whenever an address is manipulated (through the more convenient type SYM_ADRS).



3.7    Generic Loader-Library Interface

The target server includes a generic loader-support library that acts as a common part for all loaders. It includes the loader interfaces with the target server and provides utility routines that facilitate creation of new OMF managers and RUs.

For information about the generic loader-library interfaces, see the online reference material under Tornado API Reference>Target Server Internal Routines. To utilize this interface in writing a new OMF reader, see 3.9 Writing an OMF Manager.



3.8    Target Server Loader Thread

The target server has a specific thread which handles all the load operations. This design has the following advantages:

  • The load process can be done asynchronously, so the client which submitted the load can return without waiting the load to complete.
  • The OMF reader need not be reentrant because only a single thread performs the load operation.
  • A load operation can be cancelled at user request.

Figure 3-3 illustrates how a load is performed by the target server. 

Figure 3-3:   Target Server Loader Thread architecture

  • The WTX threads (servicing a client) submit a load operation to the FIFO list. They return to the client when the memory module is loaded in the target server memory.
  • The target server load thread takes the first request and processes it. Once the module loaded on the target, the loader thread puts the module information into the "complete load" list for the WTX client.
  • If a WTX client cancels its submitted load, the loader thread stops the current load operation, and processes the next one.


3.9    Writing an OMF Manager

The remainder of this chapter describes how to write a new OMF manager. Code examples are based on Wind River Systems OMF managers. They do not cover every possible situation, but they do illustrate common OMF issues.

In the remaining sections, comments enclosed between square braces ([comment]) in the code fragments indicate lines of code not detailed for the sake of clarity. Function calls with Omf as part of the name, with or without parameters indicated by (...), are examples of how to use function calls.

3.9.1   Dynamically Linked Library Implementation

An OMF manager and its RUs form a single component, the OMF reader. The OMF reader is implemented as two dynamically linked libraries (DLL): one for the OMF manager and a second for the relocation unit. The DLLs offer the ability to load and unload portions of code at run time. They can be shared by programs without being linked into the programs. DLLs have several advantages:

  • Program Size: Since only the required pieces of code are loaded, the program (in this case the target server) uses less memory.

  • Scalability: It is possible to build an application dynamically from a reduced set of routines that exactly fits the user's needs.

  • Flexibility: It is easy to add function without recompiling the main program.


*      
NOTE: DLLs should export only necessary data to maintain good performance.

The OMF-reader code is common to all supported platforms.

3.9.2   Installing a Shared-Library Manager

The target server reads installDir/host/resource/target/architecturedb when it starts. This resource file establishes the relationship between the target CPU family, the object-module format, the relocation unit, and the corresponding shared libraries. OMF and RU names are established according to the conventions outlined in 3.9.3 Naming Conventions. Shared libraries are listed without their extensions because the extension is architecture dependent (.so for Solaris, .sl for HP-UX, and .dll for Windows NT).

At initialization, the target server attaches in turn each OMF manager listed in the resource file. It tests whether or not the OMF manager can handle the core file and if not, detaches it. When the target server finds an OMF manager that can read the core file format, it keeps that OMF manager attached and continues with initialization. If it finds no appropriate OMF manager, it exits with an error message.

3.9.3   Naming Conventions

In addition to the Wind River Systems coding conventions, specific naming conventions are adopted by the loader for the OMF-manager and RU interface routines, and for the related shared libraries file names.

Interface Routines

The Omf part of both OMF and RU routine names depends on the OMF strings supported for a given CPU, which are defined by Object File Type in the installDir/host/resource/target/architecturedb file. These strings are read by the target server and used to build the routine names. The following rules apply:

  • Only the alphanumeric portion of the string is kept.

  • No matter what letter case is used in the string, for OMF routines the first letter is converted to uppercase and all others are converted to lowercase. For example, "a.out" becomes "Aout" and "PECOFF" becomes "Pecoff". For RU routines, all characters are lowercase.

The Cpu part of the RU routine name is defined in the ExtentionName field in the installDir/host/resource/target/architecturedb file.

The OMF manager has three interface routines: loadOmfFmtManage( ), loadOmfFmtCheck( ), and loadOmfFmtInit( ). Omf is the only variant portion of the name and reflects the OMF as follows:

Table 3-1:   OMF Manager Function Names


OMF
Variant part
Function name

a.out
Aout
loadAoutFmtManage( )
ELF
Elf
loadElfFmtManage( )
PECOFF
Pecoff
loadPecoffFmtManage( )

The interface routines are similar to omfCpuSegReloc( ). Again, omf and Cpu are the only variant parts; They reflect the OMF and the CPU as shown in the following examples:

Table 3-2:   RU Function Names


OMF
Cpu name
Variant parts
Function name

a.out
MC680x0
Aout - 68k
aout68kSegReloc( )
ELF
ARM
Elf - Arm
elfArmSegReloc( )
PECOFF
I86 (simulator)
Pecoff - I86
pecoffI86SegReloc( )

File Names

The variant portions of the file names are derived by rules similar to those for the RU routine names.

  • Only the alphanumeric portion of the string is kept.

  • No matter what letter case is used in the string, all letters are converted to lowercase. For example, "a.out" becomes "aout" and "PECOFF" becomes "pecoff".

The file is called loadomf where omf is the only variant portion and reflects the OMF name:

Table 3-3:   OMF Manager File Names


OMF
Variant Part
File Name

a.out
Aout
loadaout
ELF
Elf
loadelf
PECOFF
Pecoff
loadpecoff

The file is called omfcpu where omf and cpu are the only variant portions and reflect the OMF and the CPU as shown in the following examples:

Table 3-4:   RU File Names


OMF
Cpu name
Variant Parts
File Name

a.out
MC68040
aout - 68k
aout68k
ELF
ARM
elf - Arm
elf - sh
elf - coldfire
elfArm
elfsh
elfcoldfire
PECOFF
Pentium
pecoff - i86
pecoffi86

3.9.4   The RU Interface

Relocation is highly dependent on both the OMF and the target architecture. This is why the relocation unit is a separate shared library. Only one relocator can be used at a time with a given target, so only one relocation unit is linked into the target server at run-time. The relocation unit is loaded during the OMF initialization phase (see loadOmfFmtInit( ) on p. 113).

Each relocation unit must contain the following two interface routines or entry points:

  • omfcpuSegReloc( )
  • omfcpuModuleVerify( )

It may also contain an optional initialization routine:

  • omfcpuRelocInit( )
omfcpuRelocInit( )

This optional routine, if it exists in the relocation unit, is the first entry point. Its purpose is to enable the relocation unit to initialize itself.

omfcpuModuleVerify( )

This routine is the first entry point if there is no optional routine; it checks whether or not the given module is for the current target CPU.

BOOL omfcpuModuleVerify (uint32 machtype, BOOL * pSwapIsRequired);

The input parameter is:

uint32 machtype

The output parameter is:

BOOL * pSwapIsRequired

The return value is:

STATUS

This routine is the underlying routine called by loadOmfModuleIsOk( ) (see Example 3-4).

Example 3-1:   omfcpuModuleVerify ( )

/************************************************************************ 
* 
* omfcpuModuleVerify - check the object module format for cpu target 
* 
* This routine contains the heuristic required to determine if the object 
* file belongs to the OMF handled by this OMF reader, with care for the 
* target architecture. 
* 
* It is the underlying routine for loadomfModuleIsOk(). 
* 
* RETURNS: TRUE or FALSE if the object module can't be handled. 
*/ 
 
BOOL omfcpuModuleVerify 
    ( 
    UINT16  magicNumber,    /* Module's magic number */ 
    BOOL *  SwapIsRequired  /* TRUE if header fields must be swapped */ 
    ) 
    { 
    BOOL    moduleIsForTarget = FALSE; /* TRUE if intended for target*/ 
 
    *SwapIsRequired = FALSE; 
 
    switch(magicNumber) 
        { 
        case (SWAB_16 (MYCPUMAGIC)): 
            *SwapIsRequired = TRUE; 
            break; 
 
        case (MYCPUMAGIC): 
            moduleIsForTarget = TRUE; 
            break; 
 
        default : 
            errno = WTX_ERR_LOADER_UNKNOWN_OBJ_MODULE_FORMAT; 
    } 
    return moduleIsForTarget; 
    }
omfcpuSegReloc( )

This routine performs the relocation of a given segment. The relocation process is explained in 3.9.14 Relocating the Object Modules, and an example of a segment relocator is also explained in Example 3-14.

3.9.5   The OMF Interface

Each OMF manager must contain the following three interface routines or entry points:

  • loadOmfFmtInit( )
  • loadOmfFmtCheck( )
  • loadOmfFmtManage( )
loadOmfFmtInit( )

This routine is the first entry point; it loads the correct relocation unit for the current target CPU.

STATUS loadOmfFmtInit (void)

The return value is:

STATUS

Example 3-2:   loadOmfFmtInit( )

/************************************************************************ 
* 
* loadOmfFmtInit - initialize the OMF loader 
* 
* This routine initializes the correct relocator unit for the current target 
* CPU. 
* 
* RETURNS: OK or ERROR. 
*/ 
 
STATUS loadOmfFmtInit (void) 
    { 
     /* OMF relocs routines ( + 1 for the optionnal SegInit routine) */ 
 
    DYNLK_FUNC  relocDllFv[NB_MANDATORY_RELOC_RTNS + 1]; 
 
    relocDllFv[0].name = (char *)tgtSegRelocatorRtnNameGet(); 
    relocDllFv[1].name = (char *)tgtModuleVerifyRtnNameGet(); 
    relocDllFv[2].name = (char *)tgtRelocInitRtnNameGet(); 
 
    /* Link the relocator */ 
 
    if (loadRelocLink( relocDllFv, NB_MANDATORY_RELOC_RTNS+1) != OK) 
        { 
        return ERROR; 
        } 
 
        /* relocation routine pointer */ 
    omfRelSegRtn = (FUNCPTR) relocDllFv[0].func;  
 
        /* module verification routine */ 
    omfModuleVerify = (FUNCPTR) relocDllFv[1].func;  
 
    /* if an Init routine exists for this relocator, run it */ 
 
    if (relocDllFv[2].func != NULL) 
    (*relocDllFv[2].func)(); 
 
    return (OK); 
    }
loadOmfFmtCheck( )

This routine is the second entry point; it checks the file format.

STATUS loadOmfFmtCheck (int moduleFd, BOOL * pFormatIsKnown);

The input parameter is:

int moduleFd

The output parameter is:

BOOL * pFormatIsKnown

The return value is:

STATUS

The target server calls loadOmfFmtCheck( ) during initialization to determine if a file is in the appropriate format for the loader. Files submitted to this routine should be core files only. (See 3.9.2 Installing a Shared-Library Manager.) Note that in this example, the first four bytes in the object module are arbitrarily considered a magic number.

Example 3-3:   loadOmfFmtCheck( )

/************************************************************************ 
* 
* loadOmfFmtCheck - see if the object file is in a known format 
* 
* This routine contains the heuristic required to determine if the object 
* file belongs to the OMF handled by this OMF reader, and is intended for 
* the appropriate target architecture. 
* 
* RETURNS: OK or ERROR if file cannot be read. 
*/ 
 
STATUS loadOmfFmtCheck 
    ( 
    int         moduleFd,       /* module object descriptor */ 
    BOOL *      pFormatIsKnown  /* hold the answer */ 
    ) 
    { 
    UINT32      magicNumber;    /* OMF magic number */ 
    BOOL        swap;           /* not used here */ 
 
    /* Get the magic number */ 
 
    if (read (moduleFd, (char *)&magicNumber, 4) != 4) 
        return (ERROR); 
 
    /* Check the module format */ 
 
    if (loadOmfModuleIsOk (magicNumber, &swap))            *pFormatIsKnown = TRUE; 
    else            *pFormatIsKnown = FALSE; 
 
    return (OK); 
    }
loadOmfFmtManage( )

This routine is the third and main entry point; it carries out the load process.

STATUS loadOmfFmtManage (char * pObjMod, int loadFlag, void ** ppText, 
                        void ** ppData, void ** ppBss, SYMTAB_ID symTbl, 
                        MODULE_ID moduleId, SEG_INFO *pSeg);

The input parameters are:

char * pObjMode

int loadFlag

SYMTAB_ID symTbl

MODULE_ID moduleId

The input-output parameters are:

void ** ppText

void ** ppData

void ** ppBss

SEG_INFO * pSeg

The return value is:

STATUS


*      
NOTE: When no address is required (NULL pointers are given), the value LOAD_NO_ADDRESS is assigned to the fields pAddrText, pAddrData, and pAddrBss of the SEG_INFO structure.

The steps taken by loadOmfFmtManage( ) are summarized below and in Example 3-4. The steps flagged with (*) are described in more detail in later code examples.

  1. Check the loader options for consistency.
  1. Check whether the host and target byte orders are identical. (*)
  1. Read in the module headers. (*)
  1. Determine the final size of the three segments: text, data, bss. (*)
  1. Read in the module symbol table and string table. (*)
  1. Determine the compiler signature and publish the builder. (*)
  1. Allocate memory for the segments, if required.
  1. Read in the various module sections. (*)
  1. Check that the core-file checksum and the target-text-section checksum match.
  1. Process the module symbol table. (*)
  1. Relocate the text and data segments, if required. (*)
  1. Download text and data segments, if required. (*)
  1. Apply virtual memory protection and cache update, if required.
   


*      
NOTE: If the loader calls loadOutputToFile( ), it does not download the module to the target memory. It returns with a loadOutputToFile( ) status code instead. This facility allows automated testing without live hardware.

   


*      
NOTE: Even for a fully linked file, the SEG_INFO structure must be filled in because it is used for module management, core file verification, and cache and virtual memory management. If a fully linked file holds several text, data, and bss sections, for instance, one of each type should be chosen as representative and the information loaded into the SEG_INFO structure.

Example 3-4:   loadOmfFmtManage( )

/************************************************************************ 
* 
* loadOmfFmtManage - process an object module 
* 
* This routine is the routine underlying loadModuleAt().  This 
* interface allows specification of the symbol table used to resolve 
* undefined external references and to which to add new symbols. 
* 
* Three kinds of files can be handled :  - relocatable files 
*                                        - fully linked files 
*                                        - "core" files 
* 
* For relocatable files, addresses of segments may or may not be 
* specified. Memory is allocated on the target if required (nothing 
* specified). For fully linked files, addresses of segments may or may 
* not be specified. Addresses are obtained from the file if nothing is 
* specified. Note however that if addresses are specified, all three  
* types must be set. For core files, addresses of segments must not be 
* specified. In any case, addresses are obtained from the file. 
*  
* RETURNS: OK, or ERROR if cannot process the file, not enough memory, or 
*          illegal file format 
*/ 
 
STATUS loadOmfFmtManage 
    ( 
    char *   pObjMod,      /* pointer to the beginning of the module */ 
    int      loadFlag,     /* control of loader behavior */ 
    void **  ppText,       /* load text segment at addr pointed to by */ 
                            /* this ptr, return load addr via this ptr */ 
    void **  ppData,       /* load data segment at addr pointed to by */ 
                            /* this ptr, return load addr via this ptr */ 
    void **  ppBss,        /* load bss segment at addr pointed to by */ 
                            /* this ptr, return load addr via this ptr */ 
    SYMTAB_ID  symTbl,     /* symbol table to use */ 
    MODULE_ID  moduleId,   /* module id */ 
    SEG_INFO * pSeg        /* info about loaded segments */ 
    ) 
    { 
    [local variable declarations] 
 
    /* Check the load flag combination */ 
 
    if (LoadFlagCheck (loadFlag, ppText, ppData, ppBss) != OK) 
        { 
        wpwrLogErr ("Illegal combination of flags\n"); 
        errno = WTX_ERR_LOADER_ILLEGAL_FLAGS_COMBINATION; 
        return (ERROR); 
        } 
 
    /* 
     * First, we check if the module is really Omf, and if it is 
     * intended for the appropriate target architecture. 
     * We also find out if the host and target byte orders are 
     * identical. If not, we have to swap the header contents, the 
     * symbol info, and the relocation commands. 
     */ 
 
    if (! loadOmfModuleIsOk (...) 
        { 
        wpwrLogErr ("Not an omf module for the %s architecture.\n", 
                    tgtCpuFamilyNameGet ()); 
        return (ERROR); 
        } 
 
    /* Read object module header */ 
 
    if (loadOmfMdlHdrRd (...) != OK) 
        return (ERROR); 
 
    /* Allocate memory for the various tables */ 
 
        [in general array of symbols and array of symbol address] 
 
    /* Read in section headers */ 
 
    if (loadOmfScnHdrRd (...) != OK)) 
        goto error; 
 
    /* Replace a null address by a dedicated flag (LOAD_NO_ADDRESS) */ 
 
    pSeg->pAddrText = (ppText == NULL) ? LOAD_NO_ADDRESS : *ppText; 
    pSeg->pAddrData = (ppData == NULL) ? LOAD_NO_ADDRESS : *ppData; 
    pSeg->pAddrBss  = (ppBss  == NULL) ? LOAD_NO_ADDRESS : *ppBss; 
 
    /* Determine segment sizes */ 
 
    loadOmfSegSizeGet (...); 
 
    /* Read the module symbol table */ 
 
    if (loadOmfSymTabRd (...) != OK) 
        goto error; 
 
    /* Search information about compiler */ 
 
    if (loadFlag & LOAD_CORE_FILE) 
        loadOmfToolSignatureSearch (...); 
 
    /* 
     * Allocate the segments according to user requirements. 
     * This allocates memory for the image on the host. 
     */ 
 
    if (!(loadFlag & LOAD_FULLY_LINKED) && 
                    (LoadSegmentAllocate (pSeg) != OK)) 
        goto error; 
 
    /* We are now about to store the segment contents in the cache */ 
 
    if (loadOmfSegStore (...) != OK) 
        goto error; 
 
    /* 
     * Build or update the target server symbol table with symbols found 
     * in the module symbol tables. 
     */ 
 
    if (loadOmfSymTabProcess (...) != OK) 
        goto error; 
 
    /* Relocate text and data segments (if not already linked) */ 
 
    if ((!(loadFlag & LOAD_FULLY_LINKED)) && 
                    (loadOmfSegReloc (...) != OK)) 
        goto error; 
 
    /* clean up dynamically allocated temporary buffers */ 
 
    [free memory previously allocated] 
 
    /* return load addresses, where called for */ 
 
    if (ppText != NULL) 
        *ppText = pSeg->pAddrText; 
 
    if (ppData != NULL) 
        *ppData = pSeg->pAddrData; 
 
    if (ppBss != NULL) 
        *ppBss = pSeg->pAddrBss; 
 
    /* Write segments in a file if required (testing session) */ 
 
    if (loadFlag & LOAD_FILE_OUTPUT) 
        return (LoadOutPutToFile (moduleId->name, pSeg)); 
 
    /* 
     * If file is relocatable, everything (text and data) is now flushed 
     * to the target. 
     * Note that in the case of a core file, we don't want to transfer 
     * anything to the target memory (since the core file text and data 
     * are already in the target). 
     */ 
 
    if ((!(loadFlag & LOAD_FULLY_LINKED)) && 
            (loadOmfCacheFlush (pSeg, textIsCached, dataIsCached) != OK)) 
        goto error; 
 
    /* If virtual memory management is on, apply write protection */ 
 
    if ((!(loadFlag & LOAD_FULLY_LINKED) && 
            (pSeg->flagsText & SEG_WRITE_PROTECTION)) && 
            (TgtMemProtect (pSeg->pAddrText, pSeg->sizeProtectedText, 
            TRUE) != OK)) 
        goto error; 
        
 
    return (OK); 
 
    /* 
     * error: 
     * free target memory cache nodes, clean up dynamically allocated 
     * temporary buffers and return ERROR 
     */ 
error: 
 
    wpwrLogErr ("Unrecoverable trouble while loading module.\n"); 
 
        if (textIsCached) 
            TgtMemCacheSet (pSeg->pAddrText, pSeg->sizeText,  
            MEM_NONE, FALSE); 
 
        if (dataIsCached) 
            TgtMemCacheSet (pSeg->pAddrData, pSeg->sizeData,  
            MEM_NONE, FALSE); 
 
    [free memory previously allocated] 
 
    return (ERROR); 
    }

3.9.6   Byte Order

The OMF manager manages the byte ordering in the object module. In a cross-development environment, the h

The OMF manager determines the byte ordering in the object module by reading a well known datum, also known as the magic number. If the OMF manager does not recognize this datum, it swaps the datum and tries again. If the OMF manager recognizes it after swapping, it knows that the object module is in the opposite byte order and requires that all information be swapped. However, if the datum is still not recognized after a swap, then the object module is probably not in the expected format.

Some cross-compilers produce structural information (headers, symbol table, relocation entries) in host byte order, but sections in target byte order. In this case, the OMF manager cannot find a well known datum within the sections. The programmer must address this case in the relocation routines.

Several macros in host.h deal with byte ordering:

SWAB_16

SWAB_32

SWAB_16_IF

SWAB_32_IF

Note that the length of the string table, when written down in the module, may need to be swapped as well.

Example 3-5:   loadOmfModuleIsOk( )

/************************************************************************ 
* 
* loadOmfModuleIsOk - check the module format and target architecture 
* 
* This routine contains the heuristic required to determine if the object 
* file belongs to the OMF handled by this OMF reader, and is intended for 
* the proper target architecture. 
* It is the underlying routine for loadOmfFmtCheck(). 
* It also checks if the module header is of the same byte order as the host. 
* If not, a swap is required whenever information is read in to be 
* processed on the host. 
* 
* RETURNS: TRUE or FALSE 
*/ 
 
LOCAL BOOL loadOmfModuleIsOk 
    ( 
    UINT16    magicNumber,    /* OMF magic number */ 
    BOOL *    pSwapIsRequired /* TRUE if header fields have to be swapped */ 
    ) 
    { 
    BOOL      moduleIsForTarget = FALSE;   /* TRUE if intended for target*/ 
    BOOL      byteOrderDiffer = FALSE;     /* TRUE if byte same order */ 
 
    /* check if the module is in the correct OMF */ 
    /* Your code here */ 
 
    /* check if the module if for the current CPU */ 
 
    return (omfModuleVerify ((machType, pSwapIsRequired)); 
    }

3.9.7   OMF Header Processing

Object-module headers hold vital information about the whole module or pieces of the module. The numbers and characteristics of the headers depend on the OMF. For instance, a.out has one header for the whole module while COFF has a general header, an optional header, plus one header per section. The header information, which includes the section sizes, the number of symbols, and the number of relocation entries, is used for all module processing.

It is good programming practice to store the OMF description (the C objects that represent the OMF entities such as headers, symbol entries, relocation entries, and specific values) in a separate header file (filename.h). Headers then fit naturally into C structures. See a_out.h for example.

Reading in the object-module headers involves reading values from the object-module image in memory into structure fields. Complicating issues include byte ordering, type abstraction, and data element size in both the header and the structure. For all these reasons, the object-module headers have to be read field by field, as shown in Example 3-6.

In the example, a pointer of type void * is used to access all fields of the header in memory. Because no size is associated with the type void, the pointer must be advanced with a cast operation. The type size used for this cast is related to the size of the header field that must be read.

Example 3-6:   loadOmfMdlHdrRd( )

/************************************************************************ 
* loadOmfMdlHdrRd - read in the module header. 
* 
* This routine fills a header structure with information from the object 
* module in memory. It swaps the bytes if this is required. 
* 
* RETURNS: OK always. 
*/ 
 
LOCAL STATUS loadOmfMdlHdrRd 
    ( 
    char *      pObjMod,        /* pointer to beginning of object file */ 
    OMF_HDR *   pHdr,           /* pointer to header structure to fill */ 
    BOOL        swapIsRequired  /* byte order must be swapped */ 
    ) 
    { 
    void * pHeaderField = (void *) pObjMod;     /* ptr to each field */ 
 
    /* 
     * Fields are read one by one since we must avoid compiler padding. 
     * Type abstractions such INT32, UINT32 are used since we don't want 
     * to be dependent on the host sizes for short, long, and so on. 
     */ 
 
    pHdr->firstField = *((INT32 *)pHeaderField); 
    pHeaderField = (INT32 *)pHeaderField + 1; 
 
    pHdr->secondField = *((UINT32 *)pHeaderField); 
    pHeaderField = (UINT32 *)pHeaderField + 1; 
 
    pHdr->thirdField = *((UINT16 *)pHeaderField); 
    pHeaderField = (UINT16 *)pHeaderField + 1; 
 
    pHdr->fouthField = *((UINT16 *)pHeaderField); 
 
    /* Take care of byte order between host and target */ 
 
    SWAB_32_IF (swapIsRequired, pHdr->firstField); 
    SWAB_32_IF (swapIsRequired, pHdr->secondField); 
    SWAB_16_IF (swapIsRequired, pHdr->thirdField); 
    SWAB_16_IF (swapIsRequired, pHdr->fouthField); 
 
    return (OK); 
    }

3.9.8   Determining the Size of the Segments

The OMF reader must determine the size of the three segments, which may consist of several sections of the same or equivalent type. In the simplest situation, when there is only one section per segment, the segment sizes can be determined immediately. Otherwise, the sizes of the gathered sections are added to get the segment sizes. Example 3-7 calls loadOmfSegSizeGet( ) to determine segment size.

Example 3-7:   loadOmfSegSizeGet( )

/************************************************************************ 
* loadOmfSegSizeGet - determine segment sizes 
* 
* This function fills in the size fields in the SEG_INFO structure. 
* 
* RETURNS: nothing 
*/ 
 
LOCAL void loadOmfSegSizeGet 
    ( 
    int         sectionNumber,  /* number of sections */ 
    SCNHDR *    pScnHdrArray,   /* pointer to array of section headers */ 
    SEG_INFO *  pSeg            /* section addresses and sizes */ 
    ) 
    { 
    int         sectionIndex;   /* loop counter */ 
    int         nbytes;         /* additional bytes required for alignment */ 
    int         dataAlign = -1; /* Alignment for the first data section */ 
    int         bssAlign = -1;  /* Alignment for the first bss section */ 
    [other local variables] 
 
    pSeg->sizeText = 0; 
    pSeg->sizeData = 0; 
    pSeg->sizeBss = 0; 
 
    /* loop thru all sections */ 
 
    for (sectionIndex = 0; sectionIndex < sectionNumber; sectionIndex++)            { 
        [Get section's type] 
 
        /* 
         * Following the three-segment model, all sections of the same type  
         * are loaded following each other within the area allocated for the 
         * segment. But sections must be loaded at addresses that fit with 
         * the alignment requested or, by default, with the target 
         * architecture's alignment requirement. So the segment size is the 
         * total size of the sections integrated in this segment plus the 
         * number of bytes that create the required offset to align the next 
         * sections : 
         * 
         *     +-------------+ <- segment base address (aligned thanks to the 
         *     |:::::::::::::|    memory manager). This is also the load 
         *     |::::Text:::::|    address of the first section. 
         *     |:Section:1:::| 
         *     |:::::::::::::| 
         *     |:::::::::::::| 
         *     +-------------+ <- end of first section. 
         *     |/////////////| <- offset needed to reach next aligned addr. 
         *     +-------------+ <- aligned load address of next section within 
         *     |:::::::::::::|    the segment. 
         *     |:::::::::::::| 
         *     |:::::::::::::| 
         *     |::::Text:::::| 
         *     |:Section:2:::| 
         *     |:::::::::::::| 
         *     |:::::::::::::| 
         *     |:::::::::::::| 
         *     |:::::::::::::| 
         *     +-------------+ 
         *     |             | 
         * 
         * The principle here is to determine, for a given section type 
         * (text, data, or bss), how many padding bytes should be added to 
         * the previous section in order to be sure that the current section 
         * is correctly aligned. This means that the first section of 
         * each type is assumed to be aligned. Note that this 
         * assumption is correct only if each segment is allocated 
         * separately (since tgtMemMalloc() returns an aligned address). If 
         * only one memory area is allocated for the three segments, 
         * as with loadSegmentsAllocate(), another round of alignment 
         * computation must be done between the three segments. 
         */ 
 
        if ([section is of type text] || [section is of type literal]) 
            { 
            /*  
             * The contents of literal sections are considered to be 
             * "text" by the loader (see loadOmfScnRd()). So, the size of 
             * the literal sections is added to the size of the text 
             * sections.