VOGONS


First post, by superfury

User metadata
Rank l33t++
Rank
l33t++

What happens when a stack(or any expand-down data) segment (descriptor) is indexed with a register while using a expand-down segment? Say ESP=0, it gets decreased to 0xFFFFFFC for a dword write, then writing a value to SS:FFFFFFFC. The SS segment descriptor base pointing to 0x400, what memory address is the dword write written to? Is it written to 0x400+FFFFFFFC=3FC(according to the manual, it's just being added)? Or is the limit being added as well(0x400(base)+FFFFFFFC(index)+0x400(limit)=7FC), which would make more sense(the base still pointing to the start of the memory and the stack growing down from the end))?

Edit: This article seems to imply it's just simple addition, with the limit not being used in that case at all. So pushing a dword value to that stack will simply end up at linear memory address 0x3FC?

What happens when address sizes are changed? E.g. the base pointing to a 32-bit address, with a 16-bit offset being used? Is the offset simply sign-extended to 32-bits?

Edit: I've modified the accesses using 16-bits offsets within top-down segments to be extended to 32-bits by setting bits 16-31 to 1(offset |= 0xFFFF0000; ). Is that correct behaviour? Since the address space used is 32-bits wide(it's emulating a 32-bit x86 CPU(up to 80386 atm) with a 32-bit bus after all and 32-bits to address), setting those bits should ensure that 16-bit offsets behave correctly as the conversion to 32-bits and adding the upper 32 bits(before masking off the upper 8 bits when applying the address bus in the 80286) ensures that the address is indeed 'substracted' from the specified address)?

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

Reply 1 of 4, by crazyc

User metadata
Rank Member
Rank
Member
superfury wrote:

What happens when a stack(or any expand-down data) segment (descriptor) is indexed with a register while using a expand-down segment? Say ESP=0, it gets decreased to 0xFFFFFFC for a dword write, then writing a value to SS:FFFFFFFC. The SS segment descriptor base pointing to 0x400, what memory address is the dword write written to? Is it written to 0x400+FFFFFFFC=3FC(according to the manual, it's just being added)? Or is the limit being added as well(0x400(base)+FFFFFFFC(index)+0x400(limit)=7FC), which would make more sense(the base still pointing to the start of the memory and the stack growing down from the end))?

Yes, the base plus the offset is the address (so the base is the bottom of the segment just as expand up). The limit just refers to the lowest valid address rather than the highest.

superfury wrote:

What happens when address sizes are changed? E.g. the base pointing to a 32-bit address, with a 16-bit offset being used? Is the offset simply sign-extended to 32-bits?

No sign extension. sp (esp truncated to 16 bits) is used if the descriptor D bit is 0 otherwise esp if D is 1.

Reply 2 of 4, by superfury

User metadata
Rank l33t++
Rank
l33t++

My current logic always works with 32-bit offsets(on both ESP and SP). So what it does is convert 16-bit offsets(all offsets that are 16-bit in size, determined by a new parameter sent with the offset calculator parameters) into 32-bit offsets by, when calculating expand-down segments with 16-bit offsets only, OR-ing in 0xFFFF0000 into the offset(one extending) to make it 32-bit compatible. Normal 32-bit offsets are unchanged and only added to the base offset.

Is that correct behaviour?

My code doing that:

OPTINLINE uint_32 MMU_realaddr(sword segdesc, word segment, uint_32 offset, byte wordop, byte is_offset16) //Real adress?
{
SEGMENT_DESCRIPTOR *descriptor; //For checking Expand-down data descriptors!
INLINEREGISTER uint_32 realaddress;
//word originalsegment = segment;
//uint_32 originaloffset = offset; //Save!
realaddress = offset; //Load the address!
if ((EMULATED_CPU==CPU_8086) || (EMULATED_CPU==CPU_NECV30 && !((realaddress==0x10000) && wordop))) //-NEC V20/V30 wraps offset arround 64kB? NEC V20/V30 allows 1 byte more in word operations!
{
realaddress &= 0xFFFF; //Wrap arround!
}
writeword = 0; //Reset word-write flag for checking next bytes!

if (segdesc!=-1) //valid segment descriptor?
{
descriptor = &CPU[activeCPU].SEG_DESCRIPTOR[segdesc]; //Get our using descriptor!
if ((GENERALSEGMENTPTR_S(descriptor) == 1) && (EXECSEGMENTPTR_ISEXEC(descriptor) == 0) && DATASEGMENTPTR_E(descriptor)) //Data segment that's expand-down?
{
if (is_offset16) //16-bits offset? Set the high bits for compatibility!
{
realaddress |= 0xFFFF0000; //Convert to 32-bits for adding correctly!
}
}
}
realaddress += CPU_MMU_start(segdesc, segment);

realaddress &= MMU.wraparround; //Apply A20!

if (is_XT && (EMULATED_CPU<CPU_80286)) realaddress &= 0xFFFFF; //Only 20-bits address is available on a XT without newer CPU!
else if (EMULATED_CPU==CPU_80286) realaddress &= 0xFFFFFF; //Only 24-bits is available on a AT!

//We work!
//dolog("MMU","\nAddress translation: %04X:%08X=%08X",originalsegment,originaloffset,realaddress); //Log the converted address!
latchBUS(realaddress); //This address is to be latched!
return realaddress; //Give real adress!
}

The block checking "segdesc!=-1" is the block I'm talking about. That makes sure to convert the 16-bit offsets to a 32-bit compatible value(by setting bits 16-31 to make the offset behave negative compared to the base using the overflow logic). Otherwise the offset would be positive instead of negative(base+positive offset instead of base+negative offset).

The is_XT check takes care of any remaining processor-specific(address bus) wrapping that's required(24-bits 80286/AT, 20-bits 80(1)8X and 32-bit 80386+).

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

Reply 3 of 4, by superfury

User metadata
Rank l33t++
Rank
l33t++

So, checking again after some time:

When segment base=0x10000, limit 0xFFFF(16-bit offset), offset=0x777, linear address=0x10000+0xFFFF0777=777h? Or does it need an addition of the limit(+1) as well, becoming 0x10777(add limit+1 to the linear address)?

	if (likely(segdesc!=-1)) //valid segment descriptor?
{
descriptor = &CPU[activeCPU].SEG_DESCRIPTOR[segdesc]; //Get our using descriptor!
if (unlikely((GENERALSEGMENTPTR_S(descriptor) == 1) && (EXECSEGMENTPTR_ISEXEC(descriptor) == 0) && DATASEGMENTPTR_E(descriptor))) //Data segment that's expand-down?
{
if (is_offset16) //16-bits offset? Set the high bits for compatibility!
{
realaddress |= 0xFFFF0000; //Convert to 32-bits for adding correctly!
}
//The current address is a 32-bit address, expanded from 16-bit if required.
uint_32 limit; //The limit!

limit = ((SEGDESCPTR_NONCALLGATE_LIMIT_HIGH(descriptor) << 16) | descriptor->limit_low); //Base limit!

if ((SEGDESCPTR_NONCALLGATE_G(descriptor)&CPU[activeCPU].G_Mask) && (EMULATED_CPU>=CPU_80386)) //Granularity?
{
limit = ((limit << 12) | 0xFFF); //4KB for a limit of 4GB, fill lower 12 bits with 1!
}
realaddress += (limit+1); //Add the limit to the 32-bit address for a physical address!
}
}
realaddress += CPU_MMU_start(segdesc, segment); //Add the segment base address itself!

Is that behaviour correct? So offset 0 is at 0x10000 in the example, offset 1 is at 0x10001 etc. But for a 32-bit offset linear address, it would first add be offset+limit+baseaddr, and 16-bit offset linear addresss (offset|0xFFFF0000)+limit+baseaddr? The 0xFFFF0000 makes it run back from the base address(so 0=>0, 1=>0x1, 2=>0xFFFE etc.)

So how is the base+offset applied to convert a 16-bit or 32-bit offset to linear address? Is base the end of the segment or the start of the whole block in memory(offset just simply added)?

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

Reply 4 of 4, by superfury

User metadata
Rank l33t++
Rank
l33t++

So if I understand this correctly, 32-bit offsets and 16-bit offsets don't wrap the same way?
e.g. SS:1234 vs SS:FFFF1234 don't refer to the same memory when SS is properly programmed?

Since FFFF1234 wraps around 32-bits, making it effectively be before the segment, while 16-bit offsets don't wrap(unless wrapping around 32-bit due to end of 4GB range) and address past the base address(+XXXXh), while FFFF1234h addresses 10000h-1234h=address 1234h, being before the segment base address(so 32-bit becomes before, while 16-bit still stays after)? Sounds like incompatible addressing? Is that correct? The osdev article http://wiki.osdev.org/Segment_Limits#Segmentation part on relocation using the descriptor is a bit confusing on that matter.

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