Channels ▼
RSS

Design

Examining OS/2 2.1 Executable File Formats

Source Code Accompanies This Article. Download It Now.


SEP94: Examining OS/2 2.1 Executable File Formats

Examining OS/2 2.1 Executable File Formats

An inside look at 32-bit LX-style executables

John Rodley

John is president of AJR Co., located in Cambridge, MA and can be contacted on CompuServe at 72607,3142 or at [email protected]


Executable files are the end result of a massive collaboration of makefiles, source files, include files, compiler flags, linker flags, environment variables, definition files, resource files, and even source-control flags. Get something wrong in one of them, and even the prettiest algorithm morphs into a UAE.

Windows programmers who've forgotten to export a dialog window procedure, for instance, know all about this--the dialog comes up, runs (sort of), then crashes. You know you have to link with the DLL version of the run-time library (LIBCDLL.DLL), but you have this nagging suspicion you might still be catching the static run-time library. What you need is a tool that looks into the executable, telling you exactly what's going on inside by first dumping the Resident and Non-Resident Name Tables where the offending window procedure should appear, then the Imported Name Table where LLIBCDLL.DLL should appear.

SHOWEXE.C does just this. The original version of SHOWEXE, which exploded NE-style executables, was written by David Schmitt (PC Tech Journal, November, 1988). The updated version I present here does the same for 32-bit, flat-memory-model, LX-style executables. Although LX is documented in the IBM OS/2 32-bit Object Module Format and Linear Executable Module Format (available on CompuServe, type GO OS2SUP, library 17, OMF.ZIP), I've yet to run across any NE documentation. Consequently, I relied on Schmitt's article, my debugger, and lots of experimentation to write this update of SHOWEXE.

Headers

An NE or LX .EXE file always contains the old DOS 2.1 MZ .EXE and the new NE, or LX .EXE. The MZ .EXE (so called for the two ASCII bytes at offset 0 in the file) contains the DOS stub program that prints the message "This program requires Microsoft Windows." The MZ header contains a pointer to the NE or LX header (see lfanew in Listing One) that is the file offset of the new style .EXE header. Listing One shows a simplified MZ header structure that gets you the new .EXE file offset. Between the MZ header and file offset lfanew, there may reside an actual DOS program of variable size.

The ASCII chars at lfanew contain the executable format specifier: NE, LE, LX, or PE (for Windows NT). NE indicates a 16-bit, segmented Windows or OS/2 .EXE; LX a 32-bit, flat-model OS/2 2.1 .EXE. Windows 3.1 is made up almost entirely of NE executables, while OS/2 2.1 contains a mix of NE and LX executables. Table 1 shows the file types of some of the files delivered with Windows 3.1 and OS/2 2.1. SMARTDRV and EMM386 are the only LE executables I have found in either of these systems.

A look at the header flags shows that there are several moves in the direction of cross-processor portability in LX, the most significant being the Byte Order and Word Order specifiers. Occurring directly after the Executable Type specifier (to minimize the amount of wrong-endian processing any loader might have to do), these allow either Big- or Little-endian byte and word orders over the entire rest of the executable file. Processor Type and OS Type specifiers also now each get 16 bits of their own, where NE relegated them to a couple of bits each in the Flags word. Interestingly, the Memory Page Size, fixed at 4K on Intel x86 CPUs, is also parameterized in the header, presumably to vary over different hardware platforms.

NE and LX take different approaches to preserving executable integrity. NE allowed space for a 32-bit file checksum in the header. This was intended as a layer of protection against viruses, to be checked off-line via a separate virus checker. LX takes a finer-grained, load-time approach to executable integrity. There are individual checksums for each 4K page as well as the Fixup Section, the Non-Resident Name Table, and the Loader Section, which includes all tables except the Non-Resident Name Table. In sum, these cover the entire file; individually, they allow the loader to do much less expensive checksums against small pieces of the file as they're loaded, rather than doing the whole file at once as the NE checksum requires.

Initial values in both models are much what you'd expect: code segment/offset, data segment/offset, stack size, heap size, and so on. They change from 16-bit values in NE to 32-bit values in LX, but their intent remains the same.

