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. 
             */ 
 
            /* Record alignment for data sections */ 
 
            if ([alignment] > dataAlign) 
                dataAlign = [alignment]; 
 
            nbytes = LoadAlignGet ([alignment], (void *)pSeg->sizeText); 
            pSeg->sizeText += [section's size] + nbytes; 
            } 
 
        else if ([section is of type data]) 
            { 
            nbytes = LoadAlignGet ([alignment], (void *)pSeg->sizeData); 
            pSeg->sizeData += [section's size] + nbytes; 
            } 
        else if ([section is of type bss]) 
            { 
            /* Record alignment for bss sections */ 
 
            if ([alignment] > bssAlign) 
                bssAlign = [alignment]; 
 
            nbytes = LoadAlignGet ([alignment], (void *) pSeg->sizeBss); 
            pSeg->sizeBss += [section's size] + nbytes; 
            } 
        else 
            wpwrLogWarn ("Ignored section %d\n", sectionIndex); 
        } 
 
    /*  
     * If only one memory area is to be allocated for the three segments, 
     * take care of the alignment between the three segments. The text 
     * segment is always aligned thanks to tgtMemMalloc(). 
     */ 
 
    if (pSeg->pAddrText == LOAD_NO_ADDRESS && 
        pSeg->pAddrData == LOAD_NO_ADDRESS && 
        pSeg->pAddrBss  == LOAD_NO_ADDRESS) 
        { 
        if (pSeg->sizeData > 0) 
            pSeg->sizeText += 
                LoadAlignGet (dataAlign, (void *) pSeg->sizeText); 
 
        if (pSeg->sizeBss > 0) 
            pSeg->sizeData += 
                LoadAlignGet (bssAlign, 
                            (void *)(pSeg->sizeText + pSeg->sizeData)); 
        } 
    }

3.9.9   Reading the Object-Module Symbol Table

The object-module symbol table generally consists of one entry per symbol, with each entry holding several fields. Typically a symbol entry contains the symbol name (or a reference to the symbol name when this is stored in a string table), the symbol type, its value, and so on. This information is used when adding the symbol to the target server symbol table and when relocating the segments.

Reading in the file symbol table involves looping through the list and storing the fields for all entries in an array of structures. All the pitfalls discussed under reading headers apply here as well. Moreover, since we repeatedly read the same pattern of data, it is possible that fields are read across byte boundaries; this could lead to an unaligned access error. The macros UNPACK_16 and UNPACK_32 can be used to prevent this problem.

When the number of symbols is known or can be computed, it is easy to loop through the module symbol table. In Example 3-8, the position of the string table in the object module is deduced from the position of the symbol table. This is OMF dependent, not a general case.

Example 3-8:   loadOmfSymTabRd( )

/************************************************************************* 
* loadOmfSymTabRd - read and process an object module symbol table 
* 
* For each symbol entry, the fields required for relocation are saved in 
* the pSymsArray array. 
* 
* RETURN: a pointer to the string table 
*/ 
 
LOCAL void * loadOmfSymTabRd 
    ( 
    void *      pSymEntry,      /* ptr to symbol info in object module */ 
    SYMENT *    pSymsArray,     /* array of symbol entries */ 
    UINT        nbSymbols,      /* number of symbols in module */ 
    BOOL        swapIsRequired  /* if TRUE, byte order must be swapped */ 
    ) 
    { 
    int         symIndex;                       /* loop counter */ 
    SYMENT *    pSymbol;                        /* ptr to symbol entry */ 
    void *      pSymEntryField = pSymEntry;     /* ptr on each field */
    /* loop thru all symbols */ 
 
    for (symIndex = 0; symIndex < nbSymbols; symIndex++) 
        { 
        pSymbol = pSymsArray + symIndex; 
 
        /* read in next entry */ 
 
        /* 
         * Fields are read one by one since we are concerned about compiler 
         * padding. Type abstractions such INT32, UINT32 _must_ be used 
         * since we do not want to be dependent on the host sizes for 
         * short, long, and so on.  All 32-bit fields are accessed 
         * thru the UNPACK_32 macro since they may be unaligned. 
         */ 
 
        pSymbol->firstField = UNPACK_32 (pSymEntryField); 
        pSymEntryField = (INT32 *)pSymEntryField + 1; 
        pSymbol->secondField = UNPACK_32 (pSymEntryField); 
        pSymEntryField = (INT32 *)pSymEntryField + 1; 
 
        pSymbol->thirdField = UNPACK_32 (pSymEntryField); 
        pSymEntryField = (INT32 *)pSymEntryField + 1; 
 
        pSymbol->fourthField = *((INT16 *)pSymEntryField); 
        pSymEntryField = (INT16 *)pSymEntryField + 1; 
 
        /* Take care of byte order between target and host */ 
 
        SWAB_32_IF (swapIsRequired, pSymbol->firstField); 
        SWAB_32_IF (swapIsRequired, pSymbol->secondField); 
        SWAB_32_IF (swapIsRequired, pSymbol->thirdField); 
        SWAB_16_IF (swapIsRequired, pSymbol->fourthField); 
        } 
 
    /* 
     * Return the address of the first byte immediately following the 
     * symbol table. This address is the address of the string table. 
     */ 
 
    return (pSymEntryField); 
    }

