VOGONS


First post, by superfury

User metadata
Rank l33t++
Rank
l33t++

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

Reply 1 of 3, by GemCookie

User metadata
Rank Member
Rank
Member
superfury wrote on 2025-03-14, 01:11:

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?

Yes.

What happens if you flip it by setting it to a expand-down segment? Does that mean that 1MB till 4GB becomes addressible only?

Yes, if the limit is equal to 0FFFFFh.

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?

Indeed ‒ with a 16-bit segment, accessing any offset past 0FFFFh causes a general protection fault.

Gigabyte GA-8I915P Duo Pro | P4 530J | GF 6600 | 2GiB | 120G HDD | 2k/Vista/10
MSI MS-5169 | K6-2/350 | TNT2 M64 | 384MiB | 120G HDD | DR-/MS-DOS/NT/2k/XP/Ubuntu
Dell Precision M6400 | C2D T9600 | FX 2700M | 16GiB | 128G SSD | 2k/Vista/11/Arch/OBSD

Reply 2 of 3, by superfury

User metadata
Rank l33t++
Rank
l33t++
GemCookie wrote on 2025-03-15, 21:48:
Yes. […]
Show full quote
superfury wrote on 2025-03-14, 01:11:

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?

Yes.

What happens if you flip it by setting it to a expand-down segment? Does that mean that 1MB till 4GB becomes addressible only?

Yes, if the limit is equal to 0FFFFFh.

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?

Indeed ‒ with a 16-bit segment, accessing any offset past 0FFFFh causes a general protection fault.

Huh? So with an expand down segment with a limit of 0xFFFFh till 1FFFFh, granularity cleared, Big bit cleared, does that mean that 32-bit addressing from any address will fail? Even if you use the DS segment for example (which shouldn't look at the Big bit afaik)?
So basically, the Big bit doesn't apply (forced to 4GB instead of 64KB) with non-SP (or SS? Documentation isn't clear on it) register accesses, so for expand-down segments it just affects the SP/IP registers?
So does the B-bit only affect the use of IP instead of EIP and SP instead of ESP (masking off the top 16-bits)? And leave all other accesses as normal 32-bit verification (thus 4GB top address for expand down)?
Or just use the B-bit on SS segment, but enforce it to be loaded as 1 on any other register?
Edit: But the 80386 says the following:
https://pdos.csail.mit.edu/6.828/2017/reading … i386/s06_03.htm

For expand-down data segments, the limit has the same function but is interpreted differently. In these cases the range of valid addresses is from limit + 1 to either 64K or 2^(32) - 1 (4 Gbytes) depending on the B-bit. An expand-down segment has maximum size when the limit is zero. (Turning on the expand-down bit swaps which bytes are accessible and which are not.)

So basically, it adds a 'roof' like my implementation does by not allowing addresses past 64K when the B-bit is zeroed. That's true even for 32-bit address size being used (so B-bit zero, expand down, limit 64K-1 and higher invalidates all memory (since past 64K is illegal always when B-bit is zeroed). It's only possible to work up to 4GB in this case by using a page granularity, which expands the B-bit to 4GB or lower (some accessible, except 4GB (FFFFFh case)) or by setting the B-bit (to make the upper bound 4GB)).

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 3 of 3, by GemCookie

User metadata
Rank Member
Rank
Member
superfury wrote on 2025-03-16, 14:53:

Huh? So with an expand down segment with a limit of 0xFFFFh till 1FFFFh, granularity cleared, Big bit cleared, does that mean that 32-bit addressing from any address will fail? Even if you use the DS segment for example (which shouldn't look at the Big bit afaik)?

Yes.

So basically, the Big bit doesn't apply (forced to 4GB instead of 64KB) with non-SP (or SS? Documentation isn't clear on it) register accesses, so for expand-down segments it just affects the SP/IP registers?
So does the B-bit only affect the use of IP instead of EIP and SP instead of ESP (masking off the top 16-bits)? And leave all other accesses as normal 32-bit verification (thus 4GB top address for expand down)?
Or just use the B-bit on SS segment, but enforce it to be loaded as 1 on any other register?

Not quite – setting the size bit allows for accessing offsets past 0FFFFh.

Edit: But the 80386 says the following: https://pdos.csail.mit.edu/6.828/2017/reading … i386/s06_03.htm […]
Show full quote

Edit: But the 80386 says the following:
https://pdos.csail.mit.edu/6.828/2017/reading … i386/s06_03.htm

For expand-down data segments, the limit has the same function but is interpreted differently. In these cases the range of valid addresses is from limit + 1 to either 64K or 2^(32) - 1 (4 Gbytes) depending on the B-bit. An expand-down segment has maximum size when the limit is zero. (Turning on the expand-down bit swaps which bytes are accessible and which are not.)

So basically, it adds a 'roof' like my implementation does by not allowing addresses past 64K when the B-bit is zeroed. That's true even for 32-bit address size being used (so B-bit zero, expand down, limit 64K-1 and higher invalidates all memory (since past 64K is illegal always when B-bit is zeroed). It's only possible to work up to 4GB in this case by using a page granularity, which expands the B-bit to 4GB or lower (some accessible, except 4GB (FFFFFh case)) or by setting the B-bit (to make the upper bound 4GB)).

Seems about right.

Gigabyte GA-8I915P Duo Pro | P4 530J | GF 6600 | 2GiB | 120G HDD | 2k/Vista/10
MSI MS-5169 | K6-2/350 | TNT2 M64 | 384MiB | 120G HDD | DR-/MS-DOS/NT/2k/XP/Ubuntu
Dell Precision M6400 | C2D T9600 | FX 2700M | 16GiB | 128G SSD | 2k/Vista/11/Arch/OBSD