Segments, Pages, and Objects

The real difference between OS/2 1.x and OS/2 2.x is the shift from the 16-bit segmented-memory model to the 32-bit, flat-memory model. This shift is best reflected in the NE Segment Table and its LX analogs, the Object and Object Page Tables.

Listing Two has two global arrays of 32K integers, with a single reference to each array. When compiled and linked as an NE .EXE using Microsoft C 6.x, the linker produces one code segment from the example program, and three data segments: a distinct segment for each array (segments 3 and 4) and one for the Auto-DS Segment (segment 5). The same program linked 32-bit under Borland C++ for OS/2 produces the list of objects and pages shown in Table 5. As with NE, you get only one code object that has one page, but the two large arrays produce only one data object made up of 64 zero-filled, 4K pages (32,767 ints*4 bytes per int->262,136 bytes and 262,136 bytes/4096 bytes/page = 64 pages). Thus, LX replaces the NE Segment Table with the Object and Object Page Tables.

An NE Segment Table entry contains a set of flags (READ/WRITE/EXECUTE_), the file location of the Segment image, the file size of the segment, and its size in memory. An LX Object Table entry looks much the same, containing the object's size and attributes (read/write/execute_), a count of pages that make up the object, and a pointer into the Object Page Table where this object's first page is described (and the rest are stored consecutively). Listing One shows the structures of NE's Segment Table and LX's Object and Object Page Table entries.

For these objects/segments to make a coherent program, the code object/segment has to be able to access addresses in the other objects/segments. Relocation--the process of connecting references within a lump of executable code to things outside that lump--consists of the target, source, and Fixup Record. The target is the place in the code where a symbolic reference must be replaced by a real address (the target of the relocation process), the source is where the real address can be found, and the Fixup Record links the target to the source. The Fixup Record Table is a list of Fixup Records for a particular segment/object.

External fixups are references that resolve to something outside this executable, typically DLL calls. Internal fixups are references to objects within this executable, typically references to the data segment. To discuss 32- versus 16-bit executables, I'll first examine internal fixups.

NE and LX structure the relocation data differently. NE attaches a separate Fixup Record Table to each segment that needs one. If a segment contains targets, NE physically appends a Fixup Record Table to the end of that segment and sets the Relocations Available bit in the segment description in the Segment Table. The fixup records themselves contain the offset of the target and segment number and offset of the source.

LX linkers place a single Fixup Record Table in the header. Each page in the Object Page Table contains an index into the Fixup Record Table pointing to the first fixup record for that page. The linker stores all the Fixup Records for that page consecutively, right up to the first fixup record for the next page. The fixup record contains the offset of the target and the object number and offset of the source. The target itself contains only the offset of the source. When loading a particular page, the loader runs through the Fixup Record Table, reading the attributes of the target and source, the source-object number, and the location of the target from the fixup record. Then it gets the actual offset of the source within the source object from the contents of the target.

While their fixup records are roughly equivalent, NE requires one record for each source, while LX uses the more obvious one-per-target strategy. Listing Three has three large arrays and two references to each array. NE produces three fixup records, while LX produces six; see Table 3. Obviously, NE is more load-time efficient, while LX is more run-time efficient. NE also allows for chaining of targets.

External fixups refer to objects located physically outside the executable, most commonly DLL calls. Both NE and LX executables support import-by-ordinal and import-by-name external fixups. The linker generates import-by-name external fixups for called functions defined explicitly in the IMPORTS section of the .DEF file. It generates import-by-ordinal external fixups for called functions defined in import libraries, a much more common technique. The benefit of import-by-name is that you don't need the import library to link the executable, but this technique is very rare.

With import-by-name DLL calls, the linker stores the names of both the external DLL and the function within the DLL in the .EXE itself. In LX, the target field in the fixup record contains indexes into the Import Module Name Table and the Import Procedure Name Table. This gives you the name of both the DLL and the function within the DLL. In NE, the target field contains two indexes into the Imported Name Table that do the same job.