3.9.10   Determining the Compiler Signature

Example 3-9 shows a routine that looks for a compiler signature in the string table. The signatures are held in an array of strings that must be null-terminated.

Example 3-9:   loadOmfToolSignatureSearch( )

/************************************************************************* 
* loadOmfToolSignatureSearch - get information about the compiler 
* 
* This routine gets a compiler signature from the module string table. 
* If a known signature is found, it sets the appropriate builder. 
* A builder is a string used in the making of the target server. 
* Two builders are defined for now: "gnu" and "diab". They apply  
* to targets built with the GNU tool chain and the DIAB DATA tool chain. 
* 
* Signatures are stored in the signaturesTable table. A NULL pointer  
* must be the last element of this table. Each signature is compared 
* with the string evaluated in the string table.  
 
* RETURNS: N/A 
*/ 
 
LOCAL void loadOmfToolSignatureSearch 
    ( 
    void *      pStart,                 /* address to start from */ 
    UINT32      length                  /* max length to check */ 
    ) 
    { 
    void *      pEnd;                   /* last address to check */ 
    char *      signaturesTable[] =      /* compiler signatures */ 
        { 
"gcc2_compiled.",                       /* GNU gcc */ 
        NULL                            /* End mark */ 
        }; 
    char **     ppSignature;            /* ptr to current signature */ 
    char *      pString;                /* string being evaluated */ 
 
    pEnd = (char *)pStart + length; 
    pString = (char *)pStart; 
 
    /* Loop until the end of the string table if necessary */ 
 
    while (pString <= (char *)pEnd) 
        { 
        ppSignature = signaturesTable; 
 
        /* Check string against compiler signatures */ 
 
        while (*ppSignature != NULL) 
            { 
            if (strcmp (pString, *ppSignature) == 0) 
                { 
                /* If found a signature record the "builder" */ 
 
                LoadCoreBuilderSet ("gnu"); 
                return; 
                } 
            ppSignature++; 
            } 
 
        /* Walk to the next string, if any */ 
 
        for (; (pString <= (char *)pEnd), (*pString != 0); pString ++); 
        pString ++; 
        } 
 
    /* No signature has been found */ 
 
    LoadCoreBuilderSet ("unknown"); 
    wpwrLogWarn ("Can't find compiler signature.\n"); 
 
    return; 
    }

3.9.11   Allocating Memory For the Segments if Required

When the loader is ready to allocate memory on the target for the three segments of a relocatable object module, it calls loadSegmentsAllocate( ). (For details, see the online reference material under Tornado API Reference>Target Server Internal Routines.) Fully linked modules, on the other hand, need not have memory allocated; they are fully independent of the run-time system-managed heap.

When the loader allocates memory on the target, it attempts to allocate a single block of memory for all three segments. If there is not enough contiguous memory on the target to allocate a single block, it attempts to allocate separate blocks for each of the three segments. If this is also impossible, the load fails.

3.9.12   Reading in the Segments

The groundwork has been laid and the loader is ready to read in the segments. Example 3-10 shows how loadOmfSegStore( ) processes the various types of files:

  • Relocatable file segments are written in the cache but not downloaded to the target memory until after the relocation process is complete.

  • Fully linked file text segments are written in the cache and immediately downloaded to target memory. Data and bss segments are not written in the cache but are immediately downloaded to the target memory.

  • Core file text segments are written in the cache but never downloaded to target memory. Other core file segments are ignored.

For a relocatable object module, the loader manipulates the segments on the host side in the target server memory cache before downloading the segments to the target. Performing all the memory manipulation on the host side minimizes the impact on the target system. Example 3-11 shows how loadOmfScnRd( ), which is called by loadOmfSegStore( ), writes the segment contents to the target server cache. This operation coalesces any sections of equivalent type. Once segments of a relocatable module are transferred into the target memory cache, the loader can perform the relocations.

Example 3-10:   loadOmfSegStore( )

/************************************************************************ 
* loadOmfSegStore - store the module segments in target memory 
* 
* This routine stores the module's segments in target memory. It takes care 
* of host cache management and of the module type (relocatable, fully 
* linked, or core file). 
* 
* RETURNS: OK or ERROR if the segment contents cannot be stored in target 
* memory. 
*/ 
 
