Win3mu - Part 4 - Protected Mode

Win3mu - Part 4 - Protected Mode

This is Part 4 in a series of articles about building Win3mu — a 16-bit Windows 3 emulator. If you haven’t already, I recommend reading from the beginning where I explain my (ir)rationale for starting this project.

This post covers the differences between real and protected mode on x86 processors and how Sharp86's emulates this with its pseudo-protected mode.

Real Mode’s Segments and Offsets

Intel’s 8086 and 80186 processor ran in a mode that was later referred to as “real-mode”. Memory was addressed using a 16-bit segment and a 16-bit offset.

To calculate the physical memory address the segment and offset were combined like so:

physicalAddress = (segment << 4) + offset

This results in a 20-bit address and a total address space of 1Mb.

Switching to Protected Mode and Selectors

The 286 introduced protected mode and under-pinning this new model was a concept where memory is addressed using a selector and an offset.

On the surface a selector looks just like a segment in that it’s a 16-bit value, they’re stored in the segment registers just like in real mode but the way they’re mapped to physical memory is completely different.

The lower three bits of a selector represent ring levels and a flag to indicate which descriptor table to use — these aren’t relevant to this discussion. The remaining 13-bits however are an index into a selector descriptor table — a lookup table that stores a base physical memory address and other attributes about that block of memory.

The physical memory address is now calculated like this:

physicalAddress = selectorTable[selector >> 3].baseAddress + offset

The nice thing about this is that if the operating system needs to shuffle things around in physical memory (say to make a new memory allocation fit) it can do so and just update the selector table – any programs using that memory will be none the wiser. Pretty smart, not brain dead at all.

Compare that to real mode where the operating system needs to update all the segment addresses used throughout the program — an extremely complicated feat but one that real-mode Windows 3 manages to pull off — albeit with a few caveats.

Sharp86 and Pseudo-Protected Mode

My initial plans with Win3mu were to keep it simple and run a basic 8086 in real-mode with 1Mb of RAM. Realizing the nastiness of real-mode though I quickly switched it to protected mode.

Because Win3mu is an emulation of an operating system more than a hardware emulation this address mapping doesn’t need to be done in the processor. Instead it can be handled in the implementation of the IBus interface.

For example, to implement real-mode addressing the ReadByte method maps directly to one big chunk of memory:

public byte ReadByte(ushort seg, ushort offset)
{
    return memory[(seg << 4) + offset];
}

Protected mode looks up the selector in a table and then each selector has it’s own byte array backing it:

class Selector
{
    public byte[] memory;
}

Selector[] _selectorTable;

public byte ReadByte(ushort seg, ushort offset)
{
    var sel = _selectorTable[seg >> 3];
    return sel.memory[offset];
}

As you can see, by moving the physical address calculations to outside the processor (ie: passing seg and offset via IBus) protected mode addressing can be quite easily simulated — hence the term “pseudo” protected mode.

Read and Write Permissions

The other part of implementing protected mode relates to the different access rights of selectors. On a real processor these are stored as flags in the selector descriptor table and checked by the processor.

Sharp86 expects these checks to be managed externally in the implementation of IBus:


class Selector
{
    public byte[] memory;
    bool writable;
}

Selector[] _selectorTable;

public void WriteByte(ushort seg, ushort offset, byte value)
{
    try
    {
        var sel = _selectorTable[seg >> 3];
        if (!sel.writable)
            throw new Sharp86.GeneralProtectionFaultException();
        sel.memory[offset] = value;
    }
    catch (NullReferenceException)
    {
        throw new Sharp86.SegmentNotPresentException();
    }
    catch (IndexOutOfRangeException)
    {
        throw new Sharp86.GeneralProtectionFaultException();
    }

}

Executable Selectors

Protected mode on a 286 also supports marking a selector as executable — that is, declaring that the memory it refers to contains code. Trying to run code from a non-executable selector raises an exception and is used to catch jumps and calls to no-mans-land or into another program’s code.

Since code can only be executed via the CS selector Sharp86 implements this protection by calling IBus.IsExecutableSelector(seg) whenever the value of the CS register is changed:

public interface IBus
{
    byte ReadByte(ushort seg, ushort offset);
    void WriteByte(ushort seg, ushort offset, byte value);
    ushort ReadPortWord(ushort port);
    void WritePortWord(ushort port, ushort value);
    bool IsExecutableSelector(ushort seg);
}

Ring Levels

x86 protected mode also supports what are known as ring-levels.

Typically the operating system runs on ring level 0 (highest permissions) and user mode code runs on level 3 (lowest permissions). If code running in ring 3 tried to access memory using a ring 0 selector an exception occurs.

Sharp86 doesn’t implement ring levels because Win3mu only runs user-mode code on the emulated processor. All “operating system” code is implemented in C#.

Accessing Beyond 64k

You might have noticed that both real and protected mode addressing seem to have a built-in limit of 64k — the range of the 16-bit offset part of the segment:offset address. To access memory beyond the first 64k of an allocation we need to adjust the segment/selector part of the address to find the next 64k block.

In real-mode this is pretty easy to work out — just add 0x1000 to the segment and that’ll move you forward 0x10000 bytes (ie: 64k) in physical memory.

When using selectors however, the operating system needs to setup multiple selectors in the descriptor table to span each 64k block. The question is how do we find that next selector?

In protected mode, Windows 3 allocates consecutive selectors with a delta of 8 (it should be obvious why it’s 8). Say you allocate a 128k block of memory and the selector for the base address is 0x1231, then the selector for the second 64k will be 0x1239.

The Windows 3 kernel exports constants that describe the appropriate ways to do segment/selector arithmetic but given that anyone who ever had to deal with segment arithmetic would prefer it remain forgotten, I’ll stop talking about it now.

Enough About the CPU Already!

That pretty much covers all the important details on the CPU.

In the next article I’ll describe a few essentials about the NE .exe file format (I won’t bore you with all the details) before quickly moving onto other more interesting parts…