With an import-by-ordinal reference, the linker specifies the DLL name and the function's ordinal within that DLL. The fixup-record target field contains an index into the Import Module Name Table (Imported Name Table in NE). However, instead of an index into the Import Procedure Name Table to get the function name, it contains the function's ordinal number within the external DLL. Thus, to get the function name to go with the ordinal, you have to run something like SHOWEXE on the external DLL and find the ordinal in either the Resident or Non-Resident Name Tables.

While the fixup records and sources for external fixups are much the same in NE and LX, the targets can be very different. In NE, targets within the same segment that resolve to the same source are chained together so that the loader has only to read one fixup record. Listing Four makes three calls to DosSleep(). You might expect this to generate three fixup records, but you only get one. Listing Five is a disassembled Listing Four in which the contents of the three target calls to DosSleep are 0000:001B, 0000:0024, and 0000:FFFF. There's the chain: The fixup record points to the target at offset 0012, which points to the target at offset 001B to the target at offset 0024, which signals the end of the chain with FFFF. At load-time, the loader gets a real address for the source, then marches up the target chain, replacing the chain links with the real address. (You have to look at the disk file to see the target chain. CodeView will replace the target-chain links with a real address.) In LX, there is no support for target chaining. The Fixup Record Table for the LX version of Listing Four is shown in Table 4. As you can see, the linker produces a separate fixup record for each call to DosSleep().

Reading the Tables

In either format, the linker places the "file offsets" of all the tables (except the Fixup Record Table in NE) in the header, but you have to be careful with these offsets. Though most of them are relative to the beginning of the new header, some are relative to other offsets or to the beginning of the file. Some of the tables have corresponding element counts within the header, but some are terminated by a special character, and others are only terminated by reaching the file offset of the next file section. And just to make it interesting, some of the tables, such as the Entry and Fixup Record Tables, contain entries which are structures made up of members of variable size (8, 16, and 32 bit). Table 2 lists the tables for LX, where they're located, what type of elements they contain, and how they're terminated.

The structures of the fixed-size entries are shown in Listing One. To read them, keep reading the fixed-size struct until you hit the terminator. The only table you have to dig for is the NE Fixup Record Table. The NE linker appends Fixup Record Tables to the segments to which they belong. Loading them requires finding the segment through the offset in the Segment Table record, adding the segment size, reading the two-byte relocation count at that offset, and then reading that many fixup records.

Under NE, the "Name" table entries (Imported, Resident, and Non-Resident) all contain a Pascal-style string (one-byte length followed by length bytes, non-null-terminated string). Resident and Non-Resident Name Table entries also add a 2-byte ordinal to the end. Under LX, the Name table-entry structures (Import Module, Import Procedure, Resident and Non-Resident) are identical except that they expand the string length to two bytes.

The Entry Table in NE and the Entry and Fixup Record Tables in LX contain more-complex, variable-sized entries. All these tables have to be read as byte streams. Entry Table entries are bundled, with the first two bytes being the count of entries in the bundle and their type, followed by count entries of a single format. So, you hit the bundle, figure out the count and the encapsulated format, and read count encapsulated entries. Figure 1 shows the format for LX Entry Table entries.

Unlike NE, the LX Fixup Record Table is part of the file header. The fixup records themselves can take one of four formats, and within each of these formats, two or three of the fields have a variable size. The fixup-record types you'll run into most often are 16-bit Internal Fixups and Import-by-Ordinal External Fixups. Figure 2 is the format of Fixup Records for LX.

Physical Objects

The only other types of data in the EXE are Debugging Info and Segments/Pages. Both NE and LX punt on Debugging Info, allowing the linker to reserve a section of the executable for proprietary-format debugging data. LX does make an attempt to assist the debugging process, instituting a pointer to a linker/debugger-specific Debugging Info Section and Debugging Info Length.

In NE, the linker places the file offsets of all the code and data segments in the Segment Table. The actual segments are located on sector boundaries and are found by shifting the sector index in the Segment Table entry (ns_sector in the SEG struct) left by the alignment (align from NE struct). Get the offset and read ns_cbseg bytes, and you have the actual segment.

