LX - Linear eXecutable Module Format Description June 3, 1992 Figure 1. 32-bit Linear EXE File Layout 00h +------------------+ <--+ | DOS 2 Compatible | | | EXE Header | | 1Ch +------------------+ | | unused | | +------------------+ | 24h | OEM Identifier | | 26h | OEM Info | | | | |-- DOS 2.0 Section 3Ch | Offset to | | (Discarded) | Linear EXE | | | Header | | 40h +------------------+ | | DOS 2.0 Stub | | | Program & | | | Reloc. Table | | +------------------+ <--+ | | xxh +------------------+ <--+ | Executable | | | Info | | +------------------+ | | Module | | | Info | | +------------------+ |-- Linear Executable | Loader Section | | Module Header | Info | | (Resident) +------------------+ | | Table Offset | | | Info | | +------------------+ <--+ | Object Table | | +------------------+ | | Object Page Table| | +------------------+ | | Resource Table | | +------------------+ | | Resident Name | | | Table | | +------------------+ |-- Loader Section | Entry Table | | (Resident) +------------------+ | | Module Format | | | Directives Table | | | (Optional) | | +------------------+ | | Resident | | | Directives Data | | | (Optional) | | | | | | (Verify Record) | | +------------------+ | | Per-Page | | | Checksum | | +------------------+ <--+ | Fixup Page Table | | +------------------+ | | Fixup Record | | | Table | | +------------------+ |-- Fixup Section | Import Module | | (Optionally Resident) | Name Table | | +------------------+ | | Import Procedure | | | Name Table | | +------------------+ <--+ | Preload Pages | | +------------------+ | | Demand Load | | | Pages | | +------------------+ | | Iterated Pages | | +------------------+ | | Non-Resident | |-- (Non-Resident) | Name Table | | +------------------+ | | Non-Resident | | | Directives Data | | | (Optional) | | | | | | (To be Defined) | | +------------------+ <--+ | Debug Info | |-- (Not used by Loader) +------------------+ <--+ Figure 2. 32-bit Linear EXE Header +-----+-----+-----+-----+-----+-----+-----+-----+ 00h | "L" "X" |B-ORD|W-ORD| FORMAT LEVEL | +-----+-----+-----+-----+-----+-----+-----+-----+ 08h | CPU TYPE | OS TYPE | MODULE VERSION | +-----+-----+-----+-----+-----+-----+-----+-----+ 10h | MODULE FLAGS | MODULE # OF PAGES | +-----+-----+-----+-----+-----+-----+-----+-----+ 18h | EIP OBJECT # | EIP | +-----+-----+-----+-----+-----+-----+-----+-----+ 20h | ESP OBJECT # | ESP | +-----+-----+-----+-----+-----+-----+-----+-----+ 28h | PAGE SIZE | PAGE OFFSET SHIFT | +-----+-----+-----+-----+-----+-----+-----+-----+ 30h | FIXUP SECTION SIZE | FIXUP SECTION CHECKSUM| +-----+-----+-----+-----+-----+-----+-----+-----+ 38h | LOADER SECTION SIZE |LOADER SECTION CHECKSUM| +-----+-----+-----+-----+-----+-----+-----+-----+ 40h | OBJECT TABLE OFF | # OBJECTS IN MODULE | +-----+-----+-----+-----+-----+-----+-----+-----+ 48h | OBJECT PAGE TABLE OFF | OBJECT ITER PAGES OFF | +-----+-----+-----+-----+-----+-----+-----+-----+ 50h | RESOURCE TABLE OFFSET |#RESOURCE TABLE ENTRIES| +-----+-----+-----+-----+-----+-----+-----+-----+ 58h | RESIDENT NAME TBL OFF | ENTRY TABLE OFFSET | +-----+-----+-----+-----+-----+-----+-----+-----+ 60h | MODULE DIRECTIVES OFF | # MODULE DIRECTIVES | +-----+-----+-----+-----+-----+-----+-----+-----+ 68h | FIXUP PAGE TABLE OFF |FIXUP RECORD TABLE OFF | +-----+-----+-----+-----+-----+-----+-----+-----+ 70h | IMPORT MODULE TBL OFF | # IMPORT MOD ENTRIES | +-----+-----+-----+-----+-----+-----+-----+-----+ 78h | IMPORT PROC TBL OFF | PER-PAGE CHECKSUM OFF | +-----+-----+-----+-----+-----+-----+-----+-----+ 80h | DATA PAGES OFFSET | #PRELOAD PAGES | +-----+-----+-----+-----+-----+-----+-----+-----+ 88h | NON-RES NAME TBL OFF | NON-RES NAME TBL LEN | +-----+-----+-----+-----+-----+-----+-----+-----+ 90h | NON-RES NAME TBL CKSM | AUTO DS OBJECT # | +-----+-----+-----+-----+-----+-----+-----+-----+ 98h | DEBUG INFO OFF | DEBUG INFO LEN | +-----+-----+-----+-----+-----+-----+-----+-----+ A0h | #INSTANCE PRELOAD | #INSTANCE DEMAND | +-----+-----+-----+-----+-----+-----+-----+-----+ A8h | HEAPSIZE | +-----+-----+-----+-----+ Note: The OBJECT ITER PAGES OFF must either be 0 or set to the same value as DATA PAGES OFFSET in OS/2 2.0. Ie., iterated pages are required to be in the same section of the file as regular pages. Note: Table offsets in the Linear EXE Header may be set to zero to indicate that the table does not exist in the EXE file and it's size is zero. "L" "X" = DW Signature word. The signature word is used by the loader to identify the EXE file as a valid 32-bit Linear Executable Module Format. "L" is low order byte. "X" is high order byte. B-ORD = DB Byte Ordering. This byte specifies the byte ordering for the linear EXE format. The values are: 00H - Little Endian Byte Ordering. 01H - Big Endian Byte Ordering. W-ORD = DB Word Ordering. This byte specifies the Word ordering for the linear EXE format. The values are: 00H - Little Endian Word Ordering. 01H - Big Endian Word Ordering. Format Level = DD Linear EXE Format Level. The Linear EXE Format Level is set to 0 for the initial version of the 32-bit linear EXE format. Each incompatible change to the linear EXE format must increment this value. This allows the system to recognized future EXE file versions so that an appropriate error message may be displayed if an attempt is made to load them. CPU Type = DW Module CPU Type. This field specifies the type of CPU required by this module to run. The values are: 01H - 80286 or upwardly compatible CPU is required to execute this module. 02H - 80386 or upwardly compatible CPU is required to execute this module. 03H - 80486 or upwardly compatible CPU is required to execute this module. OS Type = DW Module OS Type. This field specifies the type of Operating system required to run this module. The currently defined values are: 00H - Unknown (any "new-format" OS) 01H - OS/2 (default) 02H - Windows 03H - DOS 4.x 04H - Windows 386 MODULE VERSION = DD Version of the linear EXE module. This is useful for differentiating between revisions of dynamic linked modules. This value is specified at link time by the user. MODULE FLAGS = DD Flag bits for the module. The module flag bits have the following definitions. 00000001h = Reserved for system use. 00000002h = Reserved for system use. 00000004h = Per-Process Library Initialization. The setting of this bit requires the EIP Object # and EIP fields to have valid values. If the EIP Object # and EIP fields are valid and this bit is NOT set, then Global Library Initialization is assumed. Setting this bit for an EXE file is invalid. 00000008h = Reserved for system use. 00000010h = Internal fixups for the module have been applied. The setting of this bit in a Linear Executable Module indicates that each object of the module has a preferred load address specified in the Object Table Reloc Base Addr. If the module's objects can not be loaded at these preferred addresses, then the relocation records that have been retained in the file data will be applied. 00000020h = External fixups for the module have been applied. 00000040h = Reserved for system use. 00000080h = Reserved for system use. 00000100h = Incompatible with PM windowing. 00000200h = Compatible with PM windowing. 00000300h = Uses PM windowing API. 00000400h = Reserved for system use. 00000800h = Reserved for system use. 00001000h = Reserved for system use. 00002000h = Module is not loadable. When the 'Module is not loadable' flag is set, it indicates that either errors were detected at link time or that the module is being incrementally linked and therefore can't be loaded. 00004000h = Reserved for system use. 00038000h = Module type mask. 00000000h = Program module. A module can not contain dynamic links to other modules that have the 'program module' type. 00008000h = Library module. 00018000h = Protected Memory Library module. 00020000h = Physical Device Driver module. 00028000h = Virtual Device Driver module. 40000000h = Per-process Library Termination. The setting of this bit requires the EIP Object # and EIP fields to have valid values. If the EIP Object # and EIP fields are valid and this bit is NOT set, then Global Library Termination is assumed. Setting this bit for an EXE file is invalid. MODULE # PAGES = DD Number of pages in module. This field specifies the number of pages physically contained in this module. In other words, pages containing either enumerated or iterated data, or zero-fill pages that have relocations, not invalid or zero-fill pages implied by the Virtual Size in the Object Table being larger than the number of pages actually in the linear EXE file. These pages are contained in the 'preload pages', 'demand load pages' and 'iterated data pages' sections of the linear EXE module. This is used to determine the size of the page information tables in the linear EXE module. EIP OBJECT # = DD The Object number to which the Entry Address is relative. This specifies the object to which the Entry Address is relative. This must be a nonzero value for a program module to be correctly loaded. A zero value for a library module indicates that no library entry routine exists. If this value is zero, then both the Per-process Library Initialization bit and the Per-process Library Termination bit must be clear in the module flags, or else the loader will fail to load the module. Further, if the Per-process Library Termination bit is set, then the object to which this field refers must be a 32-bit object (i.e., the Big/Default bit must be set in the object flags; see below). EIP = DD Entry Address of module. The Entry Address is the starting address for program modules and the library initialization and Library termination address for library modules. ESP OBJECT # = DD The Object number to which the ESP is relative. This specifies the object to which the starting ESP is relative. This must be a nonzero value for a program module to be correctly loaded. This field is ignored for a library module. ESP = DD Starting stack address of module. The ESP defines the starting stack pointer address for program modules. A zero value in this field indicates that the stack pointer is to be initialized to the highest address/offset in the object. This field is ignored for a library module. PAGE SIZE = DD The size of one page for this system. This field specifies the page size used by the linear EXE format and the system. For the initial version of this linear EXE format the page size is 4Kbytes. (The 4K page size is specified by a value of 4096 in this field.) PAGE OFFSET SHIFT = DD The shift left bits for page offsets. This field gives the number of bit positions to shift left when interpreting the Object Page Table entries' page offset field. This determines the alignment of the page information in the file. For example, a value of 4 in this field would align all pages in the Data Pages and Iterated Pages sections on 16 byte (paragraph) boundaries. A Page Offset Shift of 9 would align all pages on a 512 byte (disk sector) basis. The default value for this field is 12 (decimal), which give a 4096 byte alignment. All other offsets are byte aligned. FIXUP SECTION SIZE = DD Total size of the fixup information in bytes. This includes the following 4 tables: - Fixup Page Table - Fixup Record Table - Import Module name Table - Import Procedure Name Table FIXUP SECTION CHECKSUM = DD Checksum for fixup information. This is a cryptographic checksum covering all of the fixup information. The checksum for the fixup information is kept separate because the fixup data is not always loaded into main memory with the 'loader section'. If the checksum feature is not implemented, then the linker will set these fields to zero. LOADER SECTION SIZE = DD Size of memory resident tables. This is the total size in bytes of the tables required to be memory resident for the module, while the module is in use. This total size includes all tables from the Object Table down to and including the Per-Page Checksum Table. LOADER SECTION CHECKSUM = DD Checksum for loader section. This is a cryptographic checksum covering all of the loader section information. If the checksum feature is not implemented, then the linker will set these fields to zero. OBJECT TABLE OFF = DD Object Table offset. This offset is relative to the beginning of the linear EXE header. # OBJECTS IN MODULE = DD Object Table Count. This defines the number of entries in Object Table. OBJECT PAGE TABLE OFFSET = DD Object Page Table offset This offset is relative to the beginning of the linear EXE header. OBJECT ITER PAGES OFF = DD Object Iterated Pages offset. This offset is relative to the beginning of the EXE file. RESOURCE TABLE OFF = DD Resource Table offset. This offset is relative to the beginning of the linear EXE header. # RESOURCE TABLE ENTRIES = DD Number of entries in Resource Table. RESIDENT NAME TBL OFF = DD Resident Name Table offset. This offset is relative to the beginning of the linear EXE header. ENTRY TBL OFF = DD Entry Table offset. This offset is relative to the beginning of the linear EXE header. MODULE DIRECTIVES OFF = DD Module Format Directives Table offset. This offset is relative to the beginning of the linear EXE header. # MODULE DIRECTIVES = DD Number of Module Format Directives in the Table. This field specifies the number of entries in the Module Format Directives Table. FIXUP PAGE TABLE OFF = DD Fixup Page Table offset. This offset is relative to the beginning of the linear EXE header. FIXUP RECORD TABLE OFF = DD Fixup Record Table Offset This offset is relative to the beginning of the linear EXE header. IMPORT MODULE TBL OFF = DD Import Module Name Table offset. This offset is relative to the beginning of the linear EXE header. # IMPORT MOD ENTRIES = DD The number of entries in the Import Module Name Table. IMPORT PROC TBL OFF = DD Import Procedure Name Table offset. This offset is relative to the beginning of the linear EXE header. PER-PAGE CHECKSUM OFF = DD Per-Page Checksum Table offset. This offset is relative to the beginning of the linear EXE header. DATA PAGES OFFSET = DD Data Pages Offset. This offset is relative to the beginning of the EXE file. # PRELOAD PAGES = DD Number of Preload pages for this module. Note that OS/2 2.0 does not respect the preload of pages as specified in the executable file for performance reasons. NON-RES NAME TBL OFF = DD Non-Resident Name Table offset. This offset is relative to the beginning of the EXE file. NON-RES NAME TBL LEN = DD Number of bytes in the Non-resident name table. NON-RES NAME TBL CKSM = DD Non-Resident Name Table Checksum. This is a cryptographic checksum of the Non-Resident Name Table. AUTO DS OBJECT # = DD The Auto Data Segment Object number. This is the object number for the Auto Data Segment used by 16-bit modules. This field is supported for 16-bit compatibility only and is not used by 32-bit modules. DEBUG INFO OFF = DD Debug Information offset. This offset is relative to the beginning of the linear EXE header. DEBUG INFO LEN = DD Debug Information length. The length of the debug information in bytes. # INSTANCE PRELOAD = DD Instance pages in preload section. The number of instance data pages found in the preload section. # INSTANCE DEMAND = DD Instance pages in demand section. The number of instance data pages found in the demand section. HEAPSIZE = DD Heap size added to the Auto DS Object. The heap size is the number of bytes added to the Auto Data Segment by the loader. This field is supported for 16-bit compatibility only and is not used by 32-bit modules. Program (EXE) startup registers and Library entry registers Program startup registers are defined as follows. EIP = Starting program entry address. ESP = Top of stack address. CS = Code selector for base of linear address space. DS = ES = SS = Data selector for base of linear address space. FS = Data selector of base of Thread Information Block (TIB). GS = 0. EAX = EBX = 0. ECX = EDX = 0. ESI = EDI = 0. EBP = 0. [ESP+0] = Return address to routine which calls DosExit(1,EAX). [ESP+4] = Module handle for program module. [ESP+8] = Reserved. [ESP+12] = Environment data object address. [ESP+16] = Command line linear address in environment data object. Library initialization registers are defined as follows. EIP = Library entry address. ESP = User program stack. CS = Code selector for base of linear address space. DS = ES = SS = Data selector for base of linear address space. Note that a 32-bit Protected Memory Library module will be given a GDT selector in the DS and ES registers (PROTDS) that addresses the full linear address space available to a application. This selector should be saved by the initialization routine. Non-Protected Memory Library modules will receive a selector (FLATDS) that addresses the same amount of linear address space as an application's .EXE can. FS = Data selector of base of Thread Information Block (TIB). GS = 0. EAX = EBX = 0. ECX = EDX = 0. ESI = EDI = 0. EBP = 0. [ESP+0] = Return address to system, (EAX) = return code. [ESP+4] = Module handle for library module. [ESP+8] = 0 (Initialization) Note that a 32-bit library may specify that its entry address is in a 16-bit code object. In this case, the entry registers are the same as for entry to a library using the Segmented EXE format. These are documented elsewhere. This means that a 16-bit library may be relinked to take advantage of the benefits of the Linear EXE format (notably, efficient paging). Library termination registers are defined as follows. EIP = Library entry address. ESP = User program stack. CS = Code selector for base of linear address space. DS = ES = SS = Data selector for base of linear address space. FS = Data selector of base of Thread Information Block (TIB). GS = 0. EAX = EBX = 0. ECX = EDX = 0. ESI = EDI = 0. EBP = 0. [ESP+0] = Return address to system. [ESP+4] = Module handle for library module. [ESP+8] = 1 (Termination) Note that Library termination is not allowed for libraries with 16-bit entries. Object Table The number of entries in the Object Table is given by the # Objects in Module field in the linear EXE header. Entries in the Object Table are numbered starting from one. Each Object Table entry has the following format: +-----+-----+-----+-----+-----+-----+-----+-----+ 00h | VIRTUAL SIZE | RELOC BASE ADDR | +-----+-----+-----+-----+-----+-----+-----+-----+ 08h | OBJECT FLAGS | PAGE TABLE INDEX | +-----+-----+-----+-----+-----+-----+-----+-----+ 10h | # PAGE TABLE ENTRIES | RESERVED | +-----+-----+-----+-----+-----+-----+-----+-----+ VIRTUAL SIZE = DD Virtual memory size. This is the size of the object that will be allocated when the object is loaded. The object's virtual size (rounded up to the page size value) must be greater than or equal to the total size of the pages in the EXE file for the object. This memory size must also be large enough to contain all of the iterated data and uninitialized data in the EXE file. RELOC BASE ADDR = DD Relocation Base Address. The relocation base address the object is currently relocated to. If the internal relocation fixups for the module have been removed, this is the address the object will be allocated at by the loader. OBJECT FLAGS = DW Flag bits for the object. The object flag bits have the following definitions. 0001h = Readable Object. 0002h = Writable Object. 0004h = Executable Object. The readable, writable and executable flags provide support for all possible protections. In systems where all of these protections are not supported, the loader will be responsible for making the appropriate protection match for the system. 0008h = Resource Object. 0010h = Discardable Object. 0020h = Object is Shared. 0040h = Object has Preload Pages. 0080h = Object has Invalid Pages. 0100h = Object has Zero Filled Pages. 0200h = Object is Resident (valid for VDDs, PDDs only). 0300h = Object is Resident & Contiguous (VDDs, PDDs only). 0400h = Object is Resident & 'long-lockable' (VDDs, PDDs only). 0800h = Reserved for system use. 1000h = 16:16 Alias Required (80x86 Specific). 2000h = Big/Default Bit Setting (80x86 Specific). The 'big/default' bit , for data segments, controls the setting of the Big bit in the segment descriptor. (The Big bit, or B-bit, determines whether ESP or SP is used as the stack pointer.) For code segments, this bit controls the setting of the Default bit in the segment descriptor. (The Default bit, or D-bit, determines whether the default word size is 32-bits or 16-bits. It also affects the interpretation of the instruction stream.) 4000h = Object is conforming for code (80x86 Specific). 8000h = Object I/O privilege level (80x86 Specific). Only used for 16:16 Alias Objects. PAGE TABLE INDEX = DD Object Page Table Index. This specifies the number of the first object page table entry for this object. The object page table specifies where in the EXE file a page can be found for a given object and specifies per-page attributes. The object table entries are ordered by logical page in the object table. In other words the object table entries are sorted based on the object page table index value. # PAGE TABLE ENTRIES = DD # of object page table entries for this object. Any logical pages at the end of an object that do not have an entry in the object page table associated with them are handled as zero filled or invalid pages by the loader. When the last logical pages of an object are not specified with an object page table entry, they are treated as either zero filled pages or invalid pages based on the last entry in the object page table for that object. If the last entry was neither a zero filled or invalid page, then the additional pages are treated as zero filled pages. RESERVED = DD Reserved for future use. Must be set to zero. Object Page Table The Object page table provides information about a logical page in an object. A logical page may be an enumerated page, a pseudo page or an iterated page. The structure of the object page table in conjunction with the structure of the object table allows for efficient access of a page when a page fault occurs, while still allowing the physical page data to be located in the preload page, demand load page or iterated data page sections in the linear EXE module. The logical page entries in the Object Page Table are numbered starting from one. The Object Page Table is parallel to the Fixup Page Table as they are both indexed by the logical page number. Each Object Page Table entry has the following format: 63 32 31 16 15 0 +-----+-----+-----+-----+-----+-----+-----+-----+ 00h | PAGE DATA OFFSET | DATA SIZE | FLAGS | +-----+-----+-----+-----+-----+-----+-----+-----+ PAGE DATA OFFSET = DD Offset to the page data in the EXE file. This field, when bit shifted left by the PAGE OFFSET SHIFT from the module header, specifies the offset from the beginning of the Preload Page section of the physical page data in the EXE file that corresponds to this logical page entry. The page data may reside in the Preload Pages, Demand Load Pages or the Iterated Data Pages sections. If the FLAGS field specifies that this is a zero-Filled page then the PAGE DATA OFFSET field will contain a 0. If the logical page is specified as an iterated data page, as indicated by the FLAGS field, then this field specifies the offset into the Iterated Data Pages section. The logical page number (Object Page Table index), is used to index the Fixup Page Table to find any fixups associated with the logical page. DATA SIZE = DW Number of bytes of data for this page. This field specifies the actual number of bytes that represent the page in the file. If the PAGE SIZE field from the module header is greater than the value of this field and the FLAGS field indicates a Legal Physical Page, the remaining bytes are to be filled with zeros. If the FLAGS field indicates an Iterated Data Page, the iterated data records will completely fill out the remainder. FLAGS = DW Attributes specifying characteristics of this logical page. The bit definitions for this word field follow, 00h = Legal Physical Page in the module (Offset from Preload Page Section). 01h = Iterated Data Page (Offset from Iterated Data Pages Section). 02h = Invalid Page (zero). 03h = Zero Filled Page (zero). 04h = Range of Pages. Resource Table The resource table is an array of resource table entries. Each resource table entry contains a type ID and name ID. These entries are used to locate resource objects contained in the Object table. The number of entries in the resource table is defined by the Resource Table Count located in the linear EXE header. More than one resource may be contained within a single object. Resource table entries are in a sorted order, (ascending, by Resource Name ID within the Resource Type ID). This allows the DosGetResource API function to use a binary search when looking up a resource in a 32-bit module instead of the linear search being used in the current 16-bit module. Each resource entry has the following format: +-----+-----+-----+-----+ 00h | TYPE ID | NAME ID | +-----+-----+-----+-----+ 04h | RESOURCE SIZE | +-----+-----+-----+-----+-----+-----+ 08h | OBJECT | OFFSET | +-----+-----+-----+-----+-----+-----+ TYPE ID = DW Resource type ID. The type of resources are: BTMP = Bitmap EMSG = Error message string FONT = Fonts NAME ID = DW An ID used as a name for the resource when referred to. RESOURCE SIZE = DD The number of bytes the resource consists of. OBJECT = DW The number of the object which contains the resource. OFFSET = DD The offset within the specified object where the resource begins. Resident or Non-resident Name Table Entry The resident and non-resident name tables define the ASCII names and ordinal numbers for exported entries in the module. In addition the first entry in the resident name table contains the module name. These tables are used to translate a procedure name string into an ordinal number by searching for a matching name string. The ordinal number is used to locate the entry point information in the entry table. The resident name table is kept resident in system memory while the module is loaded. It is intended to contain the exported entry point names that are frequently dynamicaly linked to by name. Non-resident names are not kept in memory and are read from the EXE file when a dynamic link reference is made. Exported entry point names that are infrequently dynamicaly linked to by name or are commonly referenced by ordinal number should be placed in the non-resident name table. The trade off made for references by name is performance vs memory usage. Import references by name require these tables to be searched to obtain the entry point ordinal number. Import references by ordinal number provide the fastest lookup since the search of these tables is not required. The strings are CASE SENSITIVE and are NOT NULL TERMINATED. Each name table entry has the following format: +-----+-----+-----+-----+ +-----+-----+-----+ 00h | LEN | ASCII STRING . . . | ORDINAL # | +-----+-----+-----+-----+ +-----+-----+-----+ LEN = DB String Length. This defines the length of the string in bytes. A zero length indicates there are no more entries in table. The length of each ascii name string is limited to 127 characters. The high bit in the LEN field (bit 7) is defined as an Overload bit. This bit signifies that additional information is contained in the linear EXE module and will be used in the future for parameter type checking. ASCII STRING = DB ASCII String. This is a variable length string with it's length defined in bytes by the LEN field. The string is case case sensitive and is not null terminated. ORDINAL # = DW Ordinal number. The ordinal number in an ordered index into the entry table for this entry point. Entry Table The entry table contains object and offset information that is used to resolve fixup references to the entry points within this module. Not all entry points in the entry table will be exported, some entry points will only be used within the module. An ordinal number is used to index into the entry table. The entry table entries are numbered starting from one. The list of entries are compressed into 'bundles', where possible. The entries within each bundle are all the same size. A bundle starts with a count field which indicates the number of entries in the bundle. The count is followed by a type field which identifies the bundle format. This provides both a means for saving space as well as a mechanism for extending the bundle types. The type field allows the definition of 256 bundle types. The following bundle types will initially be defined: Unused Entry. 16-bit Entry. 286 Call Gate Entry. 32-bit Entry. Forwarder Entry. The bundled entry table has the following format: +-----+-----+-----+-----+-----+ 00h | CNT |TYPE | BUNDLE INFO . . . +-----+-----+-----+-----+-----+ CNT = DB Number of entries. This is the number of entries in this bundle. A zero value for the number of entries identifies the end of the entry table. There is no further bundle information when the number of entries is zero. In other words the entry table is terminated by a single zero byte. TYPE = DB Bundle type. This defines the bundle type which determines the contents of the BUNDLE INFO. The follow types are defined: 00h = Unused Entry. 01h = 16-bit Entry. 02h = 286 Call Gate Entry. 03h = 32-bit Entry. 04h = Forwarder Entry. 80h = Parameter Typing Information Present. This bit signifies that additional information is contained in the linear EXE module and will be used in the future for parameter type checking. The following is the format for each bundle type: +-----+-----+ 00h | CNT |TYPE | +-----+-----+ CNT = DB Number of entries. This is the number of unused entries to skip. TYPE = DB 0 (Unused Entry) +-----+-----+-----+-----+ 00h | CNT |TYPE | OBJECT | +-----+-----+-----+-----+ 04h |FLAGS| OFFSET | +-----+-----+-----+ 07h | ... | . . . | + + + + CNT = DB Number of entries. This is the number of 16-bit entries in this bundle. The flags and offset value are repeated this number of times. TYPE = DB 1 (16-bit Entry) OBJECT = DW Object number. This is the object number for the entries in this bundle. FLAGS = DB Entry flags. These are the flags for this entry point. They have the following definition. 01h = Exported entry flag. F8h = Parameter word count mask. OFFSET = DW Offset in object. This is the offset in the object for the entry point defined at this ordinal number. +-----+-----+-----+-----+ 00h | CNT |TYPE | OBJECT | +-----+-----+-----+-----+-----+ 04h |FLAGS| OFFSET | CALLGATE | +-----+-----+-----+-----+-----+ 09h | ... | . . . | . . . | + + + + + + CNT = DB Number of entries. This is the number of 286 call gate entries in this bundle. The flags, callgate, and offset value are repeated this number of times. TYPE = DB 2 (286 Call Gate Entry) The 286 Call Gate Entry Point type is needed by the loader only if ring 2 segments are to be supported. 286 Call Gate entries contain 2 extra bytes which are used by the loader to store an LDT callgate selector value. OBJECT = DW Object number. This is the object number for the entries in this bundle. FLAGS = DB Entry flags. These are the flags for this entry point. They have the following definition. 01h = Exported entry flag. F8h = Parameter word count mask. OFFSET = DW Offset in object. This is the offset in the object for the entry point defined at this ordinal number. CALLGATE = DW Callgate selector. The callgate selector is a reserved field used by the loader to store a call gate selector value for references to ring 2 entry points. When a ring 3 reference to a ring 2 entry point is made, the callgate selector with a zero offset is place in the relocation fixup address. The segment number and offset in segment is placed in the LDT callgate. +-----+-----+-----+-----+ 00h | CNT |TYPE | OBJECT | +-----+-----+-----+-----+-----+ 04h |FLAGS| OFFSET | +-----+-----+-----+-----+-----+ 09h | ... | . . . | + + + + + + CNT = DB Number of entries. This is the number of 32-bit entries in this bundle. The flags and offset value are repeated this number of times. TYPE = DB 3 (32-bit Entry) The 32-bit Entry type will only be defined by the linker when the offset in the object can not be specified by a 16-bit offset. OBJECT = DW Object number. This is the object number for the entries in this bundle. FLAGS = DB Entry flags. These are the flags for this entry point. They have the following definition. 01h = Exported entry flag. F8h = Parameter dword count mask. OFFSET = DD Offset in object. This is the offset in the object for the entry point defined at this ordinal number. +-----+-----+-----+-----+ 00h | CNT |TYPE | RESERVED | +-----+-----+-----+-----+-----+-----+-----+ 04h |FLAGS| MOD ORD# | OFFSET / ORDNUM | +-----+-----+-----+-----+-----+-----+-----+ 09h | ... | ... | ... | + + + + + + + + CNT = DB Number of entries. This is the number of forwarder entries in this bundle. The FLAGS, MOD ORD#, and OFFSET/ORDNUM values are repeated this number of times. TYPE = DB 4 (Forwarder Entry) RESERVED = DW 0 This field is reserved for future use. FLAGS = DB Forwarder flags. These are the flags for this entry point. They have the following definition. 01h = Import by ordinal. F7h = Reserved for future use; should be zero. MOD ORD# = DW Module Ordinal Number This is the index into the Import Module Name Table for this forwarder. OFFSET / ORDNUM = DD Procedure Name Offset or Import Ordinal Number If the FLAGS field indicates import by ordinal, then this field is the ordinal number into the Entry Table of the target module, otherwise this field is the offset into the Procedure Names Table of the target module. A Forwarder entry (type = 4) is an entry point whose value is an imported reference. When a load time fixup occurs whose target is a forwarder, the loader obtains the address imported by the forwarder and uses that imported address to resolve the fixup. A forwarder may refer to an entry point in another module which is itself a forwarder, so there can be a chain of forwarders. The loader will traverse the chain until it finds a non-forwarded entry point which terminates the chain , and use this to resolve the original fixup. Circular chains are detected by the loader and result in a load time error. A maximum of 1024 forwarders is allowed in a chain; more than this results in a load time error. Forwarders are useful for merging and recombining API calls into different sets of libraries, while maintaining compatibility with applications. For example, if one wanted to combine MONCALLS, MOUCALLS, and VIOCALLS into a single libraries, one could provide entry points for the three libraries that are forwarders pointing to the common implementation. Module Format Directives Table The Module Format Directives Table is an optional table that allows additional options to be specified. It also allows for the extension of the linear EXE format by allowing additional tables of information to be added to the linear EXE module without affecting the format of the linear EXE header. Likewise, module format directives provide a place in the linear EXE module for 'temporary tables' of information, such as incremental linking information and statistic information gathered on the module. When there are no module format directives for a linear EXE module, the fields in the linear EXE header referencing the module format directives table are zero. Each Module Format Directive Table entry has the following format: +-----+-----+-----+-----+-----+-----+----+----+ 00h | DIRECT # | DATA LEN | DATA OFFSET | +-----+-----+-----+-----+-----+-----+----+----+ DIRECT # = DW Directive number. The directive number specifies the type of directive defined. This can be used to determine the format of the information in the directive data. The following directive numbers have been defined: 8000h = Resident Flag Mask. Directive numbers with this bit set indicate that the directive data is in the resident area and will be kept resident in memory when the module is loaded. 8001h = Verify Record Directive. (Verify record is a resident table.) 0002h = Language Information Directive. (This is a non-resident table.) 0003h = Co-Processor Required Support Table. 0004h = Thread State Initialization Directive. Additional directives can be added as needed in the future, as long as they do not overlap previously defined directive numbers. DATA LEN = DW Directive data length. This specifies the length in byte of the directive data for this directive number. DIRECTIVE OFFSET = DD Directive data offset. This is the offset to the directive data for this directive number. It is relative to beginning of linear EXE header for a resident table, and relative to the beginning of the EXE file for non-resident tables. Verify Record Directive Table The Verify Record Directive Table is an optional table. It maintains a record of the pages in the EXE file that have been fixed up and written back to the original linear EXE module, along with the module dependencies used to perform these fixups. This table provides an efficient means for verifying the virtual addresses required for the fixed up pages when the module is loaded. Each Verify Record entry has the following format: +-----+-----+ 00h |# OF ENTRY | +-----+-----+-----+-----+-----+-----+ 02h | MOD ORD # | VERSION | MOD # OBJ | +-----+-----+-----+-----+-----+-----+ 08h | OBJECT # | BASE ADDR | VIRTUAL | +-----+-----+-----+-----+-----+-----+ 0Eh | . . . | . . . | . . . | + + + + + + + # OF ENTRY = DW Number of module dependencies. This field specifies how many entries there are in the verify record directive table. This is equal to the number of modules referenced by this module. MOD ORD # = DW Ordinal index into the Import Module Name Table. This value is an ordered index in to the Import Module Name Table for the referenced module. VERSION = DW Module Version. This is the version of the referenced module that the fixups were originally performed. This is used to insure the same version of the referenced module is loaded that was fixed up in this module and therefore the fixups are still correct. This requires the version number in a module to be incremented anytime the entry point offsets change. MOD # OBJ = DW Module # of Object Entries. This field is used to identify the number of object verify entries that follow for the referenced module. OBJECT # = DW Object # in Module. This field specifies the object number in the referenced module that is being verified. BASE ADDR = DW Object load base address. This is the address that the object was loaded at when the fixups were performed. VIRTUAL = DW Object virtual address size. This field specifies the total amount of virtual memory required for this object. Per-Page Checksum The Per-Page Checksum table provides space for a cryptographic checksum for each physical page in the EXE file. The checksum table is arranged such that the first entry in the table corresponds to the first logical page of code/data in the EXE file (usually a preload page) and the last entry corresponds to the last logical page in the EXE file (usually a iterated data page). +-----+-----+-----+-----+ Logical Page #1 | CHECKSUM | +-----+-----+-----+-----+ Logical Page #2 | CHECKSUM | +-----+-----+-----+-----+ . . . +-----+-----+-----+-----+ Logical Page #n | CHECKSUM | +-----+-----+-----+-----+ CHECKSUM = DD Cryptographic checksum. Fixup Page Table The Fixup Page Table provides a simple mapping of a logical page number to an offset into the Fixup Record Table for that page. This table is parallel to the Object Page Table, except that there is one additional entry in this table to indicate the end of the Fixup Record Table. The format of each entry is: +-----+-----+-----+-----+ Logical Page #1 | OFFSET FOR PAGE #1 | +-----+-----+-----+-----+ Logical Page #2 | OFFSET FOR PAGE #2 | +-----+-----+-----+-----+ . . . +-----+-----+-----+-----+ Logical Page #n | OFFSET FOR PAGE #n | +-----+-----+-----+-----+ |OFF TO END OF FIXUP REC| This is equal to: +-----+-----+-----+-----+ Offset for page #n + Size of fixups for page #n OFFSET FOR PAGE # = DD Offset for fixup record for this page. This field specifies the offset, from the beginning of the fixup record table, to the first fixup record for this page. OFF TO END OF FIXUP REC = DD Offset to the end of the fixup records. This field specifies the offset following the last fixup record in the fixup record table. This is the last entry in the fixup page table. The fixup records are kept in order by logical page in the fixup record table. This allows the end of each page's fixup records is defined by the offset for the next logical page's fixup records. This last entry provides support of this mechanism for the last page in the fixup page table. Fixup Record Table The Fixup Record Table contains entries for all fixups in the linear EXE module. The fixup records for a logical page are grouped together and kept in sorted order by logical page number. The fixups for each page are further sorted such that all external fixups and internal selector/pointer fixups come before internal non-selector/non-pointer fixups. This allows the loader to ignore internal fixups if the loader is able to load all objects at the addresses specified in the object table. Each relocation record has the following format: +-----+-----+-----+-----+ 00h | SRC |FLAGS|SRCOFF/CNT*| +-----+-----+-----+-----+-----+-----+ 03h/04h | TARGET DATA * | +-----+-----+-----+-----+-----+-----+ | SRCOFF1 @ | . . . | SRCOFFn @ | +-----+-----+---- ----+-----+-----+ * These fields are variable size. @ These fields are optional. SRC = DB Source type. The source type specifies the size and type of the fixup to be performed on the fixup source. The source type is defined as follows: 0Fh = Source mask. 00h = Byte fixup (8-bits). 01h = (undefined). 02h = 16-bit Selector fixup (16-bits). 03h = 16:16 Pointer fixup (32-bits). 04h = (undefined). 05h = 16-bit Offset fixup (16-bits). 06h = 16:32 Pointer fixup (48-bits). 07h = 32-bit Offset fixup (32-bits). 08h = 32-bit Self-relative offset fixup (32-bits). 10h = Fixup to Alias Flag. When the 'Fixup to Alias' Flag is set, the source fixup refers to the 16:16 alias for the object. This is only valid for source types of 2, 3, and 6. For fixups such as this, the linker and loader will be required to perform additional checks such as ensuring that the target offset for this fixup is less than 64K. 20h = Source List Flag. When the 'Source List' Flag is set, the SRCOFF field is compressed to a byte and contains the number of source offsets, and a list of source offsets follows the end of fixup record (after the optional additive value). FLAGS = DB Target Flags. The target flags specify how the target information is interpreted. The target flags are defined as follows: 03h = Fixup target type mask. 00h = Internal reference. 01h = Imported reference by ordinal. 02h = Imported reference by name. 03h = Internal reference via entry table. 04h = Additive Fixup Flag. When set, an additive value trails the fixup record (before the optional source offset list). 08h = Reserved. Must be zero. 10h = 32-bit Target Offset Flag. When set, the target offset is 32-bits, otherwise it is 16-bits. 20h = 32-bit Additive Fixup Flag. When set, the additive value is 32-bits, otherwise it is 16-bits. 40h = 16-bit Object Number/Module Ordinal Flag. When set, the object number or module ordinal number is 16-bits, otherwise it is 8-bits. 80h = 8-bit Ordinal Flag. When set, the ordinal number is 8-bits, otherwise it is 16-bits. SRCOFF = DW/CNT = DB Source offset or source offset list count. This field contains either an offset or a count depending on the Source List Flag. If the Source List Flag is set, a list of source offsets follows the additive field and this field contains the count of the entries in the source offset list. Otherwise, this is the single source offset for the fixup. Source offsets are relative to the beginning of the page where the fixup is to be made. Note that for fixups that cross page boundaries, a separate fixup record is specified for each page. An offset is still used for the 2nd page but it now becomes a negative offset since the fixup originated on the preceding page. (For example, if only the last one byte of a 32-bit address is on the page to be fixed up, then the offset would have a value of -3.) TARGET DATA = Target data for fixup. The format of the TARGET DATA is dependent upon target flags. SRCOFF1 - SRCOFFn = DW[] Source offset list. This list is present if the Source List Flag is set in the Target Flags field. The number of entries in the source offset list is defined in the SRCOFF/CNT field. The source offsets are relative to the beginning of the page where the fixups are to be made. +-----+-----+-----+-----+ 00h | SRC |FLAGS|SRCOFF/CNT*| +-----+-----+-----+-----+-----+-----+ 03h/04h | OBJECT * | TRGOFF * @ | +-----+-----+-----+-----+-----+-----+ | SRCOFF1 @ | . . . | SRCOFFn @ | +-----+-----+---- ----+-----+-----+ * These fields are variable size. @ These fields are optional. OBJECT = D[B|W] Target object number. This field is an index into the current module's Object Table to specify the target Object. It is a Byte value when the '16-bit Object Number/Module Ordinal Flag' bit in the target flags field is clear and a Word value when the bit is set. TRGOFF = D[W|D] Target offset. This field is an offset into the specified target Object. It is not present when the Source Type specifies a 16-bit Selector fixup. It is a Word value when the '32-bit Target Offset Flag' bit in the target flags field is clear and a Dword value when the bit is set. +-----+-----+-----+-----+ 00h | SRC |FLAGS|SRCOFF/CNT*| +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+ 03h/04h | MOD ORD# *| PROCEDURE NAME OFFSET*| ADDITIVE * @ | +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | SRCOFF1 @ | . . . | SRCOFFn @ | +-----+-----+---- ----+-----+-----+ * These fields are variable size. @ These fields are optional. MOD ORD # = D[B|W] Ordinal index into the Import Module Name Table. This value is an ordered index in to the Import Module Name Table for the module containing the procedure entry point. It is a Byte value when the '16-bit Object Number/Module Ordinal' Flag bit in the target flags field is clear and a Word value when the bit is set. The loader creates a table of pointers with each pointer in the table corresponds to the modules named in the Import Module Name Table. This value is used by the loader to index into this table created by the loader to locate the referenced module. PROCEDURE NAME OFFSET = D[W|D] Offset into the Import Procedure Name Table. This field is an offset into the Import Procedure Name Table. It is a Word value when the '32-bit Target Offset Flag' bit in the target flags field is clear and a Dword value when the bit is set. ADDITIVE = D[W|D] Additive fixup value. This field exists in the fixup record only when the 'Additive Fixup Flag' bit in the target flags field is set. When the 'Additive Fixup Flag' is clear the fixup record does not contain this field and is immediately followed by the next fixup record (or by the source offset list for this fixup record). This value is added to the address derived from the target entry point. This field is a Word value when the '32-bit Additive Flag' bit in the target flags field is clear and a Dword value when the bit is set. +-----+-----+-----+-----+ 00h | SRC |FLAGS|SRCOFF/CNT*| +-----+-----+-----+-----+-----+-----+-----+-----+ 03h/04h | MOD ORD# *|IMPORT ORD*| ADDITIVE * @ | +-----+-----+-----+-----+-----+-----+-----+-----+ | SRCOFF1 @ | . . . | SRCOFFn @ | +-----+-----+---- ----+-----+-----+ * These fields are variable size. @ These fields are optional. MOD ORD # = D[B|W] Ordinal index into the Import Module Name Table. This value is an ordered index in to the Import Module Name Table for the module containing the procedure entry point. It is a Byte value when the '16-bit Object Number/Module Ordinal' Flag bit in the target flags field is clear and a Word value when the bit is set. The loader creates a table of pointers with each pointer in the table corresponds to the modules named in the Import Module Name Table. This value is used by the loader to index into this table created by the loader to locate the referenced module. IMPORT ORD = D[B|W|D] Imported ordinal number. This is the imported procedure's ordinal number. It is a Byte value when the '8-bit Ordinal' bit in the target flags field is set. Otherwise it is a Word value when the '32-bit Target Offset Flag' bit in the target flags field is clear and a Dword value when the bit is set. ADDITIVE = D[W|D] Additive fixup value. This field exists in the fixup record only when the 'Additive Fixup Flag' bit in the target flags field is set. When the 'Additive Fixup Flag' is clear the fixup record does not contain this field and is immediately followed by the next fixup record (or by the source offset list for this fixup record). This value is added to the address derived from the target entry point. This field is a Word value when the '32-bit Additive Flag' bit in the target flags field is clear and a Dword value when the bit is set. +-----+-----+-----+-----+ 00h | SRC |FLAGS|SRCOFF/CNT*| +-----+-----+-----+-----+-----+-----+ 03h/04h | ORD # * | ADDITIVE * @ | +-----+-----+-----+-----+-----+-----+ | SRCOFF1 @ | . . . | SRCOFFn @ | +-----+-----+---- ----+-----+-----+ * These fields are variable size. @ These fields are optional. ENTRY # = D[B|W] Ordinal index into the Entry Table. This field is an index into the current module's Entry Table to specify the target Object and offset. It is a Byte value when the '16-bit Object Number/Module Ordinal' Flag bit in the target flags field is clear and a Word value when the bit is set. ADDITIVE = D[W|D] Additive fixup value. This field exists in the fixup record only when the 'Additive Fixup Flag' bit in the target flags field is set. When the 'Additive Fixup Flag' is clear the fixup record does not contain this field and is immediately followed by the next fixup record (or by the source offset list for this fixup record). This value is added to the address derived from the target entry point. This field is a Word value when the '32-bit Additive Flag' bit in the target flags field is clear and a Dword value when the bit is set. Import Module Name Table The import module name table defines the module name strings imported through dynamic link references. These strings are referenced through the imported relocation fixups. To determine the length of the import module name table subtract the import module name table offset from the import procedure name table offset. These values are located in the linear EXE header. The end of the import module name table is not terminated by a special character, it is followed directly by the import procedure name table. The strings are CASE SENSITIVE and NOT NULL TERMINATED. Each name table entry has the following format: +-----+-----+-----+-----+ +-----+ 00h | LEN | ASCII STRING . . . | +-----+-----+-----+-----+ +-----+ LEN = DB String Length. This defines the length of the string in bytes. The length of each ascii name string is limited to 127 characters. ASCII STRING = DB ASCII String. This is a variable length string with it's length defined in bytes by the LEN field. The string is case sensitive and is not null terminated. Import Procedure Name Table The import procedure name table defines the procedure name strings imported by this module through dynamic link references. These strings are referenced through the imported relocation fixups. To determine the length of the import procedure name table add the fixup section size to the fixup page table offset, this computes the offset to the end of the fixup section, then subtract the import procedure name table offset. These values are located in the linear EXE header. The import procedure name table is followed by the data pages section. Since the data pages section is aligned on a 'page size' boundary, padded space may exist between the last import name string and the first page in the data pages section. If this padded space exists it will be zero filled. The strings are CASE SENSITIVE and NOT NULL TERMINATED. Each name table entry has the following format: +-----+-----+-----+-----+ +-----+ 00h | LEN | ASCII STRING . . . | +-----+-----+-----+-----+ +-----+ LEN = DB String Length. This defines the length of the string in bytes. The length of each ascii name string is limited to 127 characters. The high bit in the LEN field (bit 7) is defined as an Overload bit. This bit signifies that additional information is contained in the linear EXE module and will be used in the future for parameter type checking. ASCII STRING = DB ASCII String. This is a variable length string with it's length defined in bytes by the LEN field. The string is case sensitive and is not null terminated. Preload Pages The Preload Pages section is an optional section in the linear EXE module that coalesces a 'preload page set' into a contiguous section. The preload page set can be defined as the set of first used pages in the module. The preload page set can be specified by the application developer or can be derived by a tool that analyzes the programs memory usage while it is running. By grouping the preload page set together, the preload pages can be read from the linear EXE module with one disk read. The structure of the preload pages is no different than if they were demand loaded. They are non-iterated pages. Their sizes are determined by the Object Page Table entries that correspond. If the specified size is less than the PAGE SIZE field given in the linear EXE module header the remainder of the page is filled with zeros when loaded. All pages begin on a PAGE OFFSET SHIFT boundary from the base of the preload page section, as specified in the linear EXE header. The pages are ordered by logical page number within this section. Note: OS/2 2.0 does not respect preload pages. Performance tests showed that better system performance was obtained by not respecting the preload request in the executable file. Demand Load Pages The Demand Loaded Pages section contains all the non-iterated pages for a linear EXE module that are not preloaded. When required, the whole page is loaded into memory from the module. The characteristics of each of these pages is specified in the Object Page Table. Every page begins on a PAGE OFFSET SHIFT boundary aligned offset from the demand loaded pages base specified in the linear EXE header. Their sizes are determined by the Object Page Table entries that correspond. If the specified size is less than the PAGE SIZE field given in the linear EXE module header the remainder of the page is filled with zeros when loaded. The pages are ordered by logical page number within this section. Iterated Data Pages The Iterated Data Pages section contains all the pages for a linear EXE module that are iterated. When required, the set of iteration records are loaded into memory from the module and expanded to reconstitute the page. Every set of iteration records begins on a PAGE OFFSET SHIFT offset from the OBJECT ITER PAGES OFF specified in the linear EXE header. Their sizes are determined by the Object Page Table entries that correspond. The pages are ordered by logical page number within this section. This record structure is used to describe the iterated data for an object on a per-page basis. +-----+-----+-----+-----+ 00h |#ITERATIONS|DATA LENGTH| +-----+-----+-----+-----+-----+ 04h |DATA BYTES | . . . | ... | +-----+-----+-----+-----+-----+ Figure 19. Object Iterated Data Record (Iteration Record) #ITERATIONS = DW Number of iterations. This specifies the number of times that the data is replicated. DATA LENGTH = DW The size of the data pattern in bytes. This specifies the number of bytes of data of which the pattern consists. The maximum size is one half of the PAGE SIZE (given in the module header). If a pattern exceeds this value then the data page will not be condensed into iterated data. DATA = DB * DATA LENGTH The Data pattern to be replicated. The next iteration record will immediately follow the last byte of the pattern. The offset of the next iteration record is easily calculated from the offset of this record by adding the DATA LENGTH field and the sizes of the #ITERATIONS and DATA LENGTH fields. Debug Information The debug information is defined by the debugger and is not controlled by the linear EXE format or linker. The only data defined by the linear EXE format relative to the debug information is it's offset in the EXE file and length in bytes as defined in the linear EXE header. To support multiple debuggers the first word of the debug information is a type field which determines the format of the debug information. 00h 01h 02h 03h 04h +-----+-----+-----+-----+-----+-----+-----+-----+ | 'N' | 'B' | '0' | n | DEBUGGER DATA . . . . +-----+-----+-----+-----+-----+-----+-----+-----+ TYPE = DB DUP 4 Format type. This defines the type of debugger data that exists in the remainder of the debug information. The signature consists of a string of four (4) ASCII characters: "NB0" followed by the ASCII representation for 'n'. The values for 'n' are defined as follows. These format types are defined. 00h = 32-bit CodeView debugger format. 01h = AIX debugger format. 02h = 16-bit CodeView debugger format. 04h = 32-bit OS/2 PM debugger (IBM) format. DEBUGGER DATA = Debugger specific data. The format of the debugger data is defined by the debugger that is being used. The values defined for the type field are not enforced by the system. It is the responsibility of the linker or debugging tools to follow the convention for the type field that is defined here.
Category Archives: os/2
OS/2 Pages
JFS Boot Block :: Bochs Adventure
So, getting a MBR and os2boot on a hard drive image was a real pain. None of the current CDs would boot to a point where I could get the job done. I resorted to doing it the hard way. I mounted an old 630 meg IDE hard drive (just having this around should label me a packrat) and booted the system with an eCS 2.0 RC4 CDROM. Using the eCS tools I made a compatible partition which was bootable and wrote the MBR. Next, I did a long JFS format on the partition which completed but with errors toward the end of the format process.
eax: 0x0000aa55 43605 ecx: 0x00000000 0 edx: 0x00000080 128 ebx: 0x00000000 0 esp: 0x0000ffdc 65500 ebp: 0x00000000 0 esi: 0xffff0000 -65536 edi: 0x0008fdba 589242 eip: 0x00007c00 eflags 0x00000082 IOPL=0 id vip vif ac vm rf nt of df if tf SF zf af pf cf cs:s=0x0000, dl=0x0000ffff, dh=0x00009b00, valid=1 ds:s=0x0000, dl=0x0000ffff, dh=0x00009300, valid=1 ss:s=0x0000, dl=0x0000ffff, dh=0x00009300, valid=7 es:s=0x0000, dl=0x0000ffff, dh=0x00009300, valid=1 fs:s=0x0000, dl=0x0000ffff, dh=0x00009300, valid=1 gs:s=0x0000, dl=0x0000ffff, dh=0x00009300, valid=1 ldtr:s=0x0000, dl=0x00000000, dh=0x00000000, valid=0 tr:s=0x0000, dl=0x00000000, dh=0x00000000, valid=0 gdtr:base=0x000faeb2, limit=0x30 idtr:base=0x00000000, limit=0x3ff
Phase 1 — The MBR
In brief, control is passed from BIOS to the loaded MBR code at 07C0:0000. The code relocates itself to 07E0:0000, checks for a Boot Manager partition, and checks for a bootable partition. Once a bootable partition is found, the partition boot record or more correctly the BIOS parameter block (BPB) is loaded at 07C0:0000 and control is passed to it at 07C0:0000.
Remember, as I understand the MBR is created by the LVM utility (ex: LVM /NEWMBR). At this point the system is using INT13 calls to interface with the drive. The errors that can be encountered during this phase are:
- SYS01462
- SYS01463
- SYS01464
More detailed information can be found here:
Phase 2 — The Partition Boot Block
This code uses INT 12 to, in general, find the top of low memory. Next, the result (approx 639) has 54h subtracted, AND result with FFF0h, and then shifted left 6 bits. This will be the load segment for the next code file (ie calculated load segment). The Bochs drive returns 639 from the INT12 call, so the load segment is 8800h.
All disk access during this phase uses INT13 calls to interface with the drive. After loading the next module a far return to (calculated load segment):199C is performed. This code is placed in the partition when the sysinstx.com file is run against it. So, the sysinstx.com executable writes the partition sector/micro-FSD and os2boot.
The errors that can be encountered during this phase are:
- an error when performing the next module load (INT13 Ext) which results in an emission of DAP information, – SYS2027 – message, and the system hangs. Getting this error is an indication of a failed INT13 load from the procedure readdrive which is also used in Phase 3.
- ‘Invalid code for JFS’ (followed by address code) which is emitted after the next module is loaded and a check for the signature 1961h at (calculated load segment):0200 fails. For normal operation I assume this error would be an indication that os2boot could not be loaded and possibly running sysinstx.com against the partition could correct it.
The module loaded in Phase 2 contains code to continue the boot process and the micro-FSD. Using the Bochs drive image, 20h sectors are loaded at 8800:0000 and the entry point is 8800:199C.
More detailed information can be found here:
Bootable JFS Bootsector Code (BPB)
Phase 3 — The Partition Boot Block Plus Extra (micro-FSD)
After a bit of playing, I found that the load in Phase 2 does indeed reload the Partition Boot Block and an additional 31 sectors at 0x88000. Looking at the code and stepping through execution with Bochs, I have confirmed this to be correct.
I am starting to see the structure of the Phase 3 load and it does not seem as documented. I believe this module loads os2boot and it only contains the mini-file system. It next loads os2ldr, sets up the required structures and then jumps into os2ldr.
I’m going to post my notes to the blog, link at top of the page.
PSD related information from the SMP Programming Addendum
1.0 Platform Specific Drivers (PSDs)
In OS/2 Warp Server for SMP, all of the platform specific code has been removed from the operating system, and placed into a Platform Specific Driver. These drivers provide an abstraction layer for the underlying hardware by allowing the operating system to call generic functions to perform platform-specific operations without worrying about the actual hardware implementation. This allows OS/2 Warp Server for SMP to support new MP hardware platforms without modifying the operating system.
PSDs are 32-bit flat DLLs specified in CONFIG.SYS by using the PSD= keyword , and must conform to the 8.3 file naming convention (e.g. PSD=BELIZE.PSD). They cannot contain either drive or path information because OS/2 cannot process such information at the stage of the startup sequence when the PSD statements are processed. The root directory of the startup partition is first searched for the specified file name, followed by the \OS2 directory of the startup partition. If drive or path information is included in a PSD statement, an error is generated.
PSD parameters may be specified after the PSD’s name, and may be a maximum of 1024 characters long. The parameter string is not interpreted or parsed by OS/2, but is passed verbatim as an ASCIIZ string when the PSD’s Install function is invoked.
If multiple PSD statements are encountered, OS/2 will load each PSD in the order listed in CONFIG.SYS, and call the PSD’s install function. The first PSD which successfully installs will be the one OS/2 uses.
PSD statements are processed before BASEDEV, IFS, and DEVICE statements.
2.0 Platform Specific Driver Architecture and Structure
The PSD operates in three contexts (modes): Kernel, Interrupt, and Init.
2.1 Kernel Mode
The OS/2 kernel calls the PSD for task-time operations, that is, it will execute as a thread within a process. Kernel mode is also referred to as the task context.
2.2 Interrupt Mode
The OS/2 kernel calls the PSD for interrupt-time operations. Interrupt time is a generic term that refers to executing code as a result of a hardware interrupt. The code does not execute as a thread belonging to a process.
2.3 Init Mode
The PSD is currently being used for system initialization. A limited set of PSD helps are available for use.
PSDs may contain multiple code and data objects. All objects will be fixed (not-swappable or movable) in low physical memory, with virtual addresses in the system arena. Objects are loaded in low physical memory to facilitate the use of real mode or bi-modal code. All objects default to permanent, which means that they remain in the system after initialization is completed. The SEGMENTS directive and the IOPL option in the linker DEF file should be used to mark those objects that are not to be kept after initialization.
The multitasking/multiprocessing environment of OS/2 Warp Server for SMP dictates that the PSD must be capable of handling multiple requests simultaneously. This means that global variables should be used sparingly. Upon PSD installation, the kernel passes a pointer to a small area of processor local memory (PLMA) which the PSD developer can use to store variables. This PLMA area is currently 256 bytes in size. IBM reserves the right to increase the size of this area in future releases of OS/2, though the minimum available will always be 256 bytes. The size of the PLMA is passed in the same structure as the pointer to the PLMA from the PSDINSTALL export.
PSD developers must be aware of the effects of the calls they make, because there is no guarantee that if an operation is started on a processor, and execution blocks, that the operation will continue on the same processor. OS/2 does not preempt a thread in the PSD, but it may block as a result of using a PSD help, or it may be interrupted by a hardware interrupt.
PSDs can register an interrupt handler for any given IRQ level using the SET_IRQ PSD help. These interrupt handlers are guaranteed to be called before any device driver’s interrupt handler. If the PSD’s interrupt handler returns NO_ERROR, the interrupt manager will assume the interrupt has been handled, it will end the interrupt. If a -1 is returned, the interrupt manager will assume that the interrupt has not been handled, and will call each device driver which has a registered interrupt handler for that particular level until one claims the interrupt. If the interrupt is unclaimed, the IRQ level will be masked off.
All PSDs must use the SET_IRQ PSD help to indicate which IRQ level they will be using for inter-processor interrupts (IPI). If the PSD’s IPI IRQ level is shared, it must register a handler which detects if the IRQ is an IPI or another interrupt. The handler must return NO_ERROR if the interrupt was caused by an IPI, otherwise it returns a -1. If the IPI IRQ level is unique, an interrupt handler need not be installed but SET_IRQ must still be used to indicate which is the IPI IRQ level.
The kernel will save the state of all the registers (except EAX) around calls to the PSD functions. All the functions will run at Ring 0. Upon invocation, SS, DS, and ES will be flat. The PSD functions must conform to the C calling convention. They receive parameters on the stack (4 bytes per parameter), and must return a return code in EAX.
The PSD functions have been split into three categories:
- Functions that the PSD must have for OS/2 to operate (required functions)
- Functions that the PSD does not need to have (optional functions)
- Functions that the PSD must have for OS/2 to use multiple processors (MP functions).
The kernel provides default handling for some of the PSD functions. PSD functions can also chain to a kernel default handler by returning a -1 return code. If a return code other than -1 is returned by a PSD function, the default handler will not get called. The PSD function glossary later in this chapter details the categories of all the functions, as well as any default handlers they may have.
The PSD developer makes functions available to OS/2 by using the EXPORTS statement in the module definition (DEF) file. All functions should be exported using upper case with no leading underscores (_). An example is shown below.
LIBRARY TESTPSD
EXPORTS
PSD_INSTALL = _Install
PSD_DEINSTALL = _DeInstall
Note: Needs updating with Open Watcom instructions.
The initial CS and EIP in the PSD’s executable image is ignored. The image should also not contain a stack object. OS/2 allocates a per-processor PSD stack and sets SS and ESP correctly before invoking any of the PSD functions.
PSDs should be written in flat 32-bit code, using C, and must be linked as a LIBRARY.
OS/2 invokes all PSD functions in protect mode, but there is also a PSD help which allows the PSD developer to call a PSD function in real mode.
OS/2 services are provided through the PSD help interface. Access to these services are obtained upon PSD installation. PSD helpers preserve all registers except EAX.
All the definitions (e.g. defines, structures, etc.) that are required for building a PSD are in the header file PSD.H.
3.0 OS/2 Initialization
OS/2 requires a PSD for system initialization. The system will display an error message if a valid PSD for the current platform cannot be installed.
The following is a list of steps, in the order in which they occur, that are executed after a PSD is installed. If any step does not complete successfully, the system initialization process will stop, and a fatal error message will be displayed.
- After a PSD is successfully installed, its Init function is invoked. This function is used to allocate and initialize any resources that the PSD may require, as well as initializing the state of the hardware.
- The kernel determines the number of usable processors on the current platform by using the PSD_GET_NUM_OF_PROCS function.
- The kernel allocates all resources required to support the additional processors. This step determines what to allocate based on the results of the previous step.
- The PSD’s processor initialization function is invoked on the current processor (CPU0).
- An MP daemon is created for CPU0. An MP daemon is a thread that never goes away, which is used for MP operations by a specific processor.
- An MP daemon is created for the next logical processor.
- The PSD’s start processor call is invoked to start the next logical processor. The PSD should only start the specified processor, and then return (see the PSD_START_PROC function for more detail). The started processor will spin in a tight loop waiting for a variable to be cleared. This variable is referred to as the processor initialization real mode spinlock.
- Upon return from the PSD’s start processor call, the processor initialization real mode spinlock is cleared.
- CPU0 will spin in a tight loop waiting for a variable to be cleared. This variable is referred to as the CPU0 spinlock.
- The started processor continues execution of the kernel’s real mode processor initialization code now that processor’s initialization real mode spinlock has been cleared.
- The started processor sets up all protect mode and paging information, and switches into protect mode with paging enabled.
- Up to this point, the started processor has been running on a small processor initialization stack (It has not been running as an OS/2 thread). The current context is switched to that of this processors MP daemon.
- OS/2 calls the PSD’s processor initialization function for the current processor.
- The PSD indicates that the processor has been initialized.
- The started processor will spin in a tight loop waiting for a variable to be cleared. This variable is referred to as the processor initialization protect mode spinlock.
- The CPU0 spinlock is cleared.
- System initialization continues on CPU0 now that its spinlock has been cleared.
- Steps 6, through 17 are repeated until all processors have been started.
- The rest of system initialization continues normally, on CPU0.
- After the system is fully initialized, the processor initialization protect mode spinlock is cleared. This allows CPU1 through CPU-N to start executing code.
Original posted: 10/10/2009
os2.h
/*DDK*************************************************************************/
/* */
/* COPYRIGHT Copyright (C) 1992 IBM Corporation */
/* */
/* The following IBM OS/2 source code is provided to you solely for */
/* the purpose of assisting you in your development of OS/2 device */
/* drivers. You may use this code in accordance with the IBM License */
/* Agreement provided in the IBM Developer Connection Device Driver */
/* Source Kit for OS/2. This Copyright statement may not be removed. */
/* */
/*****************************************************************************/
/*static char *SCCSID = "@(#)os2.h 6.3 91/05/26";*/
/****************************** Module Header ******************************\
*
*
* Module Name: OS2.H
*
* This is the top level include file that includes all the files necessary
* for writing an OS/2 application.
*
\***************************************************************************/
#define OS2_INCLUDED
#if !(defined(INCL_32) || defined(INCL_16))
#ifdef M_I386
#define INCL_32
#else /* not M_I386 */
#define INCL_16
#endif /* M_I386 */
#endif /* INCL_32 || INCL_16 */
/* XLATOFF */
#if (defined(INCL_32) && defined(INCL_16))
#error message ("Illegal combination of API Flags - 32 && 16")
#endif /* INCL_32 && INCL_16 */
/* XLATON */
/* Common definitions */
#include
/* OS/2 Base Include File */
#ifndef INCL_NOBASEAPI
#include
#endif /* INCL_NOBASEAPI */
/* OS/2 Presentation Manager Include File */
#ifndef INCL_NOPMAPI
#include
#endif /* INCL_NOPMAPI */
SOME ASSEMBLY REQUIRED: OS/2 Device Drivers
Contents
A practitioner’s guide to development of an asynchronous RS-232 terminal driver for OS/2 in C
AUTHOR: Steven J. Mastrianni
NOTE: I found this on the author’s site and is a Byte Article from 1991. Reprinted here without permission. M Greene
OS/2 device drivers continue to be a limiting factor in the acceptance and use of OS/2. DOS drivers abound, but OS/2 drivers are scarce as hen’s teeth — for a variety of reasons. OS/2 drivers are more complicated than DOS drivers. They’ve got to handle context switching and priorities and accommodate dual-mode operation (real versus protected) — issues foreign to many DOS programmers. In this article, I’ll describe how to build an asynchronous RS-232 terminal driver for OS/2 in C, complete with interrupt handler and timer support (the code you’ll need to build this driver is available on BIX). Once you’ve seen how that’s done, you’ll have the basic understanding you need to write OS/2 drivers for other types of devices.
The Nature of the Beast
OS/2 device drivers, like other multitasking drivers, shield applications from the physical characteristics of I/O devices (e.g., timing or I/O port addressing). An application in need of I/O service transmits a request to the OS/2 kernel, which in turn calls a driver. The device driver handles all the hardware details, such as register setup, interrupt handling, and error checking. When the request is complete, the device driver massages the data into a format recognizable by the application. It sends the data or a status indication to the application and notifies the kernel that the request is complete. If the request cannot be handled immediately, the driver may either block the requesting thread or return a Request Not Done status to the kernel. Either way, the driver then relinquishes the CPU so that other threads can run.
DOS device drivers do not have a direct OS/2 counterpart. They are simple single-task polling drivers. Even interrupt drivers under DOS poll until interrupt processing is complete. DOS device drivers support one request at a time, and any subsequent requests from the DOS kernel will cause the system to crash.
In contrast, an OS/2 driver must manage overlapping requests from different processes and threads, and it must therefore be reentrant. It must also handle interrupts from the device and interrupts from a timer handler. In addition, the OS/2 driver must oversee switches from protected mode to real mode. It must accomplish these operations in an efficient manner, allowing other threads to gain access to the CPU, and, most important, it must do all these tasks reliably. Because it operates at ring 0, the OS/2 driver is the only program that has access to critical system functions (e.g., the interrupt system and timer). The driver therefore must be a trusted program, because any error in the driver can cause a fatal system crash.
OS/2 device drivers must also be bimodal, which means they must operate in real mode and protected mode. The interrupts must continue to be processed, and the requests must be completed, even if the user switches from the OS/2 prompt to the DOS compatibility box and back. They must be able to deinstall when requested, releasing any memory used by the driver to OS/2. Additionally, OS/2 drivers may support device monitors, programs that monitor data as it is passed to and from the driver. Fortunately, OS/2 offers a wide range of system services called Device Helper routines, or DevHlps, to provide this functionality.
Tools of the Trade
Designing an OS/2 device driver requires a thorough understanding of the role of a device driver, as well as a solid working knowledge of the OS/2 operating system and design philosophy. Debugging OS/2 drivers can be difficult, even with the proper tools. The OS/2 device driver operates at ring 0 with full access to the system hardware. However, it has almost no access to OS/2 support services, except a handful of DevHlp routines. Many driver failures occur in a real-time context, such as in the midst of interrupt handling. It may be difficult or impossible to find a driver problem using normal debugging techniques. In such cases, it is necessary to visualize the operation of the device driver and OS/2 at the time of the error to help locate the problem.
The most important tool for driver development is the driver debugger. Generally, I use the kernel debugger from Microsoft, which comes with the Device Driver Development Toolkit, or DDK. Several other companies offer good driver development tools. A more complete version of this article in book form and a complete C-callable DevHlp library can be purchased from PSS. PentaSoft offers a C-callable interface to the DevHlp routines. OS Technologies offers a driver debugger that is OS/2 version-independent. And FutureWare offers a driver debugger and a C-callable interface to the DevHlp routines.
I write all my device drivers, including the interrupt and timer handlers, in Microsoft C 6.0. A device driver written in C can be written in approximately half the time it would take to write the same driver with the Microsoft Macro Assembler. In special cases, especially when writing drivers for very fast devices or where performance is extremely critical, it only makes sense to write a few subroutines in assembly language. Mostdrivers, however, work fine when written in C.
Anatomy of an OS/2 Device Driver
OS/2 drivers receive requests from the OS/2 kernel. When the driver is originally opened with a DosOpen call, the kernel returns a handle to the program that requested access to the driver. This handle is used for subsequent access to the driver, and the driver name is no longer used (or needed).
When an application makes a call to a driver, the kernel intercepts the call and formats the driver request in a standard driver data structure, called the request packet. The request packet contains the data and pointers that the driver uses to honor the request. In the case of a DosRead or DosWrite, for example, the request packet contains the physical address of the caller’s buffer. In the case of an I/O control operation (IOCtl), the request packet contains the virtual address of a data and parameter buffer. Depending on the request, the data in the request packet will change, but the length and format of the request packet’s header remain constant. The kernel passes the driver a bimodal pointer to the request packet. This bimodal, or tiled, address is a pointer valid in either protected mode
or real mode, because the processor may be in either mode when the driver is called.
How does the kernel know which driver to send the request to? Drivers are loaded by the OS/2 initialization code at boot time, and the kernel keeps a list of the installed drivers by name. Before a driver is used, it must be DosOpened from the application. The DosOpen specifies an ASCII-Z string with the device name as a parameter. The kernel compares this name with its list of installed drivers, and if it finds the name, it calls the Open section of the driver Strategy section to open the device. If that operation succeeds, the kernel returns a handle to the application to use for future driver access. The ASCII-Z name is never used again while the device remains open. The device handles are usually assigned sequentially, starting with 3 (0, 1, and 2 are claimed by OS/2). However, the handle value should never be assumed. The ASCII-Z device name is located in the device driver header.
The OS/2 Request Packet
An OS/2 device driver consists of a Strategy section and optional Interrupt and Timer sections. The Strategy section receives requests from the kernel in the form of a request packet. The Strategy section verifies the request and, if possible, completes the request and sends
the result back to the kernel. If the request cannot be completed immediately, the driver optionally queues up the request to be completed at a later time and starts the I/O operation if necessary. The kernel calls the Strategy section directly by finding its offset address in the device header.
The first entry in the request packet is the request-packet length, filled in by the kernel. The second parameter is the unit code. When a driver supports multiple logical units, the value stored here selects among them. The third field is the command code. The command code is filled in by the kernel. This is the code used by the switch statement in the Strategy section to decode the type of request from the kernel. The next field is the status word returned to the kernel. This field will contain the result of the driver operation along with the Done bit to notify the kernel the request is complete (this is not always the case; the driver may return without the Done bit set). To make things easier, I use a union to access specific types of requests and place the request-packet structures in an include file.
Building the Device Header
A simple OS/2 device driver consists of one code segment and one data segment, although more memory can be allocated if necessary (by means of DevHlp routines). The first data that appears in the data segment must be the device-driver header.
The device-driver header is a fixed-length, link-list structure that contains information for use by the kernel during INIT and normal operation. The first entry in the header is a link pointer to the next device the driver supports. If no other devices are supported, the pointer is set to -1L. This terminates the list of devices supported by this driver. If the driver supports multiple devices, such as a four-port serial board or multiple-disk controller, the link is a far pointer to the next device header.
The next entry in the device header is the attribute word, followed by a one-word offset to the driver Strategy section. Only the offset is necessary, because the driver is written in the small model with a 64-kilobyte code segment and a 64-KB data segment (this is not always true; in special cases, the driver can allocate more code and data space if needed).
The succeeding entry is an offset address to an interdriver communications routine if the driver supports IDC. (The DAW_IDC bit in the device attribute word must also be set; otherwise, the AttachDD call from the other driver will fail.)
The last field is the device name, which must be eight characters in length. Names with fewer than eight characters must be padded with blanks. Remember, any mistake in coding the device-driver header will cause an immediate crash and burn when booting.
Providing a Register Interface to the C Driver OS/2 device drivers are normally written in C, using the small model, which means 64 KB of data and 64 KB of code (code and data space may be increased in special cases). The driver .SYS file must load the data segment before the code segment. When you write an OS/2 driver in C, you must provide a mechanism for putting the code and data segments in the proper order, and you must also provide a low-level interface
to handle device and timer interrupts. Because the device header must be the first item that appears in the data segment, you have to prevent the C compiler from inserting the C start-up code before the device header. You may also have to provide a method of detecting which device is being requested for drivers that support multiple devices. The small assembly language program in listing 4 takes care of these requirements. The _acrtused entry point prevents the C start-up code from being inserted before the driver data segment. The segment-ordering directives ensure that the data segment precedes the code segment.
Note the _STRAT entry point. How does this get called? Remember, this is the address that is placed in the driver’s data-segment device header. The kernel, when making a request to the driver, looks up this address in the device header and makes a far call to it. The assembly language routine then calls the C mainline. Thus, the linkage from the kernel to the driver is established.
Why is there a push 0 at the beginning of the _STRAT routine? That’s the device number. Each device supported by the device driver requires a separate device header, and each device header contains an offset address to its own Strategy section. Using the assembly language interface, the routine pushes the device number on the stack and passes it to the driver Strategy section for service.
The Strategy Section
The Strategy section is nothing more than a big switch statement. Common driver requests, such as DosWrite and DosRead, have standard function and return codes. The driver may ignore any or all of these requests by returning a Done status to the kernel. This tells the kernel that the request has been completed. The status returned to the kernel can also include error information that the kernel returns to the calling program.
Note that in the case of a standard driver function, the kernel will map the error value returned from the driver to one of the standard return codes. It is therefore impossible to pass any special return codes to the application via a standard driver request. If you attempt to do so, the kernel will intercept the special return code and map it to one of the standard return codes. The only way to return a special code to the application is by means of an IOCtl request. IOCtls are
used for special driver-defined operations (e.g., port I/O). IOCtls are accessed when the application issues a DosDevIOCtl call with the driver’s handle. This flexibility allows the driver writer to customize the device driver to fit any device. For instance, if you had a serial driver that monitored bus traffic and reported the
occurrence of one or more special characters, you could use an IOCtl read and pass back the character in the return code.
Listing 5 shows the skeleton of a Strategy section. Note the switch on the request-packet command. Several standard driver functions have command codes predefined in OS/2. The driver writer can act on or ignore any of the requests to the driver. Although it would not make sense, the driver could ignore the Open command, issued by the kernel in response to a DosOpen call. Or, more logically, the driver can refuse to be deinstalled by rejecting a Deinstall request.
The INIT call is made only once, during system loading in response to a DEVICE= in CONFIG.SYS. The call is made in the INIT mode from ring 3, but with I/O privileges. The INIT routine is where you would insert the code to initialize your device, such as configuring a UART or sending a disk to track 0.
The very first thing you must do in the initialization code is to save the DevHlp entry-point address in the driver’s data segment. This is the only time the address is valid. It must be saved, or it is lost forever. The address of the DevHlp entry point is passed in the INIT request packet. The initialization code performs two other functions. First, it issues the sign-on message to the screen that the driver is attempting to load. Second, it finds the segment address of the last data and last code item, and it sends them back to OS/2. OS/2 uses the code- and data-segment values to size memory. If a driver fails installation, it must send back zeroes for the CS and DS registers so that OS/2 can use the memory space it occupied.
One of the most common techniques in OS/2 driver design is for the Strategy section to request service from the device and wait for a device or timer interrupt to signal completion of the request. The fragment in listing 6 shows an implementation of this scheme for the Read function of my sample serial communications driver. In this case, the Strategy section starts the I/O and issues a Block DevHlp call, which blocks the calling thre
ad. When the device interrupt signals that the operation is done, the interrupt section runs the blocked thread, completing the request. To protect against the request’s never being completed (e.g., in the case of a down device), the Block call can contain a time-out parameter. If the time expires before the completion interrupt occurs, the Strategy section can end the proper error back to the kernel.
Another way to time-out a device is to use the SetTimer DevHlp routine. You can attach a timer handler to the OS/2 system clock and have the handler run the blocked thread after a specified number of ticks.
The commands allowed by the Strategy section are up to the device driver writer. You can process only the commands you wish to act on and let the others simply pass by sending a Done status back to the kernel. You may instead wish to trap the illegal function calls and return an ERROR_BAD_COMMAND message to the kernel. Keep in mind, however, that the kernel frequently issues its own commands to the driver without your knowledge. For example, when the user of the application that opened the driver types a Control-C, the kernel checks the application’s list of open drivers and issues a Close request to each one. In general, I’ve found it easier to ignore all the requests I’m not waiting for and just flag them as done.
In the simplest of drivers, the Strategy section can only contain an Open, Close, and Read or Write request. In a complicated driver, such as a disk driver, the Strategy section may contain over two dozen standard driver functions and several additional IOCtl calls. IOCtl calls are actually Strategy functions, but they are broken down one step further to provide more detailed or device-specific operations. For instance, a driver might send a list of parameters to an I/O port to initialize it and return the input value of a status port with the status of the initialization.
A Sampler of Standard Driver Functions
INIT (code 0x00). This function is called by the kernel during driver installation at boot time. The INIT section should initialize your device, such as setting the baud rate, parity, stop bits, and so forth on a serial port or checking to see if the device is installed by issuing a status request to the device controller. This INIT function is called in a special mode in ring 3 with some ring 0 capabilities.
The driver may turn off interrupts, but they must be turned back on before returning to the kernel. The INIT code may perform direct port I/O without protection violations. Usually, the driver writer will allocate buffers and data storage during initialization, to be sure the driver will work when installed. Because the initialization is being performed in ring 3, the system can check to make sure the buffer and storage allocations are valid and the segments are owned by the driver. If not, the driver can remove itself from memory, freeing up any previously allocated space for other system components or another driver. Because initialization is done only once during system boot-up, it is not critical to optimize the section. Do all your initializations here, as it may be time-prohibitive or even impossible to do initialization during normal driver operation.
Media Check (code 0x01). This function is called by the kernel prior to disk access, and it is therefore valid only for block devices. The kernel passes the driver the media ID byte corresponding to the type of disk it expects to find in the selected drive.
BuildBPB (code 0x02). When the block driver gets a Build Bios Parameter Block call, it must return a pointer to the BPB that describes the mass-storage device.
Read (code 0x04). The application calls the Read section by issuing a DosRead with the handle obtained during the DosOpen. The Read routine may return one character at a time, but more often it returns a buffer full of data. How the Read function works is up to the driver writer. The driver returns the count of characters read and stores the received data in the data segment of the application. Read returns a standard driver return code.
Nondestructive Read (code 0x05). In response to this request, the driver must get the first character in the driver buffer and return it to the caller. If no character is present, the driver must return immediately with the proper error bits and Done bit set.
Input Status (code 0x06). The driver must clear the Busy bit in the request packet if one or more characters are in the driver’s buffer, or set it if no characters are present. This is a Peek function to determine the presence of data.
Flush Input Buffer(s) (code 0x07). This function should flush any receiver queues or buffers and return a Done status to the kernel.
Write (code 0x08). This is a standard driver request called by the application as a result of a DosWrite call. The application passes to the driver the address of data to write (usually in the application’s data segment) and the count of characters to write. The driver writes the data and returns the status to the application along with the number of characters that were actually written. Write returns a standard driver return code.
Write with Verify (code 0x09). The driver writes data as in the Write function code above, but it verifies that the data was written correctly.
Output Status (code 0x0a). The driver must set the Busy bit in the request packet if an operation is in progress, or clear it if the transmitter is free.
Output Flush (code 0x0b). The driver must flush the output queues and buffers and return a Done status to the kernel.
Device Open (code 0x0d). This function is called as a result of the application issuing a DosOpen call. The kernel makes note of the DosOpen request, and if it is successful (done with no errors) the kernel sends back a handle to the application to use for subsequent driver service. The driver writer can use this section to initialize a device, flush any buffers, reset the buffer pointer, initialize the character queues, or anything necessary for a clean starting operation.
Device Close (code 0x0e). This function is called as a result of the application doing a DosClose with the correct driver handle. It’s a good idea to make sure the application closing the driver is the same one that opened it, so save the process ID of the application that opened the driver and make sure the closing PID is the same. If not, reject it as a bogus request. You should make all your devices quiescent at this time.
Removable Media (code 0x0f). The driver receives this request when an application generates an IOCtl call to category 8, function 0x20. Instead of calling the IOCtl, the kernel issues this request. The driver must set the Busy bit of the request-packet status if the media is nonremovable, or clear it if it is removable.
Generic IOCtl (code 0x10). This is a special type of function call. It is very flexible, as the data passed to the driver is stored in two buffers owned by the caller. These buffers may contain any type of data; the format is up to the driver writer.
The first and second parameters of an IOCtl are the address of the application program’s data buffer and parameter buffer, respectively. The parameter buffer might contain a list of USHORTs, UCHARs, or pointers. The data buffer parameter might be a data buffer address in the application program, where the driver would store data from the device.
IOCtls can extend the range of status information that drivers can convey to applications. Suppose, for example, a driver needed to report to an application that the data was in ASCII or binary format, or that a parity error was detected while receiving it.
Here an IOCtl would be the answer. The reason? The kernel massages return codes from standard function calls to fit within the standard error definitions. The IOCtl, however, will pass back codes to the application exactly as they were set in the driver. In several drivers that I have written, the DosRead and DosWrite sections of the Strategy routine are commented out and never used. I use IOCtls for the reads and writes to allow the driver to communicate directly with the application without interference from the kernel.
PrepareForSysShutdown. This function tells the device driver it should post any open buffers to their devices before the system powers down. This occurs when you select Shutdown from the Desktop window.
The Interrupt Section
When OS/2 calls your interrupt handler, it does so with interrupts disabled, so any extended time spent in the interrupt handler could cause performance problems. When activated in response to the receipt of data, the interrupt handler must store the data and exit quickly. In the case of character devices, the OS/2 DevHlp library supports fast reads and writes to circular character queues. For block devices, interrupt handling is fast because the interrupt is usually caused by a DMA completion or disk seek completion. For block devices, data is ordinarily transferred to the user buffer using DMA, eliminating the need to transfer data during the interrupt processing. On a DMA transfer, the driver can exit once the DMA controller starts so that other threads can run. When the DMA completes, it generates a DMA completion interrupt that activates the driver’s interrupt handler.
The interrupt handler routine is not difficult to write or understand, but it can be very difficult to debug. Errors that occur in the interrupt handler frequently appear only in a real-time context, when the interrupt handler is active in response to a hardware interrupt. You can’t do a printf() from the interrupt routine or inspect
variables with an application debugger, such as CodeView. You must use the OS/2 KDB (Kernel Debugger) supplied with the DDK or a similar debugger. Even with the KDB, a breakpoint will halt the program, and further interrupts may pass undetected while you decide what to type next. Because of this pause in execution, you lose the real-time context of the program, which may be the root of the original problem. In the end, there’s no substitute for the ability to visualize the correct operation of the interrupt handler.
The Timer Handler
In an OS/2 driver, you can hook the system timer interrupt with a call to the DevHlp library SetTimer function. You pass OS/2 a near pointer to your timer handler, and for each system timer tick, OS/2 calls your timer handler routine and any other timer handler that had been previously registered.
If no data appears within one or two 32-millisecond time ticks, the driver assumes that data input has stopped or at least paused. If a valid Read request is pending, it sends back the data to the blocked Strategy section by issuing a Run request with the same ID used to block the requesting thread. The Strategy section becomes unblocked, gets the data from the receiver queue, and sends the data to the application’s data buffer.
Do You Really Need a Device Driver?
Maybe not. OS/2 1.x allows programs with I/O Privilege (IOPL) enabled to do direct register I/O to a device. If the device is a parallel card or digital switch, a driver may not be necessary. You can set or clear bits using IN and OUT instructions, and as long as the device is not time critical, such a method will be sufficient.
Yet devices that generate interrupts, require asynchronous service, or operate in a time-critical environment must use a device driver. Take a serial device, for example. It would be difficult or impossible to read data from the device using the IOPL method. By definition, asynchronous data may come in at any time. Because OS/2 may be running another thread at the time the data appears, your chances of missing data are excellent. But an interrupt driver could continue to read and buffer the incoming data until the OS/2 scheduler ran your thread.
Optionally, you can allow interrupts to preempt the current running thread and run your thread immediately. You need not wait for the
scheduler to run it. This sort of preemptive multitasking sets OS/2 apart from other multitasking systems, like Unix. In Unix, the
currently running program retains the CPU until it exhausts its time slice. It cannot be preempted based on an event, such as a device
interrupt. That’s why OS/2 is my choice for time-critical applications.
Steven J. Mastrianni is an independent consultant in South Windsor, Connecticut, who specializes in OS/2 device drivers. You can contact
him on BIX as “smastrianni.”
COMPANIES MENTIONED
FutureWare, Inc.
78 Temple Ave., Suite 15
Hackensack, NJ 07601
(201) 343-3921
Microsoft Corp.
One Microsoft Way
Redmond, WA
(800) 227-6444
OS Technologies
532 Longley Rd.
Groton, MA 01450
(508) 448-9653
PentaSoft
17541 Stone Ave. N
Seattle, WA 98133
(206) 546-0470
PSS Corp.
290 Brookfield St.
South Windsor, CT 06074
(203) 644-4764
Study of the OS/2-eCS Boot Process
Published by mgreene on 05/21/2008 – 00:00
My OS/2-eCS boot process notes using Bootable JFS.
My main point of curiosity is the operation of the os2ldr file and I found that it is hard to get far without understanding the steps getting to executing os2ldr. The point leading up to os2ldr is considered the “Black Box” because there are a number of ways to boot eCS-OS/2 for example FAT, IFS, or Remote IPL. The major issue is that the “Black Box” sets up the system and provides all information os2ldr requires.
Just for by own interest I pulled the MBR from my eCS 2.0 RC4 driving using DFSee and disassembled. What follows is the result and some comments from noted sources. Since I am not very good at reading decompiled code I went to the next level, trying to step through the boot using a MBR, BPB and os2boot running in Bochs on my one of my Linux systems.
I found an old 630 meg IDE hard drive and installed in the system. The system was booted using the current eCS 2.0 RC4 CD. I then ran LVM and made bootable compatible partition and wrote the MBR. Next, I did a long JFS format of the partition and then ran sysinstx.com against it to get os2boot installed. I did forget to set the partition active, but later corrected using DFSee. The hard drive was pulled and install in my LINUX system and I used dd to image the drive.
This image will boot through the running of os2boot, but will quit due to the lack of an os2ldr file. That will come later. So if anyone is interested the image can be downloaded here: harddrive.zip When setting up in Bochs the correct drive settings are 1240 cyl 16 heads 63 sectors. Here is a quick howto in getting started with Bochs.
This is a work in progress, subject to change daily. It is for my own information and if it helps or be an interest others then that would be a bonus.
Changes
21 May 2008:
I updated my Bochs image with the UNI os2ldr and os2krnl from the eCS RC4. These are not the latest but I can trace from boot through os2ldr. The file is 1.6 meg and unzips to 630 Meg and can be downloaded here: newdrive.zip.
Note!!! download DFSee iso and boot it as the CDROM to set the hard drive image active if it will not boot.
DevHlp_ReadFileAt
Description
This devhlp is used to read from a file opened with DevHlp_OpenFile, in contrast to a normal read, a start position can be specified.
Calling convention
- ES:DI = point to a SYIReadFileAt structure
- DL = 082h
Returns
- CY=0, AX = 0: read okay
- CY=1, AX = ERROR_BAD_LENGTH: packet is invalid
- CY=1, AX = <>0: other filesystem related errors
Data structures
; assembler structure
SYIReadFile struc length dw 12 ; length of structure (must be 12) buffer dd ? ; 16:16 address of a read buffer size dd ? ; number of bytes to read startpos dd ? ; starting position of read relative to ; start of file SYIReadFile ends
Restrictions
Valid at INIT time only. Although the size argument of the data structure implies, I’d recommend to better not read more than 32K at a time. Note that the buffer itself is segmented, so DMA overrun and other effects might apply, depending on the quality of the miniIFS in use.
Bugs
IMHO, the length of structure should be 14, not 12.
DevHlp_QSysState
Description
This devhlp is the equivalent of DosQuerySysState API call. See more details about the data structures, arguments and options there.
Calling convention
- EAX = function code
- EBX = argument 1
- EDI = high 16 bit: argument 2, low 16 bit: argument 3
- ESI = FLAT (linear) address of a buffer
- ECX = size of buffer
- DL = 07eh
Returns
- CY=0, AX = 0, okay
- CY=1, AX = APIRET error code
Data structures
See DosQuerySysState
Restrictions
Valid at TASK time only. See DosQuerySysState
DevHlp_ReadFile
Description
This devhlp is used to read from a file opened with DevHlp_OpenFile.
Calling convention
- ES:DI = point to a SYIReadFile structure
- DL = 081h
Returns
- CY=0, AX = 0: read okay
- CY=1, AX = ERROR_BAD_LENGTH: packet is invalid
- CY=1, AX = <>0: other filesystem related errors
Data structures
; assembler structure
SYIReadFile struc length dw 8 ; length of structure (must be 8) buffer dd ? ; 16:16 address of a read buffer size dd ? ; number of bytes to read SYIReadFile ends
Restrictions
Valid at INIT time only. Although the size argument of the data structure implies, I’d recommend to better not read more than 32K at a time. Note that the buffer itself is segmented, so DMA overrun and other effects might apply, depending on the quality of the miniIFS in use.
Bugs
IMHO, the length of structure should be 10, not 8.