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:
|
NOTE: DLLs should export only necessary data to maintain good performance.
|
||||||||||||||||||
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:
The Cpu part of the RU routine name is defined in the ExtentionName field in the installDir/host/resource/target/architecturedb file.
The variant portions of the file names are derived by rules similar to those for the RU routine names.
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:
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).
/************************************************************************ * * 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; }
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)
/************************************************************************ * * 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.
/************************************************************************ * * 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);
|
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.
|
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.
|
||||||||||||||||||
/************************************************************************ * * 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 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.
/************************************************************************ * * 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.
/************************************************************************ * 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.
/************************************************************************ * 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)); } }
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.
/************************************************************************* * 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 */
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.
/************************************************************************* * 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; }
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.
/************************************************************************ * 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); } }
/************************************************************************ * * 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 +
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:
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:
/************************************************************************ * 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.
/************************************************************************ * 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.
/************************************************************************* * 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]
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.)
/************************************************************************ * 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); }
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.