LOCAL STATUS loadOmfSegStore 
    ( 
    SEG_INFO *     pSeg,                /* info about loaded segments */ 
    int            loadFlag,            /* control of loaders behavior */ 
    char *         pObjMod,             /* pointer to beginning of module */ 
    BOOL *         pTextIsCached,       /* text segment in host cache */ 
    BOOL *         pDataIsCached,       /* data segment in host cache */ 
    SCN_ADRS_TBL * pSectionAdrsTbl,     /* tbl of section addr when loaded */ 
    [other parameters] 
    ) 
    { 
    [local variable declarations] 
 
    /* 
     * We do not want to transfer the segment contents to the target 
     * immediately. We want to keep them in the cache until all processing 
     * is complete (for example, all relocations in relocatable modules). 
     * We do this by setting the MEM_HOST attribute for text and data  
     * segments. If the file is fully linked (as is a core file) with no  
     * address specified, we do the same for each section. 
     * 
     * Read in text, data, and literal sections. In the case of a relocatable 
     * module, section contents are coalesced so that we end up with a 
     * three-segment model in memory: text, data,and bss. If the file is  
     * fully linked, just read all the sections, if not empty, where they  
     * should be located. 
     */ 
 
    if (!(loadFlag & LOAD_FULLY_LINKED))        /* Relocatable file */ 
        { 
        *pTextIsCached = ((pSeg->pAddrText != LOAD_NO_ADDRESS) && 
                         (TgtMemCacheSet (pSeg->pAddrText, 
                                            pSeg->sizeText, 
                                            MEM_HOST, FALSE) == OK)); 
 
        *pDataIsCached = ((pSeg->pAddrData != LOAD_NO_ADDRESS) && 
                          (TgtMemCacheSet (pSeg->pAddrData, 
                                            pSeg->sizeData, 
                                            MEM_HOST, FALSE) == OK)); 
 
        if (loadOmfScnRd (...) != OK) 
            return (ERROR); 
        } 
 
    else if (loadFlag & LOAD_CORE_FILE)         /* core file */ 
        { 
        if (LoadCoreFileCheck (pSeg->pAddrText, 
                                (pObjMod + [offset to text]), 
                                pSeg->sizeText) != OK) 
            wpwrLogWarn ("Core file checksums do not match.\n"); 
 
        /* 
         * Core files may have several text sections. Loop thru these 
         * sections. Note that we ignore data or bss sections (no need to 
         * have them in cache). 
         */ 
 
        for (scnNum = 0; scnNum < [number of sections]; scnNum++) 
            { 
            /*  
             * Only loadable sections are of interest to us, and we do not 
             * want to consider sections that have a null size in the file 
             * (bss sections), or sections that occupy no room in the target 
             * memory. 
             */ 
 
            if ([section is executable text]     && 
                ((TgtMemCacheSet ([section virtual address], 
                                    [section size], 
                                    MEM_HOST, 
                                    FALSE) == OK)      && 
                ((TgtMemWrite ([address of segment contents], 
                                    (REMPTR) [section virtual address], 
                                    [section size]) != OK) || 
                (TgtMemCacheSet ([section virtual address], 
                                    [section size], 
                                    MEM_TEXT, 
                                    FALSE) != OK)))) 
                return (ERROR); 
            } 
        }
    else  
 
        /* 
         * A fully linked file other than a core file may have several  
         * text, data, or bss sections. 
         * Loop thru these sections. Note that only text sections are 
         * cached; other sections are immediately downloaded. 
         */ 
 
        for (scnNum = 0; scnNum < [number of sections]; scnNum++) 
            { 
            if ([section is to be loaded in target memory]) 
                { 
                if ([section is executable text]) 
                    { 
                    *pTextIsCached = 
                        (TgtMemCacheSet ([section virtual addr], 
                                            [section size], 
                                            MEM_HOST, 
                                            FALSE) == OK); 
 
                     if (TgtMemWrite ([address of segment contents], 
                                            (REMPTR) [section virtual address], 
                                            [section size]) != OK) 
                        return (ERROR); 
 
                    if (*pTextIsCached && 
                        (TgtMemCacheSet ([section virtual address], 
                                            [section size], MEM_TEXT, 
                                            TRUE) != OK)) 
                        return (ERROR); 
                    } 
                else 
                    if (TgtMemWrite ([address of section contents], 
                                        (REMPTR) [section virtual address], 
                                        [section size]) != OK) 
                        return (ERROR); 
                } 
            } 
    return (OK); 
    }

Example 3-11:   loadOmfScnRd( )

/************************************************************************ 
* 
* loadOmfScnRd - read sections into the target memory 
* 
* This routine actually copies the sections contents into the image of 
* the target memory on the host. All sections of the same type are 
* coalesced, resulting in a three segment model. 
* 
* RETURNS: OK or ERROR if a section cannot be read in. 
*/ 
 
