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:


*

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:

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

  • OMF Manager:
  • 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( )
     
    COFF
     
    Coff
     
    loadCoffFmtManage( )
     
    ELF
     
    Elf
     
    loadElfFmtManage( )
     

  • RU:
  • 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:

    Table 3-2:  RU Function Names


    OMF
     
    Cpu name
     
    Variant parts
     
    Function name
     

    a.out
     
    MC680x0
     
    Aout - 68k
     
    aout68kSegReloc( )
     
    a.out
     
    SPARC
     
    Aout - Sparc
     
    aoutSparcSegReloc( )
     
    a.out
     
    X86
     
    Aout - X86
     
    aoutX86SegReloc( )
     
    COFF
     
    I960JX
     
    Coff - I960
     
    coffI960SegReloc( )
     
    COFF
     
    ARM
     
    Coff - Arm
     
    coffArmSegReloc( )
     
    ELF
     
    PowerPC
     
    Elf - Ppc
     
    elfPpcSegReloc( )
     
    ELF
     
    MIPS
     
    Elf - Mips
     
    elfMipsSegReloc( )
     

    File Names

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

  • OMF Manager:
  • 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
     
    COFF
     
    Coff
     
    loadCoff
     
    ELF
     
    Elf
     
    loadElf
     

  • RU:
  • The file is called omfcpu where omf and cpu are the only variant portions and reflect the OMF and the CPU as follows:

    Table 3-4:  RU File Names


    OMF
     
    Cpu name
     
    Variant Parts
     
    File Name
     

    a.out
     
    MC68040
     
    aout - 68k
     
    aout68k
     
    a.out
     
    SPARC
     
    aout -Sparc
     
    aoutSparc
     
    a.out
     
    X86
     
    Aout - X86
     
    aoutX86
     
    COFF
     
    I960JX
     
    coff - I960
     
    coffI960
     
    COFF
     
    ARM7TDMI
     
    coff - Arm
     
    coffArm
     
    ELF
     
    PowerPC
     
    Elf - Ppc
     
    elfPpc
     
    ELF
     
    MIPS
     
    Elf - Mips
     
    elfMips
     

    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. 114).

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

    It may also contain an optional initialization routine:

    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 Module's magic number.

    The output parameter is:

    BOOL * pSwapIsRequired
    A pointer to a boolean saying if object module is in the opposite byte order and requires that all information be swapped.

    The return value is:

    STATUS
    TRUE or FALSE.

    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 omfcpuFoduleVerify 
        ( 
        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( )

    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
    OK or ERROR. (See installDir/host/include/host.h for a description of error types and macros.)

    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 file descriptor of the module object.

    The output parameter is:

    BOOL * pFormatIsKnown
    A pointer to a boolean that holds the answer.

    The return value is:

    STATUS
    OK or ERROR.

    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
    A pointer to the beginning of the object-module copy in memory.

    int loadFlag
    The loader option(s). See loadlib.h for the definition of all macros associated with the options.

    SYMTAB_ID symTbl
    The target symbol table. This is actually a pointer to a SYMTAB structure. See symlib.h for a description of this structure.

    MODULE_ID moduleId
    A pointer to the object-module descriptor (MODULE structure). See module.h for a description of this structure.

    The input-output parameters are:

    void ** ppText
    Holds the required address where the text segment must be stored, or NULL if no special address is required. When the routine returns, it is set with the final address of the text segment (which may or may not have changed).

    void ** ppData
    Holds the required address for the data segment, or NULL. It returns the final address of the data segment.

    void ** ppBss
    Holds the required address for the bss segment, or NULL. It returns the final address of the bss segment.

    SEG_INFO * pSeg
    A pointer to a structure that holds all information about segments required to process the object file. See loadLib.h for a description of the SEG_INFO structure.

    The return value is:

    STATUS
    OK or ERROR.


    *

    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 host byte order, which is the byte order of the target server, may be different than the target byte order. The cross-compiler usually produces object modules in target byte order; thus the headers, symbol table, and relocation entries may be the wrong byte order for the loader. Even the pieces that require relocation within the text and data segments may be affected since computation is done on the host for the target.

    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
    Swap 2 bytes: AB becomes BA.

    SWAB_32
    Swap 4 bytes: ABCD becomes DCBA.

    SWAB_16_IF
    Apply SWAB_16 operation if a boolean is true.

    SWAB_32_IF
    Apply SWAB_32 operation if a boolean is true.

    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. 
                 */ 
     
                nbytes = LoadAlignGet ([alignment], (void *)pSeg->sizeText); 
                pSeg->sizeText += [section's size] + nbytes; 
                } 
     
            else if ([section is of type data]) 
                { 
                /* Record alignment for data sections */ 
     
                if (dataAlign < 0) 
                    dataAlign = [alignment]; 
     
                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 (bssAlign < 0) 
                    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. 
    * (The DIAB DATA tool chain is supported for compatibility only.) 
    * Note that if no signature can be found, the builder is set to  
    * "unknown". 
    * 
    * 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. The only signatures  
    * recognized are "gcc2_compiled." (GNU tool chain) and "vxWorks" (DIAB 
    * tool chain). 
     
    * 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:

    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:

    If the symbol satisfies these constraints, take the following steps to add it to the 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:

    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 two segments byte count (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 needs 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.