VOGONS


First post, by superfury

User metadata
Rank l33t++
Rank
l33t++

The manual I use (http://www.cs.nyu.edu/~mwalfish/classes/14fa/ … i386/s06_03.htm ) only mentions a few (examples) of the type checking performed during loading segments(e.g. using a MOV instruction) and during execution of memory accesses(using the loaded segment descriptor to resolve or fault memory accesses). Anyone has the complete list of what's done at what point? What's checked(of the type descriptor field) during load-time(MOV Segreg,XX) and what's used during normal execution(instruction fetches and normal memory accesses(e.g. MOV AX,[0000h]))? The manuals I've found isn't entirely clear on that, for some reason, even though that'll have a big impact on running software(e.g. fauling on a segment register load itself vs faulting on an instruction using a segment register that's loaded).

Anyone? Jepael? Reenigne?

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

Reply 1 of 5, by vladstamate

User metadata
Rank Oldbie
Rank
Oldbie

Did you check more in the Intel's own doc for 386?

https://pdos.csail.mit.edu/6.828/2012/readings/i386.pdf

YouTube channel: https://www.youtube.com/channel/UC7HbC_nq8t1S9l7qGYL0mTA
Collection: http://www.digiloguemuseum.com/index.html
Emulator: https://sites.google.com/site/capex86/
Raytracer: https://sites.google.com/site/opaqueraytracer/

Reply 2 of 5, by superfury

User metadata
Rank l33t++
Rank
l33t++

Something I've found out: both manuals have the same error in bit 3 of the type field(left of the E or C bits): the bits are set to 0 in both images. It should have 0 for data and 1 for code? So 0EW for data and 1CR for executable segments?

Pages 109-110 give some examples on when the type field is checked against memory or loading access, but don't say any more about other cases (it just gives examples, not the complete truth)?

This is what I currently do on every memory access by the CPU(using the already loaded descriptors in the caches):

byte CPU_MMU_checkrights_cause = 0; //What cause?
//Used by the CPU(VERR/VERW)&MMU I/O! forreading=0: Write, 1=Read normal, 3=Read opcode
byte CPU_MMU_checkrights(int segment, word segmentval, uint_32 offset, int forreading, SEGMENT_DESCRIPTOR *descriptor, byte addrtest)
{
//byte isconforming;

if (getcpumode() == CPU_MODE_PROTECTED) //Not real mode? Check rights for zero descriptors!
{
if ((segment != CPU_SEGMENT_CS) && (segment != CPU_SEGMENT_SS) && (!getDescriptorIndex(segmentval)) && ((segmentval&4)==0)) //Accessing memory with DS,ES,FS or GS, when they contain a NULL selector?
{
CPU_MMU_checkrights_cause = 1; //What cause?
return 1; //Error!
}
}

//First: type checking!

if (GENERALSEGMENTPTR_P(descriptor)==0) //Not present(invalid in the cache)?
{
CPU_MMU_checkrights_cause = 2; //What cause?
if (segment==CPU_SEGMENT_SS) //Stack fault?
{
return 3; //Stack fault!
}
else
{
return 2; //#NP!
}
}

if (getcpumode()==CPU_MODE_PROTECTED) //Not real mode? Check rights!
{
switch (((descriptor->AccessRights>>1)&0x7)) //What type of descriptor?
{
case 0: //Data, read-only
case 2: //Data(expand down), read-only
case 5: //Code, execute/read
case 7: //Code, execute/read, conforming
if (forreading==0) //Writing?
{
CPU_MMU_checkrights_cause = 3; //What cause?
return 1; //Error!
}
break; //Allow!
case 1: //Data, read/write
case 3: //Data(expand down), read/write
break; //Allow!
case 4: //Code, execute-only
case 6: //Code, execute-only, conforming
if (forreading!=3) //Writing or reading normally?
{
CPU_MMU_checkrights_cause = 3; //What cause?
return 1; //Error!
}
break; //Allow!
}
}

//Next: limit checking!

Show last 61 lines
	uint_32 limit; //The limit!
byte isvalid;

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!
}

if (addrtest) //Execute address test?
{
isvalid = (offset<=limit); //Valid address range!
if ((GENERALSEGMENTPTR_S(descriptor) == 1) && (EXECSEGMENTPTR_ISEXEC(descriptor) == 0)) //Data segment?
{
if (DATASEGMENTPTR_E(descriptor)) //Expand-down data segment?
{
isvalid = !isvalid; //Reversed valid!
if (SEGDESCPTR_NONCALLGATE_G(descriptor) == 0) //Small granularity?
{
isvalid = (isvalid && (offset <= 0x10000)); //Limit to 64K!
}
}
}
if (!isvalid) //Not valid?
{
CPU_MMU_checkrights_cause = 6; //What cause?
if (segment==CPU_SEGMENT_SS) //Stack fault?
{
return 3; //Error!
}
else //Normal #GP?
{
return 1; //Error!
}
}
}

//Third: privilege levels & Restrict access to data!

/*
switch (descriptor->AccessRights) //What type?
{
case AVL_CODE_EXECUTEONLY_CONFORMING:
case AVL_CODE_EXECUTEONLY_CONFORMING_ACCESSED:
case AVL_CODE_EXECUTE_READONLY_CONFORMING:
case AVL_CODE_EXECUTE_READONLY_CONFORMING_ACCESSED: //Conforming?
isconforming = 1;
break;
default: //Not conforming?
isconforming = 0;
break;
}
*/

//Don't perform rights checks: This is done when loading the segment register only!

//Fifth: Accessing data in Code segments?

return 0; //OK!
}

