First post, by superfury
How does the x86 CPU handle segment descriptors with above 64KB limits and combining it with the expand down flag?
You can theoretically put a value from 64KB-1MB(minus 1) into the field after all.
So you can effectively put a 1MB file into that (with granularity bit cleared), and address it using 32-bit instructions?
What happens if you flip it by setting it to a expand-down segment? Does that mean that 1MB till 4GB becomes addressible only?
So for example to protect against page #0 (paging) access, you could set up a 16-bit descriptor with granularity cleared, limit 0xFFF and base address 0? Then address it with 32-bit address size to be able to access anything up to 4GB?
Or is it still faulting on anything past 64KB-1?
Right now, my emulator calculates a 'roof' if you can call it that:
//Roof: Expand-up: G=0: 1MB, G=1: 4GB. Expand-down: B=0:64K, B=1:4GB.
descriptor->PRECALCS.roof = ((uint_64)0xFFFFU | ((uint_64)0xFFFFU << ((descriptor->PRECALCS.topdown?SEGDESCPTR_NONCALLGATE_D_B(descriptor):SEGDESCPTR_GRANULARITY(descriptor)) << 4))); //The roof of the descriptor!
if ((descriptor->PRECALCS.topdown==0) && (SEGDESCPTR_GRANULARITY(descriptor)==0)) //Bottom-up segment that's having a 20-bit limit?
{
descriptor->PRECALCS.roof |= 0xF0000; //Actually a 1MB limit instead of 64K!
}
The roof is also checked for when checking offsets. It performs the documented limit check and inverts the result for validity.
Then it combines the roof check (basically: offset must be less than or equal to roof, otherwise fault).
For expand-up, roof is simple: 1MB(granularity cleared) or 4GB(granularity set).
For expand-down, roof is set to 64K(big bit cleared) or 4GB(big bit set).
See the above formula that's used.
The exact check it performs when checking the address to fault or not to fault is:
OPTINLINE byte invalidLimit(SEGMENT_DESCRIPTOR* descriptor, uint_64 offset)
{
return ((((offset > (descriptor->PRECALCS.limitignored?0xFFFF:descriptor->PRECALCS.limit)) ^ descriptor->PRECALCS.topdown) | (offset > descriptor->PRECALCS.roof)) & 1);
//Execute address test?
//Invalid address range?
//Apply expand-down data segment, if required, which reverses valid/invalid!
//Limit to 16-bit/32-bit address space using both top-down(required) and bottom-up(resulting in just the limit, which is lower or equal to the roof) descriptors!
//Only 1-bit testing!
//special case handling too: limit ignored (64KB) in special processor-specific modes.
}
The result is a simple 1 when it's supposed to fault for the specified offset, 0 if not to fault.
The limitignored setting is basically an exception for pre-Pentium real mode CS descriptors, as they seem to apparently ignore the limit field entirely.
Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io