LOCAL STATUS loadOmfScnRd 
    ( 
    char *       pObjMod,       /* pointer to beginning of object file */ 
    int          sectionNumber, /* number of sections */ 
    SCNHDR *     pScnHdrArray,  /* pointer to array of section headers */ 
    SCN_ADRS_TBL sectionAddrTbl,/* table of section addresses */ 
    SEG_INFO *   pSeg           /* segments information */ 
    ) 
    { 
    int         sectionIndex;   /* loop counter */ 
    SCNHDR *    pScnHdr;        /* pointer to a section header */ 
    SCN_ADRS *  pScnAddr;       /* pointer to address of section */ 
    SCN_ADRS    pTgtLoadAddr;   /* target address to load data at */ 
    INT32       scnSize;        /* section size */ 
    void *      pTextCurAddr;   /* current addr where text is loaded */ 
    void *      pDataCurAddr;   /* current addr where data are loaded */ 
    void *      pBssCurAddr;    /* current addr where bss is "loaded" */ 
    void *      offset;         /* offset of section raw contents */ 
    int         nbytes;         /* addnl bytes required for alignment */ 
 
    pTextCurAddr = pSeg->pAddrText; 
    pDataCurAddr = pSeg->pAddrData; 
    pBssCurAddr = pSeg->pAddrBss; 
 
    /* Loop thru all the sections */ 
 
    for (sectionIndex = 0; sectionIndex < sectionNumber; sectionIndex++) 
        { 
        pScnHdr = pScnHdrArray + sectionIndex; 
        pScnAddr = sectionAddrTbl + sectionIndex; 
        pTgtLoadAddr = NULL; 
        scnSize = [size of section]; 
 
        /* 
         * About the section alignment, see explanations and diagram in 
         * loadOmfSegSizeGet(). 
         */ 
 
        if (scnSize != 0) 
            { 
            if ([section of type text] || [section of type literal]) 
                { 
                /* 
                * Text sections and literal sections are merged in one text 
                * segment. Note that we could have Text-Literal-Text... 
                */ 
 
                pTgtLoadAddr = pTextCurAddr; 
                nbytes = LoadAlignGet ([alignment], pTgtLoadAddr); 
                pTgtLoadAddr = (UINT8 *)pTgtLoadAddr + nbytes; 
                pTextCurAddr = (UINT8 *)pTgtLoadAddr + scnSize; 
                } 
            else if ([section of type data]) 
                { 
                /* Data sections */ 
 
                pTgtLoadAddr = pDataCurAddr; 
                nbytes = LoadAlignGet ([alignment], pTgtLoadAddr); 
                pTgtLoadAddr = (UINT8 *)pTgtLoadAddr + nbytes; 
                pDataCurAddr = (UINT8 *)pTgtLoadAddr + scnSize; 
                } 
 
            else if ([section of type bss]) 
                { 
                /*  
                * Bss sections. Such sections must not be downloaded 
                * since they do not actually exist in the object module. 
                * However, for relocation purposes, we need to know 
                * where they are located in target memory. 
                */ 
 
                pTgtLoadAddr = pBssCurAddr; 
                nbytes = LoadAlignGet ([alignment], pTgtLoadAddr); 
                pTgtLoadAddr = (UINT8 *)pTgtLoadAddr + nbytes; 
                pBssCurAddr = (UINT8 *)pTgtLoadAddr + scnSize; 
                } 
            else 
                /* ignore any other type of sections */ 
 
                continue; 
 
            /* 
            * Advance to position in file and copy the section into the 
            * target memory image on the host (only if the section 
            * exists in module). 
            */ 
 
            if ([module holds section contents]) 
                { 
                offset = (void *) (pObjMod + pScnHdr->pScnAddr); 
 
                if (TgtMemWrite (offset, (REMPTR) pTgtLoadAddr, 
                                            scnSize) != OK) 
                    return (ERROR); 
                } 
            } 
 
            /* record the load address of each section */ 
 
            *pScnAddr = pTgtLoadAddr; 
        } 
    return (OK); 
    }

3.9.13   Target Server Symbol-Table Processing

The target server maintains a symbol table based on the loader symbol management options (see 3.5.3 Symbol Management). Typically every defined symbol is added to the target server symbol table.

A symbol meets the criteria for adding to the symbol table if:

  • The symbol will not be discarded (for example, is not a debug symbol).

  • The symbol is neither undefined nor common.

  • The options allow adding the symbol to the symbol table.

If the symbol satisfies these constraints, take the following steps to add it to the symbol table:

  • Get the symbol name (from the symbol entry or from the string table).

  • Set the symbol type.

  • Compute the address to which the symbol refers.

  • Add the symbol to the target server symbol table.

The address computation is OMF dependent and may be complex. It is based on the following equation:

reference address = section's base address + symbol value

The base address of each section can be computed as follows:

base address of segment + [size of previous section in segment +
    alignment computed on size of previous section] * n

where n is the number of previous sections before the section being computed.

The loader returns the base address of the segments. The size of the previous sections must be obtained from the object file section headers. The alignment (the number of bytes in the "hole" between sections) is computed as follows:

requested alignment - (size of section % requested alignment)


*      
NOTE: Some OMF readers require the reference addresses in order to perform the relocations. These addresses may be stored in the same way as undefined symbols.

