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.
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.)
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.
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"
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
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_COMMON_MATCH_NONE* |
|||||||||||||||||||
|
|
|||||||||||||||||||
|
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.
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
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.
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.
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.
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.
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.
|
|
|||||||||||||||||||
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:
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:
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.
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 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.)
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.
#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.
|
|
|||||||||||||||||||
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:
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:
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.
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:
|
|
|||||||||||||||||||
Three mutually exclusive options provide the ability to specify how the loader handles common symbols. They are listed in order of decreasing strictness:
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.
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:
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.
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:
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.
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).
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.
The target server has a specific thread which handles all the load operations. This design has the following advantages:
Figure 3-3 illustrates how a load is performed by the target server.
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.
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:
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.
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:
|
|
|||||||||||||||||||
|
|
|||||||||||||||||||
|
|
|||||||||||||||||||
|
|
|||||||||||||||||||
|
|
|||||||||||||||||||
|
|
|||||||||||||||||||
|
|
|||||||||||||||||||
|
|
|||||||||||||||||||
|
|
|||||||||||||||||||
|
|
|||||||||||||||||||
|
|
|||||||||||||||||||
|
|
|||||||||||||||||||
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:
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.
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);
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;
}
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.
This routine is the first entry point; it loads the correct relocation unit for the current target CPU.
STATUS loadOmfFmtInit (void)
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);
}
This routine is the second entry point; it checks the file format.
STATUS loadOmfFmtCheck (int moduleFd, BOOL * pFormatIsKnown);
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);
}
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 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.
|
|
|||||||||||||||||||
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);
}
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.
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));
}
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);
}
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));
}
}
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);
}
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;
}
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.
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:
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);
}
/************************************************************************
*
* 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);
}
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 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)
|
|
|||||||||||||||||||
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:
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:
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);
}
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).
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:
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);
}
code address = section address + offset of reference within section
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);
}
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);
}
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( ).
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.
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.