And this is what's done (type checking) on loading the segment registers themselves with new values(loading the caches):

	//Handle invalid types now!
if ((segment==CPU_SEGMENT_CS) &&
(getLoadedTYPE(&LOADEDDESCRIPTOR)!=1) //Data or System in CS (non-exec)?
)
{
THROWDESCGP(segmentval,0,(segmentval&4)?EXCEPTION_TABLE_LDT:EXCEPTION_TABLE_GDT); //Throw #GP error!
return NULL; //Not present: invalid descriptor type loaded!
}
else if ((getLoadedTYPE(&LOADEDDESCRIPTOR)==1) && (segment!=CPU_SEGMENT_CS) && (EXECSEGMENT_R(LOADEDDESCRIPTOR.desc)==0)) //Executable non-readable in non-executable segment?
{
THROWDESCGP(segmentval,0,(segmentval&4)?EXCEPTION_TABLE_LDT:EXCEPTION_TABLE_GDT); //Throw #GP error!
return NULL; //Not present: invalid descriptor type loaded!
}
else if (getLoadedTYPE(&LOADEDDESCRIPTOR)==0) //Data descriptor loaded?
{
if ((segment!=CPU_SEGMENT_DS) && (segment!=CPU_SEGMENT_ES) && (segment!=CPU_SEGMENT_FS) && (segment!=CPU_SEGMENT_GS) && (segment!=CPU_SEGMENT_SS)) //Data descriptor in invalid type?
{
THROWDESCGP(segmentval,0,(segmentval&4)?EXCEPTION_TABLE_LDT:EXCEPTION_TABLE_GDT); //Throw #GP error!
return NULL; //Not present: invalid descriptor type loaded!
}
if ((DATASEGMENT_W(LOADEDDESCRIPTOR.desc)==0) && (segment==CPU_SEGMENT_SS)) //Non-writable SS segment?
{
THROWDESCGP(segmentval,0,(segmentval&4)?EXCEPTION_TABLE_LDT:EXCEPTION_TABLE_GDT); //Throw #GP error!
return NULL; //Not present: invalid descriptor type loaded!
}
}
else if (getLoadedTYPE(&LOADEDDESCRIPTOR)==2) //System descriptor loaded?
{
if ((segment==CPU_SEGMENT_DS) || (segment==CPU_SEGMENT_ES) || (segment==CPU_SEGMENT_FS) || (segment==CPU_SEGMENT_GS) || (segment==CPU_SEGMENT_SS)) //System descriptor in invalid register?
{
THROWDESCGP(segmentval,0,(segmentval&4)?EXCEPTION_TABLE_LDT:EXCEPTION_TABLE_GDT); //Throw #GP error!
return NULL; //Not present: invalid descriptor type loaded!
}
}

After that it updates the cache with the new data and performs any post-loading actions(copying stack, loading other registers required for a privilege switch etc.).

Checking for data segments loaded into a code segment can't be done in real (or virtual 8086 mode), due to problems with compatibility. Since CS is loaded with access rights byte 0x93, it would cause all code fetches to be invalid when checked at runtime(since it's actually a data descriptor instead of a code descriptor, but it's set up that way by the CPU itself when loading CS with any segment (selector)).

Checking CS for executable type gives problems when entering protected mode (directly after MOV CR0,XX, it would crash because CS holds selector 0x93, which is a data segment. This would result in a triple fault in most cases, due to unavailable or not completely initialized tables in memory, as well as the CS still needing to be loaded(containing a real-mode selector still, instead of it's protected mode version).

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

Reply 3 of 5, by vladstamate

User metadata
Rank Oldbie
Rank
Oldbie

It is 0 for data and 1 for code. Figure 6-1 in the link you gave has the correct bits for both data and executable segments. Do you mean a different image?

YouTube channel: https://www.youtube.com/channel/UC7HbC_nq8t1S9l7qGYL0mTA
Collection: http://www.digiloguemuseum.com/index.html
Emulator: https://sites.google.com/site/capex86/
Raytracer: https://sites.google.com/site/opaqueraytracer/

Reply 4 of 5, by superfury

User metadata
Rank l33t++
Rank
l33t++

The cs.nyu.edu link tells me as I've implemented it. The pdf file linked to by vladstamate instead tells 10CRA instead of the (probably correct) 11CRA fields? They're both supposed to be the same manual. Perhaps the html version is a newer revision?

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

Reply 5 of 5, by vladstamate

User metadata
Rank Oldbie
Rank
Oldbie

Yes I see what you mean, at page 108 there is definitely an error. Like you said, the HTML version is likely newer.

YouTube channel: https://www.youtube.com/channel/UC7HbC_nq8t1S9l7qGYL0mTA
Collection: http://www.digiloguemuseum.com/index.html
Emulator: https://sites.google.com/site/capex86/
Raytracer: https://sites.google.com/site/opaqueraytracer/