LC-Executable | DOS/32a Extender

Linear Compressed Executable | DOS/32a executable format

The internet remembers everything, and this infamous format is not an exception. Unfortunately, people, who want to find some of information about - couldn’t find it. The Linear compressed executables bound to DOS/32a extender very hard. And if you don’t know about this extender - you can’t find anymore about this details.

In another words: if we will trust in some internet segment, the LC executable format is a DOS/32a feature.

Little about DOS32/a | Technical details review

The DOS/32a extender is a freeware for now, and sources of it are store in GitHub repository. All what we need – find anything about supporting programs and games by our target.

DOS/32a supports
 -> LC (extender's) executables
 -> LE (Windows-OS/2) executables;
 -> LX (OS/2-ArcaOS) executables;
 -> MZ real-mode DOS executables;

The real-time DOS executables and other programs we may convert or compress and run with other options for extender’s toolkit.

That’s all we need to run it.

The DOS/32a extender has some parts

DOS32A.EXE          #Main real-mode DOS executable
    +--> SVER.EXE
    +--> SC.EXE     #Compressor
    +--> SB.EXE     #Builder
    +--> SS.EXE     #Stub configuration
    +--> STUB32A    #Advanded (default) stub <--+ Those files just code objects
    +--> STUB32C    #Configurable stab       <--+ but marked with .EXE

This extender may bind with Watcom toolkit, and proceed projects in OS/2-DOS modules we can process again with DOS/32a. Or we may configure DOS/32a for DOS32a stub insertions. It has 3 types of stub by itself, and help page represents us the choise:

I’ve run under eComStation (OS/2 Warp 4.5) because I need a support of OS/2 standard modules and DOS emulation.

For test a file format we don’t need all package, just compressor SC.EXE.

Who wants to be compressed? | Victim details review

Before we run 1.44MiB virtual diskette with prepared DOS/32a package and testable file for target compressor. Let’s define all what I want.

My tasks for this run:

  • Mount diskette
  • Run under DOS session
  • Compress the target executabe;

My target was a DOOM II "Hell on the Earth" for OS/2. So, then we have a well linked LE executable program. I want to see just two things:

  • Objects Table;
  • Fixup Records;

The first table here at is:

# Name:s VirtualSize:4 RelBase:4 FlagsMask:4 PageMapIndex:4 PageMapEntries:4 Unknown:4 Flags:s
1 .CODE 0x0001BEA0 0x00010000 0x00002045 0x00000001 0x0000001C 00000000 SINGLE PRELOAD_PAGES USE_32
2 .DATA 0x0003EEC0 0x00030000 0x00002043 0x0000001D 0x00000005 00000000 SINGLE PRELOAD_PAGES USE_32

Remember: This is an LE compiled and linked program module without version and module directives. We can’t find more details about this file. (Verification records are missing, and external fixups too) The EntryTable of DOOM.EXE are empty.

But 2 objects with all sources are there. And we need it magic number (number of Objects) later.

Here is a Fixup records common data:

Source TargetFlags SourceOffset HasSourceList HasAdditive Is32BitTarget Is32BitAdditive Is16BitObjectModule Is8BitOrdinal TargetData AdditiveValue SourceOffsetList #
0x07 0x10 0x081A False False True False False False Internal Target 0x00000000   0
0x07 0x10 0x0800 False False True False False False Internal Target 0x00000000   1

I’ve truncated it. For real the count of relocations here equals 5951. This document makes a boom if I insert it. This is a FLAT32 pointers, just because objects of code and data are 32-bit. (look at the Objects Table). Finally we don’t need segments and FAR pointers!

I’ll not show you details of internal targets. They are pointers to functions in statically linked modules bound with this EXE.

I’ve decided my DOOM.EXE is legal and troubles with rebuild/compression couldn’t happened.

Tests

Finally, I’ve moved file into diskette, ejected it and run OS/2 with inserted virtual disk with letter A.

The help page of SC.EXE don’t desribes syntax rules a little. And I’ve tell it here:

Firstly we need iterate all flags for target program.
Secondary tell output file path in filesys.
A:\> SC.EXE </flag /flag /flag...> <target.exe>

Our special flags will be

  • Insert default stub (DOS32a);
  • Don’t apply optimizations;
  • Verbose (2’nd level);

And my target is ready to be compressed into new format. Let’s do it!

Have I compressed it or not?

I suppose, flag /D (Disable optimizations) depends on compression very hard. (Obvious :D).

My target DOOM engine for OS/2 has size about 230K. After SC call it was reformatted and differs with linear bro with 30 kilobytes.

Do you remember what I’ve told about objects? It’s really found 2 objects with data and code. And following fixup records by fixup pages are follows next after objects header.

Our victim ready to be explored and explained for now. We have produced LC-executable what can runs only through the DOS32A.EXE. If we look up at the sky and down at the backend of DOS32A compressor, we’ll see next part of tree:

   ...
    | +dos32a
    | |-+text
    | | +-> changes.txt
    | | +-> kernel.asm
    | | +-> loadpe.asm
    | | +-> loadlc.asm
    | | +-> make.bat
    | | +-> makebeta.bat
    | + +-> notes.txt
    | +sb
    | | ...
    ...

This is not disassembled project, you’re right. This is an actually backend part of extender.

Linear Compressed | Format review

Only three sources in all project what describes this format little better are the files

  • loadlc.asm (.../dos32a/text/loadlc.asm);
  • sccomp.asm (.../sc/sccomp.asm);
  • sload.asm (.../sc/sload.asm);

All what I want to write here – are belongs to those three files.

The compressed file’s layout looks like this:

┌───────────────────────┐
│    IMAGE_DOS_HEADER   <-- MZ Header
├───────────────────────┤
│ This could be a stub  <-- Data here depends on configured
│ but this is a DOS32a  │   compressor and builder.
│ or DOS32c stub        │   STUB32A.EXE or STUB32C placed here.
├───────────────────────┤
│   IMAGE_DOS32_HEADER  <-- LC Header
├───────────────────────┤
│     Object #1 Header  │ <-- 16 Bytes
├───────────────────────┤
│ 00 01 FF 4D D6 90 20 <-- Deompressed or raw code/data
│ 33 00 00 03 00 04 FF  │  contains here.
│ 56 09 DA F0 0B CC 3C  │  Strictly by the header.
├───────────────────────┤  
│     Object #2 Header  │
├───────────────────────┤
│ 03 00 05 DD 0A 5F 4D <-- Same idea.
├───────────────────────┤
│    *Fixups Header*   <-- Fixup records header  
├───────────────────────┤
│ ???                   │ ← Decompressed/raw Fixups
└───────────────────────┘

There’s no tables like in linear executables (LE/LX binaries). There flat layout of all file. All data follows strictly by headers about those data.

The loader header equals the 1 Intel paragraph (or 16 BYTEs). And each header of following objects equals 1 Intel paragraph too.

Linear Compressed Header

Let’s name it not only IMAGE_DOS32_HEADER. It seems the same with infamous Windows API named MZ header struct (a.k.a. IMAGE_DOS_HEADER).

#[repr(C, packed)]
struct LcHeader {
    e32_magic: [u8; 4], // "LC\0\0" 
    e32_objcnt: u8,     // Number of Objects
    e32_ver: u8,        // LC_SPECVER = 0x04
    e32_startobj: u8,   // Number of object with set-EIP
    e32_stackobj: u8,   // Number of object with set-ESP
    e32_eip: u32,       // Offset in EIP-set object
    e32_esp: u32,       // Offset ESP-set object
}

I suppose, first object will be CODE object with loaded instructions. And second object will be a stack. As we runs under DOS, as DOS32A wants, We need back to the segmend addresses world. The offsets

The Linear Compressed header really reminds about Linear Executables header. Let’s compare them right now

┌────────────────────────────────────┐
│                 Explicit Offsets   │
│    Field       LC      LE      LX  │
│e32_ver         0x05    0x0E    0x0E│
│e32_startobj    0x06    0x24    0x24│
│e32_stackobj    0x07    0x32    0x32│           
│e32_eip         0x08    0x28    0x28│
│e32_esp         0x0C    0x34    0x34│
│e32_objcnt      0x04    0x4C    0x4C│
└────────────────────────────────────┘

Object Header

After the LC header the object header structure follows. If you are compress the Linear executable: all object flags what have taken from ObjectTable are moved to compressed format into Object headers.

#[repr(C, packed)]
struct LcObjectHeader {
    virtual_size: u32,       // Bitmask: 31=encoded(0)/not_encoded(1)
    compressed_size: u32,    // Size of compressed data
    flags: u16,              // Flags of Objest (takes from LE/LX linked module)
    extended_flags: u16,     // Extended (always 0)
    page_table_index: u16,   // Index in PageMap/Table
    num_page_entries: u16,   // Number of page entries
    // The raw/compressed data strictly follows next
}

Fixup Header

After all headers follows big section named “Fixup Data” and header of Fixup data having 12-bytes size.

#[repr(C, packed)]
struct LcFixupsHeader {
    uncompressed_size: u32,  // Bits: 31=encoded(0)/not_encoded(1)
    compressed_size: u32,    // Size of compressed data
    fixup_table_offset: u32, // FixupRecords Table Offset
    // LE/LX Fixup data goes next
}

Fixup information inherits from Linear executable format and layout of FixupPagesTable and FixupRecordsTable are described very good in IBM Linear eXecutable module format manual. See any revision for more details.

Little more

  • If you look at the, loadlc.asm and jump to decompress_data label, You’ll may see that LC data compression bases on LZ algorithm or something same with it;
  • Rewrite already LC executable back to LE/LX not possible; (see FAQ of DOS/32a)

Author | Alexey Tolstopyatov (21 y.0.)

Desktop developer and System developer enthusiast. Currently a student in Tomsk State University in the area of software for microprocessor systems.