VOGONS


First post, by superfury

User metadata
Rank l33t++
Rank
l33t++

Is the expand-down segment's linear address calculated differently from expand-up segments?

I have, for example, a descriptor which points to a base address of 0x80000000. Then that is a kernel stack(of course differently for different operating systems, but it's just for simplicity).
If I have a 16-bit expand-up segment, addressing offset fffc(e.g. a dword push to a stack) will point to a linear address of 0x80000000+fffc=0x8000fffc.

What happens if said 16-bit segment was a expand-down stack segment? Would the resulting linear address be 0x8000fffc(LA=base+offset) or 0x7ffffffc(LA=base+offset-64K)?

What I've read about this: http://www.sudleyplace.com/dpmione/expanddown.html

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

Reply 1 of 15, by crazyc

User metadata
Rank Member
Rank
Member
superfury wrote:

Is the expand-down segment's linear address calculated differently from expand-up segments?

No. I've never seen anything use large expand down segment probably because they appear to be useless.

Last edited by crazyc on 2019-04-20, 19:12. Edited 2 times in total.

Reply 2 of 15, by superfury

User metadata
Rank l33t++
Rank
l33t++
crazyc wrote:
superfury wrote:

Is the expand-down segment's linear address calculated differently from expand-up segments?

No.

So, the result of a base address of 0x80000000 and limit 0xFFFF, then addressing offset 0xFFFC actually resolves to linear address(or physical address without paging) 0x8000FFFC?

Last edited by superfury on 2019-04-20, 19:13. Edited 1 time in total.

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

Reply 4 of 15, by superfury

User metadata
Rank l33t++
Rank
l33t++

Or is the offset 1-extended to 32-bits before adding it(as long as it's obeying the segment descriptor's limit(being more than the limit field(which is 1-appended for 4K segments(Granularity being 4K))) and roof of 64K/4G), resulting in a offset of FFFFFFFC(wrapping afterwards), thus resulting in a offset of (1)7FFFFFFC afterwards(with the carry being discarded due to wrapping to 32-bits(even on 64-bit architectures))?

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

Reply 6 of 15, by superfury

User metadata
Rank l33t++
Rank
l33t++

Well, the documentation I gave on the first post says something interesting on it:

Sometimes the arithmetic is dependent upon the B (Big) bit in the DTE. This bit controls several aspects of how the CPU interpre […]
Show full quote

Sometimes the arithmetic is dependent upon the B (Big) bit in the DTE. This bit controls several aspects of how the CPU interprets the Base, Limit, and Valid Offsets of a selector. As you can see from the table below, the B bit has a huge effect on how Expand Down segments are described and no effect on Expand Up segments. Because the arithmetic is done modulo either 64KB (for B=0) or 4GB (for B=1), we define the Modulus to be 64KB for B=0, and 4GB for B=1.

Stack Type Expand Up Expand Down
GDT/LDT Base LA LA + Length - Modulus
GDT/LDT Limit Length - 1 (Modulus - 1) - Length
Smallest Offset 0 Modulus - Length
Largest Offset Length - 1 Modulus - 1
Initial eSP Length 0

That first field(GDT/LDT Base) got me thinking...

So the base is LA(which is the base)+FFFF(length)-FFFFh(FFFFFFFFh for 4G granularity)(modulus) in the case of said 64K segment(although at that size, according to documentation, it disappears(due to the offset being <=limit always, thus no valid offsets)). Thus 0x80000000+ffffh(length)-ffffh(modulus)=0x80000000 as it's base. But when the length field is decreased, said address becomes closer to 0x80000000(still ending at the byte before it?)? So a size of limit field 0x7FFF(32K being addressable at the end of the segment) becomes a base of 0x7FFF8000(80000000h+7FFFh-FFFFh), with it's final byte(offset FFFFh added to that) at 80007FFF(thus the linear address ending like a plain 32K segment in memory)?

So perhaps that "LA+Length-Modulus"(Just LA for Expand-Up descriptors, Length being the limit field(expanded to 4G pages if the B-bit is set(filling the lower 12 bits with FFFh)) and Modulus being FFFFh or FFFFFFFFh depending on the granularity) is the actual formula used by a CPU for it's adding to the offset? If that's true, the OS can simply program the base to where it wants to put the data in memory(for example 0x80000000 in my example), without having to worry about a Expand-Up or Expand-Down segment is used. The block stays at the same address when the limit field is decreased(increasing the stack size for example, the OS just needs to move it's data ahead by the amount of bytes that are added to the segment). In the case of 4K pages(assuming the base address is 4K aligned), this is even easier(just modify the paging unit and map all pages one or more pages ahead).

Edit: The text further ahead seems to verify that information I mentioned above(in my bottom examples):

For Expand Up segments, the GDT/LDT Base Address is fixed at LA, and for Expand Down segments it's LA + Length - Modulus. As Length increases up to and including Modulus, the Base Address approaches LA (the same as for Expand Up segments).

This illustrates the great benefit of an Expand Down data segment when used as a stack. Because stacks grow downward, when they overflow, the stack can be expanded easily by copying its data to the top of a new (and larger) area of memory and then pointing the stack selector to the new location. Note that references to items on the stack do not change when the stack expands because the upper limit of the stack does not change and offsets in an Expand Down stack are all relative to the top of the stack.

In a similar manner, an Expand Up segment grows upward, so it can be expanded by copying its data to the bottom of a new (and larger) area of memory and then pointing the data selector to the new location. Here, too, references to items in the data segment do not change when the data segment expands because the start of the segment (offset zero) does not change and offsets in an Expand Up segment are all relative to the bottom of the data segment.

If the base fields of the descriptor behaved the same in both Expand-Up and Expand-Down descriptors, said quotes would make little sense. It clearly says that the data is copied to the top of the new segment with Expand-Down descriptors and to the bottom of an Expand-Up descriptor. If the base field would just be added to the offset(e.g. FFFFh) in such a case, you'll end up 32K past any data you would copy(in my example above) instead of the correct location(which is at 0x80000000-0x800007FFF.

If the base would function the same way (just being added to the offset without substracting (FFFF)FFFFh and the limit from it), said explanation quoted would break all code that uses said logic?

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

Reply 7 of 15, by crazyc

User metadata
Rank Member
Rank
Member

From the IA32 manual:

For expand-down segments, the segment limit has the reverse function; the offset can range from the segment limit plus 1 to FFFFFFFFH or FFFFH, depending on the setting of the B flag. Offsets less than or equal to the segment limit generate general-protection exceptions or stack-fault exceptions. Decreasing the value in the segment limit field for an expand-down segment allocates new memory at the bottom of the segment's address space, rather than at the top. IA-32 architecture stacks always grow downwards, making this mechanism convenient for expandable stacks.

So decreasing the limit grows the segment downward toward the base address.

Reply 8 of 15, by superfury

User metadata
Rank l33t++
Rank
l33t++

Looking at http://premium.caribe.net/~adrian2/docs/exc_ia.pdf page 8 seems to agree with the documentation and my logic. Indeed, as you can see in the diagram on said page, the final byte of the segment descriptor(at offset FFFFh in 64K granuliary) is actually located at the address set in the base fields. So the byte before the base field is actually the last offset(FFFFFFFFh) that's addressed by the block pointed to by the descriptor? So the descriptor always points to one past the final byte of the stack in it's GDT entry?

So if the stack's base fields point to 0x80000000 and a limit of 0h(also substract 64K), offset FFFFh points to 0x7FFFFFFF?

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

Reply 10 of 15, by superfury

User metadata
Rank l33t++
Rank
l33t++

So, for offset FFFFh in a 64K expand-down segment with limit field set to 0 and offset FFFFFFFFh in a 4G expand-down segment with a limit field set to 0 won't point to the same address in memory? For a 64K, it would point to base+FFFFh, while for a 4G it would point to the byte immediately below the base address?

That does sound kind of not backwards compatible in that respect, doesn't it? While adding the limit field and substracting 64K for the base address to be added actually results in 64K and 4G arithmetics to result in the same logical address, which does make sense? Isn't the 80386+ backwards compatible with the 80286 16-bit modes? So the 4G mode and 64K mode should both point to LA (base-1) for offset FFFF(64K) and FFFFFFFF(4G)?

Again, if the modulus wasn't substracted from the address and the limit wasn't added to the address, said arithmetic would fail.

I also seem to remember debugging the OS/2 kernel, which uses a very strange base address for it's 64K top-down selector(where it would overlap with one of the descriptor or page tables if the base was used directly if I remember correctly). So if the stack was used with said base address added to the offset, the stack would be written into the processor's and OS's tables instead for the upper half of it's contents(offsets FFFFh to size/2).

Edit: OS/2 seems to load it's first top-down segment with a base of ffdf7000 and a limit of 3fff. The GDTR is at 0xffe3e000(size 1fff) and IDTR is at 0xffe16270 limit 3ff. If the base is adjusted like in my example, it's at FFDEB000 so offset ffffh is at FFDFAFFF, which is the same location as offset 3fff in a expand-up descriptor.

Last edited by superfury on 2019-04-20, 21:53. Edited 1 time in total.

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

Reply 11 of 15, by crazyc

User metadata
Rank Member
Rank
Member

So, for offset FFFFh in a 64K expand-down segment with limit field set to 0 and offset FFFFFFFFh in a 4G expand-down segment with a limit field set to 0 won't point to the same address in memory?

Huh? Sure they would. The small segment would be valid from base+1 - base+64K and the large from base+1 - base+4G and offsets 1 - 64K would be the same memory (if the base address were the same).

Reply 12 of 15, by superfury

User metadata
Rank l33t++
Rank
l33t++
crazyc wrote:

So, for offset FFFFh in a 64K expand-down segment with limit field set to 0 and offset FFFFFFFFh in a 4G expand-down segment with a limit field set to 0 won't point to the same address in memory?

Huh? Sure they would. The small segment would be valid from base+1 - base+64K and the large from base+1 - base+4G and offsets 1 - 64K would be the same memory (if the base address were the same).

Nope, they won't(if the substracting 0x10000(for 64k segments) isn't applied):

say, base 0x80000000 in both 64k and 4G descriptors.
64k:ffff=0x80000000+ffff=0x8000ffff
4g:ffffffff=0x80000000+ffffffff=0x7fffffff

Thus they would point to different memory locations.

If just the modulus substraction is performed, the offsets point to the same memory location. The OS/2 stack would thus begin at FFDEB000 through FFDF6FFF?

Edit: Tested my theory out with the 64k substraction. It immediately triple faults on the stack when OS/2 is loading the first disk's installer:S

So indeed, the 64K and 4G offsets point to different memory addresses.

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

Reply 13 of 15, by crazyc

User metadata
Rank Member
Rank
Member
superfury wrote:

say, base 0x80000000 in both 64k and 4G descriptors.
64k:ffff=0x80000000+ffff=0x8000ffff
4g:ffffffff=0x80000000+ffffffff=0x7fffffff

Yeah, so? Offset 0xffff would still be address 0x8000ffff in both segments.

Reply 14 of 15, by superfury

User metadata
Rank l33t++
Rank
l33t++

Ehm..... Offset 0xffff isn't what I was talking about. I clearly stated offset 0xffff for the 64K one and offset 0xffffffff for the 4G one.

So, for offset FFFFh in a 64K expand-down segment with limit field set to 0 and offset FFFFFFFFh in a 4G expand-down segment with a limit field set to 0 won't point to the same address in memory? For a 64K, it would point to base+FFFFh, while for a 4G it would point to the byte immediately below the base address?

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

Reply 15 of 15, by superfury

User metadata
Rank l33t++
Rank
l33t++

Also, some interesting trivia. Found some proof that Windows 95 also uses Expand-Down segments(to prevent Windows 95 applications from accessing the MS-DOS VM and shared 4MB area(for Windows 3.x programs)):

Geek trivia: To prevent Win32 applications from accessing the MS-DOS compatibility area, the flat data selector was actually an expand-down selector which stopped at the 4MB boundary. (Similarly, a null pointer in a 16-bit Windows application would result in an access violation because the null selector is invalid. It would not have accessed the interrupt vector table.)
The linker chooses a default base address for executables of 0x0400000 so that the resulting binary can load without relocation on both Windows NT and Windows 95. Nobody really cares much about targeting Windows 95 any more, so in principle, the linker folks could choose a different default base address now. But there’s no real incentive for doing it aside from making diagrams look prettier, especially since ASLR makes the whole issue moot anyway. And besides, if they changed it, then people would be asking, “How come some executables have a base address of 0x04000000 and some executables have a base address of 0x00010000?”

From: https://devblogs.microsoft.com/oldnewthing/20 … 003-00/?p=43923

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