Symbol-table management is performed with the symbol utility library. For more information, see the online reference material under Tornado API Reference>Target Server Internal Routines.

When handling an undefined symbol, the OMF manager follows these steps:

  1. It tries to find a matching reference in the target symbol table.
  1. If no reference is found, it records the symbol as unknown.
  1. It records the address referred by the undefined symbol or NULL if there is no address).

It is critical to store the addresses of the undefined symbols because they are required in order to relocate the references to these symbols in the module segments. Usually an array of SYM_ADRS (see loadlib.h) is used for this purpose.

The matching reference is searched by symFindByNameAndType( ), which is summarized in the online reference material under Tornado API Reference>Target Server Internal Routines. If the symbol is not found, loadUndefSymAdd( ) records the symbol in the unknown symbol list, which is returned to the caller when the load is complete. Note that an unknown symbol does not generate an error (see 3.5.3 Symbol Management).

When handling a common symbol, the OMF uses the symbol management policy specified in the loader options. This is done by calling loadCommonManage( ). The final address of the common symbol is stored with the other addresses referred to by the undefined symbols so that relocation can be performed.

The string table needs no further processing because it is loaded into memory in the correct format (strings followed by a NULL character). Only a pointer to the beginning of this table is required. The string table stores the symbol names in the target server symbol table. A secondary role may be to hold a symbol name specific to the tool chain used to compile the file. This name is used as a signature to return the compiler type to the target server when processing the core file. Depending on the OMF, there may be no string table, or the signature may be found in the symbol table or in some other location. You must write a heuristic that suits the particular OMF.

Example 3-12 is a sample symbol-table processing routine. The parameters vary depending upon the OMF-manager implementation, but the following parameters are typical:

  • the module identifier (MODULE_ID)

  • the loader option (loadFlag)

  • the target-symbol-table identifier (SYMTAB_ID)

  • the symbol entries (usually an array of structures holding the entries)

  • the number of symbol entries

  • the section addresses (stored in SEG_INFO or in a specific array if required)

  • the location where undefined symbol addresses are stored

  • the module string table in case all symbol names are not in the symbol entries

Example 3-12:   loadOmfSymTabProcess( )

/************************************************************************ 
* loadOmfSymTabProcess - process an object module symbol table 
* 
* A pointer is passed to a coff symbol table and processes each of the 
* external symbols defined therein.  This processing performs two functions: 
* 
* 1) Defined symbols are entered in the target system symbol table as 
*    specified by the "loadFlag" argument: 
*      LOAD_ALL_SYMBOLS    = all defined symbols (LOCAL and GLOBAL) added, 
*      LOAD_GLOBAL_SYMBOLS = only external (GLOBAL) symbols added, 
*      LOAD_NO_SYMBOLS     = no symbols added; 
* 
* 2) Any symbols of type "undefined external" are looked up in the target 
*    server symbol table to determine their values. If found, they are 
*    entered in an array. This array is indexed by the symbol number  
*    (position in the symbol table). Note that all symbol values, not just 
*    undefined externals, are entered in this array.  The values are used 
*    to perform relocations. 
* 
*    Note that common symbols have type undefined external; the value 
*    field of the symbol is non-zero for common symbols, indicating 
*    the size of the object. 
* 
* If an undefined external cannot be found in the target server symbol table, 
* a warning message is printed, the noUndefSym field of the module is set 
* to FALSE, and the name of the symbol is added to the list in the module. 
* Note that this is not considered to be an error since the loader attempts 
* to resolve undefined externals as additional modules are loaded. 
* 
* RETURNS: OK or ERROR. 
*/ 
 