In LX, the linker puts the offset of the page within the Data or Iterated Page Section in the page's Object Page Table entry, while placing the sizes and file offsets of the actual Data and Iterated Page Sections in the header. You locate the physical pages within the file by reading the page-data offset from the Object Page Table, shifting it left by the Page Offset Shift and adding it to either the Data or Iterated Data Pages Offset (depending on flags in the OBJPG struct). All pages are physically 4K long with any sub-4K pages zero-filled to reach the required size. Remember that both formats support zero-filled pages/segments that exist as entries in the Object Page or Segment Tables but don't have physical images in the executable.

Conclusion

The days when the loader simply copied all the bytes into memory and jumped to the entry point are long gone. With OOP, GUIs, internationalization, and the drive toward portability, EXE file formats have become more interesting. The loader and operating system now encompass functionality that would previously have been written into the source--if written at all. To master this functionality, you need to understand the reaction of the loader and the OS to particular facets of the executable, and be able to see clearly all the parts of that executable.

Acknowledgments

Special thanks to Michael Roth at IBM Austin for his help with this article.

Table 1: (a) Windows 3.1 programs and their .EXE formats; (b) OS/2 2.1 programs and their .EXE formats.

    Format   Name           Description
(a) NE   SOL.EXE        Solitaire game
    LE   EMM386.EXE     Extended-memory manager
    NE   PRINTMAN.EXE   Print manager
    NE   PROGMAN.EXE    Program-manager shell
    LE   SMARTDRV.EXE   SmartDrive disk cache
    NE   GDI.EXE        Graphical-device interface API
    NE   RECORDER.DLL   Windows recorder
    NE   VBRUN300.DLL   Visual Basic run-time DLL

(b) LX   PMSHELL.EXE    Presentation Manager shell
    LX   CMD.EXE        Command-line shell
    NE   LINK386.EXE    Linker
    NE   RC.EXE         Resource compiler
    LX   DOSCALL1.DLL   System-call DLL
    LX   OS2KRNL        OS/2 2.1 kernel

Table 2: LX tables, their file locations and terminations. All symbolic references are to the LX .EXE header structure of Listing One except lfanew, which is a member of the MZ header.

Table Name     File Location          Termination                Entry Size

Object         lfanew+ObjTblOfs       Item count NumObjs         Fixed
Object Page    lfanew+ObjPgTblOfs     Item count*                Fixed
Resource       lfanew+RscTblOfs       Item count NumRscEntries   Fixed
Resident Name  lfanew+ResTblOfs       Null terminator            Variable/
                                                                   string
Non-Resident   NResTblOfs             Byte count NResNmTblLen    Variable/
  Name                                                             string
Entry          lfanew+EntryTblOfs     Null terminator            Variable/
                                                                   bundled
Module-format  lfanew+ModFmtTblOfs    Item count NumModEntries   Fixed
Directive
Fixup Page     lfanew+FixupPgTblOfs   See Object Page Table      Fixed
Fixup Record   lfanew+FixupRecTblOfs  File offset <              Variable
                                      lfanew+ImpModTblOfs
Import         lfanew+ImpModTblOfs    Item count ImpModEntries   Variable/
  Module Name                                                      string
Import         lfanew+ImpProcTblOfs   File offset < DataPgOfs    Variable/
  Procedure                                                        string
  Name

Table 3: LX relocation list for Listing Three.

    Type       Target   Source

    Internal   1.0054   2.000401ec
    Internal   1.004a   2.000401e8
    Internal   1.0040   2.000201f0
    Internal   1.0036   2.000201ec
    Internal   1.002c   2.01f4
    Internal   1.0022   2.01f0

Table 4: LX relocation list for Listing Four.

    Type                Target   Source

    Import by ordinal   1.37     DOSCALLS.229
    Import by ordinal   1.2d     DOSCALLS.229
    Import by ordinal   1.23     DOSCALLS.229

Table 5: LX Object and Object Page Tables for Listing Two.

