Intel x86 Assembly Language & Microarchitecture
Real vs Protected-lägen
Sök…
Real Mode
När Intel designade originalet x86, 8086 (och 8088-derivatet), inkluderade de Segmentering för att möjliggöra för 16-bitars processor att få tillgång till mer än 16 bitar värd adress. De gjorde detta genom att göra 16-bitars adresserna relativt till ett givet 16-bitars segmentregister, av vilka de definierade fyra: kodsegment ( CS ), datasegment ( DS ), extra segment ( ES ) och stack segment ( SS ) .
De flesta instruktioner antydde vilket segmentregister som ska användas: instruktioner lades fram från kodsegmentet, PUSH och POP antydde stapelsegmentet, och enkla datareferenser antydde datasegmentet - även om detta kunde åsidosättas för att få åtkomst till minne i något av de andra segmenten.
Implementeringen var enkel: för varje minnesåtkomst skulle CPU ta det underförstådda (eller uttryckliga) segmentregistret, flytta fyra platser till vänster och sedan lägga till den angivna adressen:
+-------------------+---------+
Segment | 16-bit value | 0 0 0 0 |
+-------------------+---------+
PLUS
+---------+-------------------+
Address | 0 0 0 0 | 16-bit value |
+---------+-------------------+
EQUALS
+-----------------------------+
Result | 20-bit memory address |
+-----------------------------+
Detta tillät olika tekniker:
- Att låta alla kod, data och stack vara ömsesidigt åtkomliga (
CS,DSochSSalla samma värde); - Att hålla kod, data och stack helt separerade från varandra (
CS,DSochSSalla 4K (eller mer) separerade från varandra - kom ihåg att det multipliceras med 16, så det är 64K).
Det tillät också bisarra överlappningar och alla slags konstiga saker!
När 80286 uppfanns stöder det detta äldre läge (nu kallat "Real Mode"), men lägger till ett nytt läge som heter "Protected Mode" (qv).
De viktiga sakerna att märka är att i Real Mode:
- Varje minnesadress var tillgänglig, helt enkelt genom att placera rätt värde i ett segmentregister och komma åt 16-bitarsadressen;
- Omfattningen av "skydd" var att låta programmeraren att separera olika minnesområden för olika ändamål och göra det svårare att av misstag skriva till fel data - och ändå göra det möjligt att göra det.
Med andra ord ... inte särskilt skyddad alls!
Skyddat läge
Introduktion
När 80286 uppfanns stöttade det den äldre 8086-segmenteringen (nu kallad "Real Mode") och lade till ett nytt läge som heter "Protected Mode". Detta läge har funnits i varje x86-processor sedan, om än förbättrat med olika förbättringar som 32- och 64-bitars adressering.
Design
I skyddat läge avlägsnades det enkla "Lägg till adress till skiftat segmentregistervärde" helt. De höll segmentregistret, men istället för att använda dem för att beräkna en adress, använde de dem för att indexera till en tabell (faktiskt en av två ...) som definierade segmentet som ska nås. Denna definition beskrev inte bara var i minnet segmentet var (med hjälp av Base och Limit), utan också vilken typ av segment det var (kod, data, stack eller till och med system) och vilka typer av program som kunde komma åt det (OS Kernel, normalt program , Enhetsdrivrutin, etc.).
Segmentregister
Varje 16-bitars segmentregister tog följande form:
+------------+-----+------+
| Desc Index | G/L | Priv |
+------------+-----+------+
Desc Index = 13-bit index into a Descriptor Table (described below)
G/L = 1-bit flag for which Descriptor Table to Index: Global or Local
Priv = 2-bit field defining the Privilege level for access
Global / lokal
Den globala / lokala biten definierade om åtkomsten var i en global tabell över deskriptorer (som inte förvånansvärt kallas Global Descriptor Table eller GDT) eller en Local Descriptor Table (LDT). Idén för LDT var att varje program kunde ha sin egen beskrivningstabell - OS definierar en global uppsättning segment, och varje program skulle ha sin egen uppsättning lokala kod-, data- och stapelsegment. OS skulle hantera minnet mellan de olika beskrivningstabellerna.
Beskrivningstabell
Varje beskrivningstabell (global eller lokal) var en 64K-grupp med 8 192 deskriptorer: var och en 8-byte-post som definierade flera aspekter av segmentet som det beskrev. Segmentregistrarnas deskriptorindexfält tillåter 8 192 deskriptorer: ingen slump!
Descriptor
En deskriptor innehöll följande information - notera att formatet för deskriptorn ändrades när nya processorer släpptes, men samma typ av information bevarades i varje:
- Bas
Detta definierade startadressen för minnessegmentet. - Begränsa
Detta definierade storleken på minnessegmentet - typ av. De var tvungna att fatta ett beslut: skulle en storlek på0x0000betyda en storlek på0, så inte tillgänglig? Eller maximal storlek?
Istället valde de ett tredje alternativ: fältet Begränsning var den sista adresserbara platsen inom segmentet. Det innebar att ett enskilt segment kunde definieras; eller en maximal storlek för adressstorleken. - Typ
Det fanns flera typer av segment: traditionell kod, data och stack (se nedan), men andra systemsegment definierades också:- Lokala deskriptorstabelsegment definierade hur många lokala deskriptorer som skulle kunna nås;
- Uppgiftstillståndssegment kan användas för hårdvarahanterad kontextväxling;
- Kontrollerade "Call Gates" som kan göra det möjligt för program att ringa in i operativsystemet - men bara genom noggrant hanterade ingångspunkter.
- attribut
Vissa attribut för segmentet bibehölls också, där det var relevant:- Read-Only vs Read-Writ;
- Oavsett om segmentet för närvarande var närvarande eller inte - möjliggör minneshantering på begäran;
- Vilken nivå av kod (OS vs Driver vs program) kan komma åt detta segment.
Äntligen skydd!
Om operativsystemet behöll beskrivningstabellerna i segment som inte kunde nås med bara program, skulle det kunna hantera vilka segment som definierades och vilket minne som tilldelades och var tillgängligt för var och en. Ett program kunde tillverka det Segment Register-värde som det gillade - men om det hade den modiga att faktiskt ladda det i ett Segment Register ! ... CPU-hårdvaran skulle erkänna att det föreslagna Descriptor-värdet bröt någon av ett stort antal regler, och istället för att fullfölja begäran, skulle det höja ett undantag för operativsystemet så att det kan hantera det felaktiga programmet.
Detta undantag var vanligtvis nr 13, det allmänna skyddsundantaget - gjort världsberömt av Microsoft Windows ... (Någon som tror att en Intel-ingenjör var vidskeplig?)
fel
De typer av fel som kan hända inkluderade:
Om det föreslagna deskriptorindexet var större än tabellens storlek;
Om den föreslagna deskriptorn var en systembeskrivare snarare än kod, data eller stack;
Om den föreslagna deskriptorn var mer privilegierad än det begärande programmet;
Om den föreslagna deskriptorn var markerad som inte läsbar (t.ex. ett kodsegment), men det försökte läsas snarare än körs;
Om den föreslagna beskrivaren var markerad Inte närvarande.
Observera att det sista kanske inte är ett dödligt problem för programmet: OS kan notera flaggan, återställa segmentet, markera det som nu och låt felinstruktionen fortsätta.
Eller kanske beskrivaren laddades framgångsrikt i ett segmentregister, men då bröt en framtida åtkomst med den en av ett antal regler:
- Segmentregistret laddades med
0x0000Descriptor Index för GDT. Detta reserverades av hårdvaran somNULL; - Om den laddade beskrivaren var markerad skrivskyddad, men en skrivning försökte.
- Om någon del av åtkomsten (1, 2, 4 eller fler byte) låg utanför segmentets gräns.
Växlar till skyddat läge
Att växla till skyddat läge är enkelt: du behöver bara ställa in en enda bit i ett kontrollregister. Men att stanna i skyddat läge, utan att CPU: n kastar upp sina händer och återställer sig på grund av att inte veta vad man ska göra härnäst, kräver mycket förberedelser.
Kort sagt är de steg som krävs enligt följande:
Ett minnesområde för Global Descriptor-tabellen måste ställas in för att definiera minst tre deskriptorer:
- Nollet,
NULLdeskriptor; - En annan beskrivare för ett kodsegment;
- En annan beskrivare för ett datasegment.
Detta kan användas för både Data och Stack.
- Nollet,
Global Descriptor Table Register (
GDTR) måste initieras för att peka på detta definierade minnesområde;GDT_Ptr dw SIZE GDT dd OFFSET GDT ... lgdt [GDT_Ptr]PMbiten iCR0måste ställas in:mov eax, cr0 ; Get CR0 into register or eax, 0x01 ; Set the Protected Mode bit mov cr0, eax ; We're now in Protected Mode!Segmentregistren måste laddas från GDT för att ta bort de aktuella verkliga lägesvärdena:
jmp 0x0008:NowInPM ; This is a FAR Jump. 0x0008 is the Code Descriptor NowInPM: mov ax, 0x0010 ; This is the Data Descriptor mov ds, ax mov es, ax mov ss, ax mov sp, 0x0000 ; Top of stack!
Observera att detta är det absoluta minimumet, bara för att få CPU till skyddat läge. För att faktiskt göra hela systemet klart kan det krävas många fler steg. Till exempel:
- De övre minnesområdena kanske måste vara aktiverade - stänga av
A20grinden; - Avbrytningarna bör definitivt inaktiveras - men kanske de olika felhanterarna kan ställas in innan de går in i skyddat läge för att möjliggöra fel tidigt i behandlingen.
Den ursprungliga författaren till detta avsnitt skrev en hel tutorial om att gå in i skyddat läge och arbeta med det.
Unreal-läget
Det oerkliga läget utnyttjar två fakta om hur både Intel- och AMD-processorer laddar och sparar informationen för att beskriva ett segment.
Processorn cachar den beskrivningsinformation som hämtas under en rörelse i ett väljarregister i skyddat läge.
Denna information lagras i en arkitektonisk osynlig del av själva väljarregistret.I verkligt läge kallas väljarregistret segmentregister, men annat än de betecknar samma uppsättning register och som sådan har de också en osynlig del. Dessa delar är fyllda med fasta värden, men för basen som härrör från det värde som just laddats.
I en sådan vy är verkligt läge bara ett speciellt fall av skyddat läge: där informationen för ett segment, till exempel bas och gräns, hämtas utan en GDT / LDT men fortfarande läses från dolda segmentregistret.
Genom att växla i skyddat läge och skapa en GDT är det möjligt att skapa ett segment med önskade attribut, till exempel en bas av 0 och en gräns på 4GiB.
Genom en successiv lastning av ett väljarregister cachas sådana attribut, det är då möjligt att växla tillbaka i verkligt läge och ha ett segmentregister genom vilket åtkomst till hela 32-bitars adressutrymme.
BITS 16
jmp 7c0h:__START__
__START__:
push cs
pop ds
push ds
pop ss
xor sp, sp
lgdt [GDT] ;Set the GDTR register
cli ;We don't have an IDT set, we can't handle interrupts
;Entering protected mode
mov eax, cr0
or ax, 01h ;Set bit PE (bit 0) of CR0
mov cr0, eax ;Apply
;We are now in Protected mode
mov bx, 08h ;Selector to use, RPL = 0, Table = 0 (GDT), Index = 1
mov fs, bx ;Load FS with descriptor 1 info
mov gs, bx ;Load GS with descriptor 1 info
;Exit protected mode
and ax, 0fffeh ;Clear bit PE (bit0) of CR0
mov cr0, eax ;Apply
sti
;Back to real mode
;Do nothing
cli
hlt
GDT:
;First entry, number 0
;Null descriptor
;Used to store a m16&32 object that tells the GDT start and size
dw 0fh ;Size in byte -1 of the GDT (2 descriptors = 16 bytes)
dd GDT + 7c00h ;Linear address of GDT start (24 bits)
dw 00h ;Pad
dd 0000ffffh ;Base[15:00] = 0, Limit[15:00] = 0ffffh
dd 00cf9200h ;Base[31:24] = 0, G = 1, B = 1, Limit[19:16] = 0fh,
;P = 1, DPL = 0, E = 0, W = 1, A = 0, Base[23:16] = 00h
TIMES 510-($-$$) db 00h
dw 0aa55h
överväganden
- Så snart ett segmentregister laddas om, även med samma värde, laddar processorn in de dolda attributen enligt det aktuella läget. Det är därför koden ovan använder
fsochgsatt hålla de "utökade" segmenten: sådana register är mindre benägna att användas / sparas / återställs av de olika 16-bitars-tjänsterna. -
lgdtinstruktionen laddar inte en långt pekare till GDT, istället laddar den en 24 bitars (kan åsidosättas till 32 bitars) linjär adress . Detta är inte en nära adress , det är den fysiska adressen (eftersom sökning måste vara inaktiverad). Det är anledningen tillGDT+7c00h. - Programmet ovan är en startladdare (för MBR, den har ingen BPB) som ställer in
cs/ds/sstp 7c00h och startar platsräknaren från 0. Så en byte i offset X i filen är på offset X i segmentet 7c00h och vid den linjära adressen 7c00h + X. - Avbrott måste inaktiveras eftersom en IDT inte är inställd för den korta rundresan i skyddat läge.
- Koden använder en hack för att spara 6 byte med kod. Strukturen laddad av
lgdtsparas i själva ... GDT, i null-deskriptorn (den första deskriptorn).
För en beskrivning av GDT-deskriptorerna, se kapitel 3.4.3 i Intel Manual Volume 3A .