LOCAL STATUS loadOmfSymTabProcess 
    ( 
    MODULE_ID    moduleId,      /* module id */ 
    int          loadFlag,      /* control of loader behavior */ 
    SYMENT *     pSymsArray,    /* pointer to symbol array */ 
    SCN_ADRS_TBL sectionAddrTbl,/* array of section addresses */ 
    SYM_ADRS_TBL symAdrsTbl,    /* array of in symbol absolute values */ 
    char *       pSymStrings,   /* symbol string table */ 
    SYMTAB_ID    symTbl,        /* symbol table to use */ 
    int          symNumber,     /* num of symbols in module symbol table */ 
    SCNHDR *     pScnHdrArray   /* pointer to Omf section header array */ 
    ) 
    { 
    [local variables declarations] 
 
    /* Loop thru all symbol table entries in object file */ 
 
    for (symIndex = 0; symIndex < symNumber; symIndex++) 
        { 
        pSymbol = pSymsArray + symIndex; 
 
        /* Get rid of debug stuff */ 
 
        if ([symbol is a debug symbol]) 
            continue; 
 
        /* Get symbol's name from string table */ 
 
        name = pSymStrings + [index to reach the symbol's name]; 
 
        if ([symbol is not undefined] && [symbol is not common]) 
            { 
            /* 
             * Symbol is neither an undefined external nor a common symbol. 
             * Determine symbol section and address bias 
             * 
             * If the object file is already absolutely located (by the  
             * linkeron the host), then the symbol values are already 
             * correct. There is no need to bias them. Bias is also not  
             * needed when the symbol is absolute. 
             */ 
 
            if ((loadFlag & LOAD_FULLY_LINKED) || [symbol is absolute]) 
                    bias = 0; 
                else 
                    bias = (void *)([section's base address + symbol's value]); 
 
            /* Determine the symbol type. */ 
 
            /* For an absolute symbol, don't consider the section type */ 
 
            if ([symbol is absolute]) 
                    symType = SYM_ABS; 
 
            /* Is it a symbol from a text or literal section ? */ 
 
            else if ([symbol is of type text or literal]) 
                    symType = SYM_TEXT; 
 
            /* Is it a symbol from a data section ? */ 
 
            else if ([symbol is of type data]) 
                    symType = SYM_DATA; 
 
            /* Is it a symbol from a bss section ? */ 
 
            else if ([symbol is of type bss]) 
                symType = SYM_BSS; 
 
            /* If none of these, we don't know how to handle this  
             * type of symbol */ 
 
            else 
                { 
                wpwrLogWarn ("Unknown sym type for symbol %s\n", name); 
                continue; 
                } 
 
            /* Determine if symbol should be put into symbol table. */ 
 
            if (((loadFlag & LOAD_LOCAL_SYMBOLS) && [symbol not global]) || 
                ((loadFlag & LOAD_GLOBAL_SYMBOLS) && [symbol is global])) 
                { 
                if ([symbol is global]) 
                    symType |= SYM_GLOBAL; 
                else 
                    symType |= SYM_LOCAL; 
 
                /* Add symbol to symbol table. */ 
 
                if (SymAdd (symTbl, name, (char *)([symbol value] + 
                            (INT32)bias), symType, moduleId->group) !=OK) 
                    { 
                    wpwrLogErr ("Can't add '%s' to sym table\n", name); 
                    status = ERROR; 
                    } 
                } 
 
            /* 
            * Add the symbol address to the externals table. 
            * For omf, we add all symbol addresses to the externals 
            * table, not only those symbols added to the target server 
            * symbol table. This is required by the relocation process. 
            */ 
 
            symAdrsTbl [symIndex] = (SYM_ADRS)([sym's value]+(INT32)bias); 
 
            } 
        else 
            { 
            /* 
            * A "common" symbol type is denoted by "ndefined external" 
            * with its value set to non-zero. 
            */ 
 
            if ([symbol is common]) 
                { 
                /* follow common symbol management policy */ 
 
                if (LoadCommonManage ([symbol value], name, symTbl,&adrs, 
                                        loadFlag, moduleId->group) != OK) 
                                        status = ERROR; 
                } 
            else 
                /* look up undefined external symbol in symbol table */ 
 
                if (SymFindByNameAndType (symTbl,name,(char **)&adrs, 
                                        &symType, SYM_GLOBAL, 
                                        SYM_GLOBAL) != OK) 
                { 
                /* symbol not found in symbol table */ 
 
                adrs = NULL; 
 
                /* Record the symbol name for further request */ 
 
                LoadUndefSymAdd (moduleId, name); 
                } 
 
            /* add symbol address to externals table */ 
 
            symAdrsTbl [symIndex] = adrs; 
            } 
        } 
    return (status); 
    }

3.9.14   Relocating the Object Modules

Relocation is highly dependent on both the OMF and the target architecture. It is therefore difficult to give precise information. The relocation unit resides in a separate shared library which is linked in during the OMF reader initialization (see 3.9.4 The RU Interface).

Relocations occur only for relocatable files, which have segments not immediately usable for execution. Relocatable files generally hold references to undefined symbols, whose addresses have not yet been determined. Even references to defined symbols may be relative to the beginning of the section, which is initially assumed to be address zero. The relocation is performed by omfCpuSegReloc( ) (see Relocation Process), which in turn calls omfCpuRelocEntryRd( ) to fill in the relocation structure (see 3.9.5 The OMF Interface).

Reading in Relocation Entries

Object modules hold information about how to relocate the sections. This information is generally presented as a table of relocation entries. A relocation entry (OMF dependent) is composed of:

  • the offset of the symbol reference within the section

  • a reference to the symbol entry in the symbol table (called an index)

  • the type of relocation process to be done

Note that the offset within a section requires that each section address be recorded when the sections are read in. We cannot use the segment base address here, but need the real address where each section is stored within the segment. A routine such loadOmfScnRd( ), shown in Example 3-11, should be modified to fill an array with these section addresses.

Reading the relocation entries requires the same care as reading the headers; a swap may be required if byte ordering differs, and unaligned accesses must be avoided while reading the entry fields. Example 3-13 is a routine for reading the relocation entries.

Example 3-13:   omfCpuRelocEntryRd( )

/************************************************************************ 
* omfCpuRelocEntryRd - read in an OMF relocation entry 
* 
* This routine fills a relocation structure with information from the  
* object module in memory. It swaps the bytes if this is required. 
* 
* RETURNS: the address of the next relocation entry. 
*/ 
 
LOCAL void * omfCpuRelocEntryRd 
    ( 
    void *      pRelocEntry,     /* ptr to relocation cmd in object file */ 
    RELOC *     pReloc,          /* ptr to relocation structure to fill */ 
    BOOL        swapIsRequired   /* if TRUE, byte order must be swapped */ 
    ) 
    { 
    void *      pRelocField = pRelocEntry;    /* points to each "field" */ 
 
    pReloc->offset = UNPACK_32 (pRelocField); 
    pRelocField = (long *)pRelocField + 1; 
 
    pReloc->index = UNPACK_32 (pRelocField); 
    pRelocField = (long *)pRelocField + 1; 
 
    pReloc->type = *((unsigned short *)pRelocField); 
    pRelocField = (unsigned short *)pRelocField + 1; 
 
    /* Take care of the byte order between host and target */ 
 
    SWAB_32_IF (swapIsRequired, pReloc->offset); 
    SWAB_32_IF (swapIsRequired, pReloc->index); 
    SWAB_16_IF (swapIsRequired, pReloc->type); 
 
    return (pRelocField); 
    }

Relocation Process

The relocation process itself has three parts:

  1. Computing the address of the code that needs to be modified within the section. It is generally computed as follows:
code address = section address + offset of reference within section
  1. Computing the address of the symbol referred to. It comes from the symbol table processing; addresses for all undefined symbols are recorded, usually in an array.
  1. Code replacement. This consists of modifying the code located in Step (1) by integrating the symbol address located in Step (2). This modification depends on the type of relocation process. For some OMF readers, the symbol address must be combined with a value found at the code address; for others, the relocation entry holds all such values.

After the relocation computation is complete, store the resulting value at the code address with tgtMemWrite( ). If you must write 8-bit or 16-bit values, call tgtMemWriteByte( ) and tgtMemWriteShort( ). Example 3-14 shows the relocations.

Example 3-14:   omfCpuSegReloc( )

/************************************************************************* 
* omfCpuSegReloc - perform relocation for the CPU family 
* 
* This routine reads the specified relocation command entry and performs 
* all the relocations specified therein. 
* Absolute symbol addresses are looked up in the 'externals' table. 
* 
* RETURNS: OK or ERROR. 
*/ 
 
STATUS omfCpuSegReloc 
    ( 
    void *       pNextRelocCmd,  /* ptr to current relocation command */ 
    [information about sections], 
    SCN_ADRS *   pScnAddr,       /* section address once loaded */ 
    SYM_ADRS_TBL symAdrsTbl,     /* array of absolute symbol values */ 
    [information about symbols], 
    BOOL         swapIsRequired  /* if TRUE, byte order must be swapped */ 
    ) 
    { 
    [Initialization of routine variables]
    /* Relocation loop */ 
 
    for (relocNum = 0; relocNum < [# of relocation entries]; relocNum++) 
        { 
        /* read relocation command */ 
 
        pNextRelocCmd = omfCpuRelocEntryRd (pNextRelocCmd, &relocCmd, 
                                            swapIsRequired); 
 
        /* 
         * Calculate the actual remote address that needs relocation and 
         * perform external or section-relative relocation. 
         */ 
 
        pAdrs = (void *)((INT32)*pScnAddr + relocCmd.offset); 
 
        switch (relocCmd.type) 
            { 
            case RELOC_TYPE_1: 
                TgtMemRead (pAdrs, &value, 4); 
                SWAB_32_IF (swapIsRequired, value);       /* host fmt */ 
                value = [relocation computation]; 
                SWAB_32_IF (swapIsRequired, value);       /* tgt fmt */ 
                TgtMemWriteManyInts (pAdrs, value); 
                break; 
 
            case RELOC_TYPE_2: 
                TgtMemRead (pAdrs, &value, 1); 
                value = [relocation computation]; 
                tgtMemWriteByte (pAdrs, value); 
                break; 
 
            case RELOC_TYPE_3: 
                TgtMemRead (pAdrs, &value, 2); 
                SWAB_16_IF (swapIsRequired, value);       /* host fmt */ 
                value = [relocation computation]; 
                SWAB_16_IF (swapIsRequired, value);       /* tgt fmt */ 
                tgtMemWriteShort (pAdrs, value); 
                break; 
 
        default: 
            wpwrLogErr("Unrecognized reloc type %d\n",relocCmd.type); 
            errno = UNRECOGNIZED_RELOC_ENTRY; 
            status = ERROR; 
            break; 
            } 
        } 
 
    return (status); 
    }

3.9.15   Downloading Relocated Modules to Target Memory

After all relocation manipulations are done, download the text and data segments to target memory using loadOmfCacheFlush( ) as shown in Example 3-15. This process includes a modification of the block attributes using tgtMemCacheSet( ) with the push flag set to TRUE. It also cleans up the area used by the bss segment. Remember that non-relocatable files were already downloaded to the target where necessary at the same time that relocatable files were loaded into cache for relocation. Remember also that the core file is not pushed to the target memory. (See 3.9.12 Reading in the Segments.)

Example 3-15:   loadOmfCacheFlush( )

/************************************************************************ 
* loadOmfCacheFlush - flush the target server cache to target's memory 
* 
* This routine synchronizes cache on host with target memory and sets the 
* appropriate attributes, depending on the segment types. 
* 
* RETURNS: OK, or ERROR if host cache and target memory cannot be 
*          synchronized. 
* 
*/ 
 
LOCAL STATUS loadOmfCacheFlush 
    ( 
    SEG_INFO *   pSeg,                 /* info about loaded segments */ 
    BOOL         textIsCached,         /* text segment in host cache */ 
    BOOL         dataIsCached          /* data segment in host cache */ 
    ) 
    { 
    /* 
     * Everything (text and data) is flushed to the target when the 
     * attribute is changed (to MEM_TEXT or MEM_NONE). 
     */ 
 
    if ((pSeg->sizeBss != 0) &&                 /* zero out bss */ 
        (TgtMemSet (pSeg->pAddrBss, pSeg->sizeBss, 0) != OK)) 
        return (ERROR); 
 
    /* 
     * Update the load state with the beginning of the download.  
     * We give the byte count of the two segments. (Bss is not  
     * transferred, but initilized.) 
     */ 
 
    asyncLoadUpdate (LOAD_DOWNLOADING, pSeg->sizeText+pSeg->sizeData, 0); 
 
    /* 
     * Synchronize cache on host and target memory. The text segment is set 
     * to MEM_TEXT since only writes need to go to the target. The data 
     * segment is set to MEM_NONE so that reads and writes go to the 
     * target (with automatic synchronization between cache and target). 
     * Update the target instruction cache with the text segment in memory. 
     */ 
 
    if (textIsCached && 
        (TgtMemCacheSet (pSeg->pAddrText, pSeg->sizeText, MEM_TEXT, 
                            TRUE) != OK)) 
        return (ERROR); 
 
    if (TgtMemCacheTextUpdate (pSeg->pAddrText, pSeg->sizeText) != OK) 
        return (ERROR); 
 
    if (dataIsCached && 
        (TgtMemCacheSet (pSeg->pAddrData, pSeg->sizeData, MEM_NONE, 
                            TRUE) != OK)) 
        return (ERROR); 
 
    return (OK); 
    }

3.9.16   Managing Memory

The final responsibility of the OMF manager is to apply write protection on the text segment (if the target has an MMU) and to update the target instruction and data caches.

Achieve synchronization between the target memory and the target caches with tgtCacheTextUpdate( ). The OMF manager does not have to know if such caches actually exist on the target system. The update request is satisfied as well as possible by the target agent. Note that this routine is called for every text and literal section of a fully linked executable that does not correspond to the three-segment model. However, you need not call this routine for the core file because it is not downloaded to the target.

Virtual memory management is more complex. It is not possible to protect fully linked files since these files may not be page-aligned. For the text segment of relocatable object modules, the loader manages alignment automatically using loadSegmentAllocate( ). The text segment is automatically allocated on a page boundary if text protection is implemented on the target and the flag SEG_WRITE_PROTECTION is set in the flagsText field of the SEG_INFO structure. The OMF manager checks that the module is relocatable and that the flag SEG_WRITE_PROTECTION is set before calling tgtTextProtect( ).

3.9.17   Notes on Asynchronous Load Operation

The target server can report load progress information to the WTX client which submitted the load operation. In order to do this, the target server exports a routine called asyncLoadUpdate ( ) which the loader should call to update the load progress state. For example, the loader should call asyncLoadUpdate ( ) before downloading the segments to the target (See Example 3-15). For information on how the target server retrieves the status, see 3.4.4 Asynchronous Load Operation.

The target server regularly updates the downloaded bytes count while a load is in process. If a load is cancelled, the target server returns directly from the download operation, reporting an error. The OMF reader must free all the allocated resources.

3.9.18   Utility Functions and Error Logging

Log errors and warnings with wpwrLogErr( ) and wpwrLogWarn( ). The target server only displays messages if it is running in verbose mode.

The routine wpwrLogErr( ) is used for critical failures; it prevents further module processing. The routine wpwrLogWarn( ) is used for non-critical problems. These routines accept parameters in a format similar to printf( ). A message begins with a capital letter and does not have a period at the end (for example, "This is an error message"). Examples of these calls are found in the code examples already presented.

For a complete survey of utility routines available from the target server, see the online reference material under Tornado API Reference>Target Server Internal Routines.