Object and Object Page Tables
        Object 1, Size 1598, Addr 0, Flags 2005, PgTableInd 1, NumPgs 1, Rsv0
             READ, EXECUTE, 32-BIT,
        PAGES:    Number    Offset        Size    Flags
             1    0 (0x0)    2048    Legal Physical Page - 0000
        Object 2, Size 262808, Addr 0, Flags 2003, PgTableInd 2, NumPgs 65, Rsv0
             READ, WRITE, 32-BIT,
        PAGES:    Number    Offset        Size    Flags
             2    4 (0x4)    512    Legal Physical Page - 0000
             3    0 (0x0)    0    Zero Filled Page - 0003
             4    0 (0x0)    0    Zero Filled Page - 0003
             5    0 (0x0)    0    Zero Filled Page - 0003
             6    0 (0x0)    0    Zero Filled Page - 0003
             7    0 (0x0)    0    Zero Filled Page - 0003
             8    0 (0x0)    0    Zero Filled Page - 0003
             9    0 (0x0)    0    Zero Filled Page - 0003
            10    0 (0x0)    0    Zero Filled Page - 0003
            11    0 (0x0)    0    Zero Filled Page - 0003
            12    0 (0x0)    0    Zero Filled Page - 0003
            13    0 (0x0)    0    Zero Filled Page - 0003
            14    0 (0x0)    0    Zero Filled Page - 0003
            15    0 (0x0)    0    Zero Filled Page - 0003
            16    0 (0x0)    0    Zero Filled Page - 0003
            17    0 (0x0)    0    Zero Filled Page - 0003
            18    0 (0x0)    0    Zero Filled Page - 0003
            19    0 (0x0)    0    Zero Filled Page - 0003
            20    0 (0x0)    0    Zero Filled Page - 0003
            21    0 (0x0)    0    Zero Filled Page - 0003
            22    0 (0x0)    0    Zero Filled Page - 0003
            23    0 (0x0)    0    Zero Filled Page - 0003
            24    0 (0x0)    0    Zero Filled Page - 0003
            25    0 (0x0)    0    Zero Filled Page - 0003
            26    0 (0x0)    0    Zero Filled Page - 0003
            27    0 (0x0)    0    Zero Filled Page - 0003
            28    0 (0x0)    0    Zero Filled Page - 0003
            29    0 (0x0)    0    Zero Filled Page - 0003
            30    0 (0x0)    0    Zero Filled Page - 0003
            31    0 (0x0)    0    Zero Filled Page - 0003
            32    0 (0x0)    0    Zero Filled Page - 0003
            33    0 (0x0)    0    Zero Filled Page - 0003
            34    0 (0x0)    0    Zero Filled Page - 0003
            35    0 (0x0)    0    Zero Filled Page - 0003
            36    0 (0x0)    0    Zero Filled Page - 0003
            37    0 (0x0)    0    Zero Filled Page - 0003
            38    0 (0x0)    0    Zero Filled Page - 0003
            39    0 (0x0)    0    Zero Filled Page - 0003
            40    0 (0x0)    0    Zero Filled Page - 0003
            41    0 (0x0)    0    Zero Filled Page - 0003
            42    0 (0x0)    0    Zero Filled Page - 0003
            43    0 (0x0)    0    Zero Filled Page - 0003
            44    0 (0x0)    0    Zero Filled Page - 0003
            45    0 (0x0)    0    Zero Filled Page - 0003
            46    0 (0x0)    0    Zero Filled Page - 0003
            47    0 (0x0)    0    Zero Filled Page - 0003
            48    0 (0x0)    0    Zero Filled Page - 0003
            49    0 (0x0)    0    Zero Filled Page - 0003
            50    0 (0x0)    0    Zero Filled Page - 0003
            51    0 (0x0)    0    Zero Filled Page - 0003
            52    0 (0x0)    0    Zero Filled Page - 0003
            53    0 (0x0)    0    Zero Filled Page - 0003
            54    0 (0x0)    0    Zero Filled Page - 0003
            55    0 (0x0)    0    Zero Filled Page - 0003
            56    0 (0x0)    0    Zero Filled Page - 0003
            57    0 (0x0)    0    Zero Filled Page - 0003
            58    0 (0x0)    0    Zero Filled Page - 0003
            59    0 (0x0)    0    Zero Filled Page - 0003
            60    0 (0x0)    0    Zero Filled Page - 0003
            61    0 (0x0)    0    Zero Filled Page - 0003
            62    0 (0x0)    0    Zero Filled Page - 0003
            63    0 (0x0)    0    Zero Filled Page - 0003
            64    0 (0x0)    0    Zero Filled Page - 0003
            65    0 (0x0)    0    Zero Filled Page - 0003
            66    0 (0x0)    0    Zero Filled Page - 0003
         Object 3, Size 49152, Addr 0, Flags 2003, PgTableInd 67, NumPgs 1, Rsv0
            READ, WRITE, 32-BIT,
         PAGES:    Number    Offset        Size    Flags
            67    0 (0x0)    0    Zero Filled Page - 0003

