How to Use Expand Down Segments
on Intel 386 and Later CPUs

Overview

You may be used to thinking in terms of Expand Up data segments only because they are the default type for both code and data segments. In fact, there isn't even a corresponding concept to Expand Down for code segments. As you are already aware, for code and Expand Up data segments, the base address is the linear address, and the limit is the length minus one.

Expand Down segments, in a sense, switch those and many other concepts you have always taken for granted. For example, were you to toggle the Expand Down bit in a Descriptor Table Entry (DTE), and leave the Base Address and Limit the same, then all offsets which were valid before, become invalid and vice versa.

The following table points out some of the differences between the two types of segments where the same area of memory is mapped as an Expand Up and Expand Down segment for a given Linear Address (LA) and Segment Length (Length).

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

The two GDT/LDT rows indicate how to set the corresponding entries in those tables.

The rows labeled Smallest and Largest Offsets indicate what indices can be used to address data within the segment.

Thus, if two DTEs were setup as per the GDT/LDT rows in the above table, the same area of memory would be addressed. The only difference would be in the offsets used to reference the memory. Using the Expand Up selector, the first byte would be addressed as offset zero; using the Expand Down selector, it would be addressed as offset Modulus - Length.

An Example

If the area of memory to be mapped by the above DTEs were at Linear Address 03000000h and were 4KB (1000h) in length and the B bit in the DTE's Access Rights byte were one, then the valid offsets for the Expand Up selector would be from 0 to 4KB - 1 (0FFFh); for the Expand Down selector they would be from 4GB - 4KB (0FFFFF000h) to 4GB - 1 (0FFFFFFFFh). Note that both offset ranges have a total of 4KB valid values; they are just based differently. For reference, the LSL instruction would return 0FFFh for the Expand Up segment and 0FFFFEFFFh for the Expand Down segment.

An Anomaly

Unfortunately, there is one anomaly in the above table when it comes to the limiting cases. Normally, arithmetic in the above table is done modulo the Modulus. A segment Length can range from 1 to the Modulus, and Length of zero is treated (modulo the Modulus) as length Modulus.

Except for Expand Down segments.

Consider the case where the Length increases up to and including the Modulus. For Expand Up segments, the segment becomes larger and larger, growing upwards with the Largest Offset increasing to Modulus - 1 (which is the same as the Largest Offset for an Expand Down segment) until, at the limit, the segment Length is at its largest (Modulus). For Expand Down segments, as the Length increases, the segment becomes larger and larger, growing downwards with the Smallest Offset decreasing to zero (which is the same as the Smallest Offset for an Expand Up segment) until, at the limit, the segment is the same as the largest Expand Up segment.

But, that's not what happens.
Instead, at the limit, the Expand Down segment disappears!

By disappears, I mean there are no valid offsets within the segment. That is, regardless of what value you put in an index register (say EBX), SS:[EBX] always signals a Fault!

Let's look at the various elements of the Expand Up and Down segments more closely:

In other words, at the maximum Length, the DTEs for the two segments are identical in all respects except for the state of the Expand Down bit. However, for some reason, Intel chose to treat that case specially and for Expand Down segments at maximum Length, there are no valid offsets.

Thus for Expand Up segments, the maximum segment size occurs when Length equals Modulus at which point Limit equals Modulus - 1. But for Expand Down segments, the maxiumum size occurs when the Length is Modulus - 1, and Limit equals zero. Consequently, because of the above anomaly,

In a maximum size Expand Down segment the byte at offset zero is always unaddressible.

Actually, the above calculations are a little trickier than shown because we need to consider the effect of the G (Granularity) bit. When the G bit is one, the Length must be a multiple of 4KB, so a Length of one is impossible. Consequently, because of the above anomaly,

In a maximum size Expand Down segment with the G bit set, the bytes from 0 to 4KB - 1 are always unaddressible.

Another Limiting Case

For an Expand Down segment with B=0, if the Limit field in the GDT/LDT is from 64KB - 1 (0FFFFh) to 1MB - 1 (0FFFFFh) inclusive, the segment has no valid offsets.

Benefits

Expand Up segments have a fixed starting offset (zero), and a variable ending offset which depends upon the Segment Length. Expand Down segments reverse those concepts such that the ending offset is fixed (0FFFFh for 16-bit (B=0) segments and 0FFFFFFFFh for 32-bit (B=1) segments) and the starting offset varies depending upon the Segment Length.

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.

In short, new pointers in an Expand Up segment are larger offsets, so the segment can be expanded upwards. New pointers in an Expand Down segment are smaller offsets, so the segment can be expanded downwards.

Miscellaneous

Author

This page was created by Bob Smith -- please any questions or comments about it to me.