Figure 1 LX Entry Table entry format.

Figure 2 LX fixup-record format.

Listing One

// NE and LX Header structures and structures of all fixed size table entry 
// types. Dummy struct that gets you file offset of "new" exe header. Ignores
// MZ header items other than ID word and lfanew. Read at offset 0 of file.
typedef struct {
    unsigned short magic;       // MUST BE ASCII "MZ"
    char useless_bytes[34];     // Ignore these bytes
        unsigned long lfanew;   // Here's the file offset of new exe header.
   } SIMPLE_MZ_EXE;
// structure of NE header.  Follows magic bytes "NE" in file.
typedef struct {
   unsigned char ver;      // Version.
   unsigned char rev;      // Revision
   unsigned short enttab;  // File offset of Entry Table from lfanew.
   unsigned short cbenttab;   // Entry Table byte count.
   long crc;               // CRC checksum of entire file.
   unsigned short flags;   // Exe flags (such as ERROR ...)
   unsigned short autodata;   // Segment num of Auto-DS seg, 1-based.
   unsigned short heap;    // Segment number of heap, 1-based.
   unsigned short stack;   // Segment number of stack, 1-based.
   unsigned short ip;      // Initial value of IP register.
   unsigned short cs;      // Initial value of CS register.
   unsigned short sp;      // Initial value of SP register.
   unsigned short ss;      // Initial value of SS register.
   unsigned short cseg;    // # of segments in Segment Table.
   unsigned short cmod;    // # of modules in Module Reference Table.
   unsigned short cbnrestab;  // Byte count of Non-Res Name Table.
   unsigned short segtab;  // Offset of Segment Table from lfanew.
   unsigned short rsrctab; // Offset of Resource Table from lfanew.
   unsigned short restab;  // Offset of Res Name Table from lfanew.
   unsigned short modtab;  // Offset of Module Ref Table from lfanew.
   unsigned short imptab;  // Offset of Imp Name Table from lfanew.
   unsigned long nrestab;  // Offset of Non-Resident Name Table from 
                           // beginning of file.
   unsigned short cmovent; // Number of movable entries.
   unsigned short align;   // File sector size, Segments are aligned 
                           // on boundaries of this value.
   unsigned short cres;    // Item count of Resource Table.
   char resv[10];          // reserved.
   } NE_EXE;
// an entry in the NE Segment Table
struct SEG {
   unsigned short ns_sector;     // The file sector segment starts at.
   unsigned short ns_cbseg;      // # of bytes in segment image.
   unsigned short ns_flags;      // Type of segment (code,data ...)
   unsigned short ns_minalloc;   // Minimum size in memory.
   };
// an entry in NE Module Reference Table.
struct MOD_REF {
   unsigned index;   // An index into the Imported Name Table.
   unsigned uModNum; // Module number used by Fixup Records 
                     // trying to use this Module Reference.
   };
// an entry in a NE Fixup Record Table
struct NEW_REL {
   unsigned char target;       // Type of target (see targets below)
   unsigned char source;       // Type of source (see sources below)
   unsigned offset;            // Offset in this segment of target.
   unsigned module_num;        // Module number (see Mod Reference entry) if
                               // source = 1 or 2, segment number if source = 0
   unsigned ordinal;   // target offset if source=0, function ordinal if 
                       // source=1 and function name, offset in Imported 
                       // Name Table if source=2
   };
// Possible values for target
#define NE_TARG_16SEG           2   // 16-bit segment
#define NE_TARG_16SEGOFS        3   // 16-bit segment, 16-bit offset.
#define NE_TARG_16OFS           5   // 16-bit offset.
#define NE_TARG_16SEG32OFS      11  // 16-bit segment, 32-bit offset.
#define NE_TARG_32OFS           13  // 32-bit offset.
// Possible values for source
#define NE_DEST_THISEXE         0   // Source is in this exe.
#define NE_DEST_DLLBYORDINAL    1   // Source is imported by ordinal.
#define NE_DEST_DLLBYNAME       2   // Source is imported by name.
// Structure that defines the LX exe header. Follows the two magic bytes "LX".
typedef struct {
    UCHAR ByteOrder;        // LITTLE_ENDIAN or BIG_ENDIAN
    UCHAR WordOrder;        // LITTLE_ENDIAN or BIG_ENDIAN
    ULONG FormatLevel;      // Loader format level, currently 0
    USHORT CpuType;         // 286 through Pentium+
    USHORT OSType;          // DOS, Win, OS/2 ...
    ULONG ModVersion;       // Version of this exe
    ULONG ModFlags;         // Program/Library ...
    ULONG ModNumPgs;        // Number of non-zero-fill or invalid pages
    ULONG EIPObjNum;        // Initial code object
    ULONG EIP;              // Start address within EIPObjNum
    ULONG ESPObjNum;        // Initial stack object
    ULONG Esp;              // Top of stack within ESPObjNum
    ULONG PgSize;           // Page size, fixed at 4k 
    ULONG PgOfsShift;       // Page alignment shift
    ULONG FixupSectionSize; // Size of fixup information in file
    ULONG FixupCksum;       // Checksum of FixupSection
    ULONG LdrSecSize;       // Size of Loader Section
    ULONG LdrSecCksum;      // Loader Section checksum
    ULONG ObjTblOfs;        // File offset of Object Table
    ULONG NumObjects;       // Number of Objects
    ULONG ObjPgTblOfs;      // File offset of Object Page Table
    ULONG ObjIterPgsOfs;    // File offset of Iterated Data Pages
    ULONG RscTblOfs;        // File offset of Resource Table
    ULONG NumRscTblEnt;     // # of entries in Resource Table
        ULONG ResNameTblOfs;    // File offset of Resident Name Table
        ULONG EntryTblOfs;      // File offset of Entry Table
    ULONG ModDirOfs;        // File offset of Module Directives
    ULONG NumModDirs;       // Number of Module Directives
    ULONG FixupPgTblOfs;    // File offset of Fixup Page Table
    ULONG FixupRecTblOfs;   // File offset of Fixup Record Table
    ULONG ImpModTblOfs;     // File offset of Imp Module Table
    ULONG NumImpModEnt;     // Number of Imported Modules
    ULONG ImpProcTblOfs;    // File offset of Imported Proc Table
    ULONG PerPgCksumOfs;    // File offset of Per-Page 
                            // Checksum Table
    ULONG DataPgOfs;        // File offset of Data Pages
    ULONG NumPreloadPg;     // Number of Preload Pages
    ULONG NResNameTblOfs;   // File offset of Non Resident Name Table 
                            // from beginning of file!
    ULONG NResNameTblLen;   // Length in bytes of Non Resident Name Table; 
                            // table is also NULL terminated.
    ULONG NResNameTblCksum; // Non Resident Name Table checksum
    ULONG AutoDSObj;        // Object number of auto data
    ULONG DebugInfoOfs;     // File offset of debugging info
    ULONG DebugInfoLen;     // Length of Debugging Info
    ULONG NumInstPreload;   // Number of instance-preload pages
    ULONG NumInstDemand;    // Number of instance-demand pages
    ULONG HeapSize;         // Heap size
    ULONG StackSize;        // Stack size
   } LX_EXE;
// An entry in the LX object table
typedef struct {
    ULONG size;               // Load-time size of object
    ULONG reloc_base_addr;    // Address the object wants to be loaded at.
    ULONG obj_flags;          // Read/Write/Execute, Resource, Zero-fill ...
    ULONG pg_tbl_index;       // Index in Object Page Table at which this
                              // object's first page is located.
    ULONG num_pg_tbl_entries; // Number of consecutive Object Page Table 
                              // entries that belong to this object.
    ULONG reserved;           // reserved.
} LX_OBJ;
// An entry in the LX Object Page Table.
typedef struct {
   ULONG offset;     // File offset of this pages data.  Relative to
                     // beginning of Iterated or Preload Pages 
   USHORT size;      // Size of this page.  <= 4096
   USHORT flags;     // Iterated, Zero-filled, Invalid ...
   } LX_PG;
// An entry in the LX Fixup Page Table is a single 32-bit value.
// possible values for LX Object Page Table flags member
typedef enum pg_types {
   LX_DATA_PHYSICAL = 0, // Legal Physical Page, file offset relative to 
                         // Preload Pages
   LX_DATA_ITERATED,     // Iterated Data Page, file offset relative to 
                         // Iterated Pages
   LX_DATA_INVALID,      // Invalid page.
   LX_DATA_ZEROFILL,     // Zero-filled page.
   LX_DATA_RANGE         // Range of pages.
   };
// An entry in the LX Resource Table
typedef struct {
   USHORT type_id;   // one of rsc_types
   USHORT name_id;   // ID application uses to load this resource
   ULONG size;       // size of the resource
   USHORT object;    // which object is this resource located in?
   ULONG offset;     // resource offset within the object 
   } LX_RSC;


Listing Two

// TwoArray.C - Two almost-64k arrays, with one reference to each.
//    NE builds 3 segments for this, LX one object with 64 4k-pages.
#include <stdio.h>
int array1[32767];
int array2[32767];
int main(){ array1[0] = 1; array2[0] = 2; return( 0 ); }


Listing Three

// SixRef.C - Three almost-64k arrays, with two references to each.
//    NE uses 3 fixups for the references, LX 6.
#include <stdio.h>
int array1[32767], array2[32767], array3[32767];
int main(){ 
   array1[0] = 1; array1[1] = 2; 
   array2[0] = 1; array2[1] = 2; 
   array3[0] = 1; array3[1] = 2; return( 0 ); }


Listing Four

// ThreeExt.C - Three DLL calls. NE uses 1 fixup record with the three targets
// chained together. LX uses three fixups, no chain.
#define INCL_DOSPROCESS
#include <os2.h>
int main(){
   DosSleep( 2 ); DosSleep( 2 ); DosSleep( 2 );
   return( 0 ); }


Listing Five

000F:0001 8BEC           MOV       BP,SP
000F:0003 B80000         MOV       AX,0000
000F:0006 9A72020F00     CALL      000F:0272
000F:000B 57             PUSH      DI
000F:000C 56             PUSH      SI
7:         DosSleep( 2 ); DosSleep( 2 ); DosSleep( 2 );
000F:000D 6A00           PUSH      00
000F:000F 6A02           PUSH      02
000F:0011 9A1B000000     CALL      0000:001B
000F:0016 6A00           PUSH      00
000F:0018 6A02           PUSH      02
000F:001A 9A24000000     CALL      0000:0024
000F:001F 6A00           PUSH      00
000F:0021 6A02           PUSH      02
000F:0023 9AFFFF0000     CALL      0000:FFFF
8:         return( 0 ); }
000F:0028 B80000         MOV       AX,0000
000F:002B E90000         JMP       002E
000F:002E 5E             POP       SI
000F:002F 5F             POP       DI
000F:0030 C9             LEAVE     

Copyright © 1994, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.