Memory management dei processori i386 in protected mode - ... tuffiamoci nell'architettura Intel ...
Pubblicato da Ritz il 19/11/2000
Livello avanzato
Introduzione
--== LETTERATURA ==--
Molte delle info che trovate in questo tutorial sono state prese dai seguenti libri.
* "Volume 3: System Programming Guide", dell'"Intel Architecture Software Devloper's Manual", scaricabile dal sito della Intel nella sezione developer, Order Number 243192.
Anche i volumi 1 ("Basic Architecture", Order Number 243190) e 2 ("Instruction Set Reference Manual", Order Number 243191) di tale manuale possono tornare utili.
Per quanto riguarda le tabelle, invece, esse sono state spudoratamente copiate dai testi sopra citati :).
--== INTRO ==--
Salve a tutti.
Come penso avrete gia' capito questo tutorial non avra', come solitamente accade, una scopo pratico, bensi' il suo unico proposito e' quello di spiegare il funzionamento in protected mode dei processori i386.
Parlero' delle tavole di descrittori, dei meccanismi di paginazione e segmentazione, di exception handling, and so on. Ad ogni modo, in alcuni ambiti queste informazioni possono cmq risultare utili, come ad esempio nel caso si desideri scendere a ring0 da win9x.
Nonostante questo, non daro' esempi pratici di come ottenere cio', sia perche' cosi' vi divertire un po' a farlo da soli (evabbe' sono bastardo lo so ;) ), ma soprattutto perche', visto che questo tutorial e' soprattutto per il RACL (http://racl.oltrelinux.com) non mi pare il caso di soffermi su sistemi della M$.
Ecco quindi che cerchero' di essere il + generale possibile.
Tanto per fare un esempio per chiarire come alcuni aspetti siano applicabili solo in ambienti specifici, se sotto win potete scendere a ring0 semplicemente utilizzando metodi quali call gate, int gate e cosi' via perche' gdt, ldt e idt (spieghero' tutto sotto) sono modificabili a ring3 (se volete sull'asj 1 trovate il relativo tute), in Linux cio' non e' possibile perche' essendo quest'ultimo un SO serio e sicuro non mette tali strutture in pagine a ring3 ma le protegge da eventuali modifiche.
Detto questo, let's start!
Iniziamo
--== MEMORY MANAGEMENT NELLE ARCHITETTURE i386 ==--
Come tutti probabilmente saprete, lo stato nativo di un i386 e' il protected
mode. Le principali caratteristiche di tale tipo di memory management sono 2:
segmentazione e paging.
PAGING
Tramite il paging si ha la possiblilta' di eseguire un dato processo utilizzando
un sistema di "memoria virtuale" in cui le varie parti in cui viene diviso il
programma (chiamate pagine, che solitamente sono di 4KB) vegnono mappate nella
memoria fisica solo quando ce n'e' effettivamente bisogno. Tale meccanismo puo'
essere cmq disattivato.
Il paging e' controllato da 3 flag nei registri di controllo:
1- Il bit 31 di CR0 (flag PG). Questo flag e' responsabile dell'utilizzo della
paginazione e viene settato solitamente dal SO durante l'inizializzazione.
2- Il bit 4 di CR4 (flag PSE, page flag extension). Questo flag serve a
specificare l'utilizzo di pagine di 4MB o 2 MB (se il flag PAE e' settato). Se
tale flag e' spento, viene utilizzata per le pagine la classica grandezza di
4KB.
3- Il bit 5 di CR4 (flag PAE, physical address extension). Questo bit permette
di utilizzare indirizzi fisici di 36bit al posto dei comuni 32bit. Ne parlero' +
avanti.
Se e' attivata la paginazione, il processore per poter ricavare l'indirizzo
fisico di memoria partendo da quello lineare deve utilizzare alcune strutture,
ovvero
. Page directory: un array di PDE (page-directory entries) contenute in una
pagina di 4KB. Una page dir puo' contenere fuino a 1024 entry
. Page table: un array di PTE (page-table entries) contenute in una pagina di
4KB. Una page table puo' contenere fino a 1024 entry. NOTA: le page table non
sono usate per pagine di 4 o 2 MB.
. Page: la pagina fisica:).
. Page-Directory-Pointer Table: un array di entry a 64-bit ognuna delle qualli
punta a una page dir. Questa struttura non e' usata se si sta utilizzando il
phys. addr. extension.
Senza spiegare tutte le situazioni in cui si puo' capitare faccio di seguito uno
schemino che sicuramente sara' + esplicativo delle parole.
+---------+----------+----------+---------+------+---------------+ | Flag PG | PAE Flag | PSE Flag | PS Flag | Page | Physical | | CR0 | CR4 | PDE | PDE | Size | Address Space | +---------+----------+----------+---------+------+---------------+ | 0 | x | x | x | / |Paging Disabled| +---------+----------+----------+---------+------+---------------+ | 1 | 0 | 0 | x | 4 KB | 32 bit | +---------+----------+----------+---------+------+---------------+ | 1 | 0 | 1 | 0 | 4 KB | 32 bit | +---------+----------+----------+---------+------+---------------+ | 1 | 0 | 1 | 1 | 4 MB | 32 bit | +---------+----------+----------+---------+------+---------------+ | 1 | 1 | x | 0 | 4 KB | 36 bit | +---------+----------+----------+---------+------+---------------+ | 1 | 1 | x | 1 | 2 MB | 36 bit | +---------+----------+----------+---------+------+---------------+Ma cosa accade una volta che, attivato il paging, si vuole ricavare un indirizzo fisico da uno lineare? Se le pagine utilizzate sono di 4KB, il linear address si presenta cosi':
31 22 21 12 11 0 +----------+----------+------------+ | Directory| Table | Offset | +----------+----------+------------+L'entry "Directory" offre un offset ad un'entry nella page dir. Tale entry dara' poi il base physical address della page table.
31 12 11 9 8 7 6 5 4 3 2 1 0 +-------------------------+-----+---+---+---+---+---+---+---+---+---+ | | | | | | | P | P | U | R | | | Page-Table Base Address |Avail| G | PS| 0 | A | C | W | / | / | P | | | | | | | | D | T | S | W | | +-------------------------+-----+---+---+---+---+---+---+---+---+---+Ora un disegnino pure delle Page Table entry con pagine a 4KB.
31 12 11 9 8 7 6 5 4 3 2 1 0 +-------------------------+-----+---+---+---+---+---+---+---+---+---+ | | | | | | | P | P | U | R | | | Page-Table Base Address |Avail| G | 0 | D | A | C | W | / | / | P | | | | | | | | D | T | S | W | | +-------------------------+-----+---+---+---+---+---+---+---+---+---+Ora un altro disegnino di una Page Dir entry con pagine a 4MB.
31 22 21 12 11 9 8 7 6 5 4 3 2 1 0 +--------------+----------+-----+---+---+---+---+---+---+---+---+---+ | Page | | | | | | | P | P | U | R | | | Base | Reserved |Avail| G | PS| D | A | C | W | / | / | P | | Address | | | | | | | D | T | S | W | | +--------------+----------+-----+---+---+---+---+---+---+---+---+---+E ora la spiegazione dei relativi campi e flag + importanti (non li metto proprio tutti).
Physical Address Extension
Quando il physical address extension viene attivato, vengono apportati questi
cambiamenti nelle strutture inerenti al paging.
31 30 29 21 20 12 11 0 +-----+---------+----------+------------+ | |Directory| Table | Offset | +-----+---------+----------+------------+ ^ | +---------- Directory PointerPer tradurre un indirizzo lineare in uno fisico con pagine da 4 KB, vengono utilizzati i bit 30 e 31 della entry Page-dir-pointer-table per ricavare una delle 4 entry nella page-dir-pointer table. Tale entry dara' il base address fisico di una page dir. A questo verra' aggiunto l'offset specificato nel campo "Page directory entry" (bit 21 -> 29) dell'indirrizzo lineare, in modo da ricavare un'entry nella page dir selezionata. Da qui verra' ottenuto i base address fisico di una page table.
Page-Directory-Pointer-Table Entry 63 36 35 32 +---------------------------------------------------+---------------+ | | | | Reserved (set to 0) | Base Address | | | | +---------------------------------------------------+---------------+ 31 12 11 9 8 5 4 3 2 1 0 +-------------------------+-----+---------------+---+---+-------+---+ | | | | P | P | | | |Page-Directory Basse Addr|Avail| Reserved | C | W | Res | 1 | | | | | D | T | | | +-------------------------+-----+---------------+---+---+-------+---+ Page-Directory Entry (4KB Page Table) 63 36 35 32 +---------------------------------------------------+---------------+ | | | | Reserved (set to 0) | Base Address | | | | +---------------------------------------------------+---------------+ 31 12 11 9 8 7 6 5 4 3 2 1 0 +-------------------------+-----+---+---+---+---+---+---+---+---+---+ | | | | | | | P | P | U | R | | |Page-Directory Basse Addr|Avail| 0 | 0 | 0 | A | C | W | / | / | P | | | | | | | | D | T | S | W | | +-------------------------+-----+---+---+---+---+---+---+---+---+---+ Page-Table Entry (4KB Page Table) 63 36 35 32 +---------------------------------------------------+---------------+ | | | | Reserved (set to 0) | Base Address | | | | +---------------------------------------------------+---------------+ 31 12 11 9 8 7 6 5 4 3 2 1 0 +-------------------------+-----+---+---+---+---+---+---+---+---+---+ | | | | | | | P | P | U | R | | |Page-Directory Basse Addr|Avail| G | 0 | D | A | C | W | / | / | P | | | | | | | | D | T | S | W | | +-------------------------+-----+---+---+---+---+---+---+---+---+---+ Page-Directory-Pointer-Table Entry 63 36 35 32 +---------------------------------------------------+---------------+ | | | | Reserved (set to 0) | Base Address | | | | +---------------------------------------------------+---------------+ 31 12 11 9 8 5 4 3 2 1 0 +-------------------------+-----+---------------+---+---+-------+---+ | | | | P | P | | | |Page-Directory Basse Addr|Avail| Reserved | C | W | Res | 1 | | | | | D | T | | | +-------------------------+-----+---------------+---+---+-------+---+ Page-Directory Entry (2MB Page Table) 63 36 35 32 +---------------------------------------------------+---------------+ | | | | Reserved (set to 0) | Base Address | | | | +---------------------------------------------------+---------------+ 31 21 20 12 11 9 8 7 6 5 4 3 2 1 0 +-----------+-------------+-----+---+---+---+---+---+---+---+---+---+ | Page | | | | | | | P | P | U | R | | | Base | Reserved (0)|Avail| G | 1 | D | A | C | W | / | / | P | | Address | | | | | | | D | T | S | W | | +-----------+-------------+-----+---+---+---+---+---+---+---+---+---+Nel caso di pagine a 2MB la page dir pointer table entry e' identica a quella per pagine a 4KB.
SEGMENTATION
Il meccanismo della segmentazione, invece, consente di dividere lo spazio di
memoria indirizzabile, ovvero quello lineare, in spazi di indirizzamento piu'
piccoli, chiamati segmenti. Ogni segmento puo' esser usato per contenere codice,
dati o stack di un programma o per contenere strutture quali LDT. La
segmentazione, al contrario del paging, *non* puo' essere disattivata.
Di conseguenza, affinche' un dato processo possa accedere a un byte in un dato
spazio di memoria (contenuto nel linear address space) deve fornire al
processore un indirizzo logico, composto a un segment selector (un selettore di
16 bit) e un offset di 32 bit.
Il selettore e' un identificatore *unico* per ogni segmento, che oltre ad altre
cose consiste in un offset che punta ad un dato descrittore (sempre
caratteristico di quel segmento) all'interno della GDT (Global Descriptor Table,
Tavola dei Descrittori Globale). Il compito del processore e' quello, dato un
selettore, di individuare il corrispondente descrittore nella GDT e da esso
ricavare il base address del segmento corrispondente. Aggiungendo al base
address del segmento l'offset indicato nell'indirizzo logico si ricavera'
l'indirizzo lineare a cui si vuole accedere.
Schematicamente,
+INDIRIZZO LOGICO+---->(del tipo 1234:12345678, ad ex CS:EIP o DS:ESI) +---------+------+ SELETTORE OFFSET | | +-----+ | +--------------------->| + |-------> INDIRIZZO LINEARE nel Linear Address Space | +-----+ ^^^^^^^^^^^^^^^^^ | ^ | | | +-------+ | +---->| GDT |----- BASE ADDRESS -+ +-------+ ^^^^^^^^^^^^Lo schemino e' molto semplice (a dire il vero pensavo mi venisse peggio;P ) e non fa alcun riferimento a meccanismi quali paging che dall'indirizzo lineare ricavano quello fisico in cui la pagina e' stata mappata, btw a noi cio' non interessa visto che ne abbiamo gia' parlato:).
15 3 2 1 0 +--------------------------------------------------+----+-------+ | | | | | Index | TI | R P L | | | | | +--------------------------------------------------+----+-------+* Index, bit 3 --> 15
31 24 23 22 21 20 19 16 15 1413 12 11 8 7 0 +----------------+--+--+--+--+-------+--+----+--+--------+---------------+ | | | D| | A| Seg | | D | | | | | Base 31:24 | G| /| 0| L| Limit | P| P | S| Type | Base 23:16 | | | | B| | V| 19:16 | | L| | | | +----------------+--+--+--+--+-------+--+----+--+--------+---------------+ 31 16 15 0 +------------------------------------+------------------------------------+ | | | | Base Address 15:00 | Segment Limit 15:00 | | | | +------------------------------------+------------------------------------+* Segment Limit field, bit 0 --> 15 (1a dw) + 16 --> 19 (2a dw)
+-----------------+------------------+-----------------------------------------+ | Type Field | | | +----+----+---+---+ Descriptor | | | 11 | 10 | 9 | 8 | Type | Description | | | E | W | A | | | +----+----+---+---+------------------+-----------------------------------------+ | 0 | 0 | 0 | 0 | Data | Read-Only | | 0 | 0 | 0 | 1 | Data | Read-Only, accessed | | 0 | 0 | 1 | 0 | Data | Read/Write | | 0 | 0 | 1 | 1 | Data | Read/Write, accessed | | 0 | 1 | 0 | 0 | Data | Read-Only, expand-down | | 0 | 1 | 0 | 1 | Data | Read-Only, expand down, accessed | | 0 | 1 | 1 | 0 | Data | Read/Write, expand-down | | 0 | 1 | 1 | 1 | Data | Read/Write, expand-down, accessed | +----+----+---+---+------------------+-----------------------------------------+ | | C | R | A | | | +----+----+---+---+------------------+-----------------------------------------+ | 1 | 0 | 0 | 0 | Code | Execute-Only | | 1 | 0 | 0 | 1 | Code | Execute-Only, accessed | | 1 | 0 | 1 | 0 | Code | Execute/Read | | 1 | 0 | 1 | 1 | Code | Execute/Read, accessed | | 1 | 1 | 0 | 0 | Code | Execute-Only, conforming | | 1 | 1 | 0 | 1 | Code | Execute-Only, conforming, accessed | | 1 | 1 | 1 | 0 | Code | Execute/Read-Only, conforming | | 1 | 1 | 1 | 1 | Code | Execute/Read-Only, conforming, accessed | +----+----+---+---+------------------+-----------------------------------------+Quando invece l'S flag non e' settato, il descrittore e' di tipo system. Ecco di seguito alcuni tipi di descrittori di sistema.
+-----------------+-----------------------+ | Type field | | +----+----+---+---+ Description | | 11 | 10 | 9 | 8 | | +----+----+---+---+-----------------------+ | 0 | 0 | 0 | 0 | Reserved | | 0 | 0 | 0 | 1 | 16-bit TSS (Avaiable) | | 0 | 0 | 1 | 0 | LDT | | 0 | 0 | 1 | 1 | 16-bit TSS (Busy) | | 0 | 1 | 0 | 0 | 16-bit Call Gate | | 0 | 1 | 0 | 1 | Task Gate | | 0 | 1 | 1 | 0 | 16-Bit Interrupt Gate | | 0 | 1 | 1 | 1 | 16-bit Trap Gate | | 1 | 0 | 0 | 0 | Reserved | | 1 | 0 | 0 | 1 | 32-bit TSS (Avaiable) | | 1 | 0 | 1 | 0 | Reserved | | 1 | 0 | 1 | 1 | 32-bit TSS (Busy) | | 1 | 1 | 0 | 0 | 32-bit Call Gate | | 1 | 1 | 0 | 1 | Reserved | | 1 | 1 | 1 | 0 | 32-bit Interrupt Gate | | 1 | 1 | 1 | 1 | 32-bit Trap Gate | +----+----+---+---+-----------------------+
--== MECCANISMI DI PROTEZIONE IN PROTECTED MODE ==--
Ogni volta che in una qualsiasi situazione avviene un riferimento alla memoria i
processori i386 eseguono svariati check di vario tipo in parallelo con la
traduzione degli indirizzi (senza quindi che ci sia perdita di cicli). Tali
check si dividono in:
Privilege Level Checking in riferimento ai data segment
Per accedere ai dati di un data segment, e' necessario spostare in un registo di
segmento per dati (DS, ES, FS e GS) un selettore per il segmento a cui si
desidera accedere.
Prima che il processore carichi tale valore nei registri di segmento, pero',
attua un privilege level check che consiste in questo: vengono confrontati il
CPL, l'RPL del selettore e il DPL del descrittore del segmento. Affinche' il
processore carichi nel registri di segmento il selettore, il DPL dele essere
maggiore o uguale sia al CPL che all'RPL; in caso contrario, #GP rulez;).
Per quanto riguarda gli stack segment, invece, affinche' il registro SS venga
caricato con il selettore indicato sia l'RPl dello stack segment selector che il
DPL dello stack segment descriptor devono essere uguali al CPL. Lascio a voi
indovinare cosa accade in caso contrario;).
Privilege Level Checking nel trasferimento del controllo del programma tra code segment
Quando il controllo del prg in esecuzione viene passato da un segmento a un
altro, il selettore per il codice di destinazione deve esser caricato nel
registro CS. Il processore pero' carica tale selettore solo dopo aver fatto sul
descrittore a cui esso si riferisce check di tipo, di limite e di *privilegio*.
Se tali check hanno successo, l'esecuzione sara' passata al nuovo segmento,
precisamente all'indirizzo CS:EIP.
Per poter trasferire il contorllo a un segmento diffrente da quello attuale si
utilizzano le classiche istruzioni del tipo JMP/CALL, ma le strade che si
possono seguire sono piu' di una:
Caso #1 - Trasferimento diretto al segmento a cui si vuole accedere.
Caso #2 - Utilizzo di un gate descriptor che contiene il selettore per il code
segment a cui si desidera ccedere.
Caso #3 - Trasferimento al TSS, che contiene il selettore per il segmento
target.
Caso #1
Se si desidera trasferire il controllo del programma a un segmento diverso da
quello attuale una strada e' quella di esegure un far JMP/CALL alla procedura
desiderata. naturalmente, *prima* di eseguire tale salto o tale call si eseguono
dei controlli di privilegio. In questo caso, vengono controllati:
- Il CPL.
- Il DPL per il segmento target.
- L'RPL.
- Il C flag nel descrittore del target code segment, che determina se il
segmento e' conforming o non-conforming.
Se il segmento e' nonconforming (e la maggioranza lo sono), il CPL (naturalmente
sempre della procedura chiamante) deve essere uguale alla DPL del segmento di
destinazione. In caso contrario, #GP. Avrete gia' capito che allora questo non
puo' essere un modo per scendere a ring0 da ring3.
Se il segmento e' conforming, il CPL deve essre uguale o maggiore (ovvero aver
meno livello di privilegio) del DPL del segmento target, e l'RPL del selettore
del segmento target non e' controllato. Se il check ha esito positivo, in CS
viene caricato il selettore, altrimenti solito #GP.
In altre parole, per i conforming code segment la DPL rappresenta il livello di
privilegio piu' basso a cui puo' essere la procedura chiamante affinche' possa
essere eseguita con successo una call al code segment target.
A questo punto sembrerebbe che utilizzando segmenti conforming e settanto la DPL
del descrittore che si riferisce al segmento target a 0 si possa scendere molto
facilmente a ring0... ma c'e' un problema, infatti quei simpaticoni della Intel
hanno fatto in modo che, anche se in questo tipo di procedura la DPL e' 0 o cmq
< CPL, quando il selettore e' caricato in CS il CPL rimanga invariato e non
scenda al valore della DPL, questo per questioni di protezione tra segmenti
conforming e nonconforming (che solitamente sono utilizzati da moduli quali math
library)... bella sfiga per noi, visto anche che questo e' l'unico caso, come
accennato sopra, in cui CPL != DPL del code segment corrente.
In definitiva, se vogliamo scendere a ring0 su un processore Intel possiamo
sognarci di fare un JMP/CALL diretto al codice che codice che ci interessa.
Fuori 1.
Caso #2
Per permettere a un programma di vere accesso a segmenti con livelli di
privilegio tra loro differenti le <Architetture i386 mettono a disposizione un
set particolare di descrittori, chiamati gate descriptor. Ne esistono 4 tipi:
- Call gate.
- Trap gate.
- Interrupt Gate.
- Task Gate.
I Call Gate sono stati progettati proprio per permettere lo switching tra
segmenti dal livello di privilegio diverso o tra segmenti a 16 e 32 bit.
Essi possono essere installati nella GDT o nella LDT, *non* nella IDT, e il suo
compito e' quello di:
1- Specificare il segmento a cui accedere.
2- Definire un entrypoint per la procedura nel segmento target.
3- Specificare il livello di privilegio che la procedura chiamante deve avere
per poter accedere alla nuova procedura.
4- Specificare il numero di parametri da cpiare tra gli stack se accade uno
stack-switch.
5- Definire la grandezza dei valori da pushare nello stack target (16 bit
segment = push a 16 bit, 32 bit segment = push a 32 bit).
6- Specificare in quali casi il call gate e' valido.
Ecco come si presenta un call gate.
31 16 15 1413 12 11 8 7 6 5 4 0 +-------------------------------------+--+----+--+--------+-----+----------+ | | | D | | Type | | | | Offset in Segment 31:16 | P| P | 0| |0 0 0| Parameter| | | | L| |1|1|0|0 | | Count | +-------------------------------------+--+----+--+-+-+-+--+-----+----------+ 31 16 15 0 +------------------------------------+-------------------------------------+ | | | | Segment Selector | Offset in Segment 15:00 | | | | +------------------------------------+-------------------------------------+Il campo "Segment Selector" specifica il segmento a cui accedere. Il campo "Offset" specifica l'entrypoint nel segmento. Il DPL specifica il livello di privilegio del call gate, ovvero il livello di privilegio necessario per accedere al segmento attraverso il call gate. Il P flag indica se il descrittore e' valido (ovvero e' il flag P che indica se il segmento effettivamente esiste). Il campo "Parameter Count" indica il numero di parametri da copiare dallo stack della procedura chiamante a quello della nuova procedura se avviene uno stack- switch (ovvero il numero di word per il call gate a 16 bit o il numero di dw per il call gate a 32 bit).
Stack Switching
Quando un programma utilizza un call gate per traserire il controllo da un
segmento meno privilegiato a uno piu' priviligiato il processore automaticamente
cambia (switcha, appunto;) ) il task utilizzato. Cosa significa tutto cio?
Nelle architetture Intel per ogni task in esecuzione esistono 4 stack: 1 per
l'attuale livello di privilegio (ring3) e altri 3 per i rimanenti livelli ring2,
ring1 e ring0 (naturalmente per i sistemi che utilizzano sono 2 livelli ring3 e
ring0 quali WinCesso esistono solo 2 stack), ognuno naturalmente presente in un
proprio segmento separato. Quando da un livelo meno privilegiato si scende a uno
piu' privilegiato (anche se usando questi termini sarebbe piu' opportuno dire
"si sale" a dire il vero ;) ) viene effettuato proprio un cambiamento del task
utilizzato (task switching) che in primo luogo permette alle procedure piu'
privilegiate di avere un minor rischio di crash a causa di uno stack troppo
piccolo e in secondo luogo impedisce che le procedure a ring piu' alto (meno
privilegiate) interferiscano con quelle a ring piu' basso a causa di uno stack
condiviso (shared stack).
Come noto per puntare allo stack servono un selettore e uno stack pointer.
Quando siamo a ring3, il selettore e' localizzato in SS e il suo stack pointer
in ESP, mentre i puntatori agli stack a ring piu' bassi sono localizzati nel TSS
(Task-State Segment) del task attualmente in esecuzione: quando si accedera' a
un livllo di privilegio diverso da ring3, essi saranno utilizzati proprio per
creare il nuovo stack, che dovra' essere sufficientemente grande per contenere:
- Il contenuto di SS, ESP, CS ed EIP della procedura chiamante.
- I parametri indicati nella chiamata alla nuova procedura.
- Il registro di EFLAG e gli error code (per le exception e gli interrupt).
Riassumendo, quando viene chiamato un call gate per passare a un livello di
privilegio piu' basso (ricordo infatti che *non* e' possibile passare a CPL 3 da
CPL 0, 1 o 2 se non attraverso un RET) ecco cosa fa il processore per switchare
lo stack:
1- Utilizza il DPL del segmento di destinazione per selezionare un puntatore al
nuovo stack dal TSS.
2- Legge il selettore e lo stack pointer per il nuovo stack dal TSS. Eventuali
violazioni del limite durante la lettura generano una invalid TSS exception
(#TS).
3- Controlla i giusti privilegi del descrittore (sempre dello stack-segment). Se
sono erati altra #TS.
4- Salva i valiri di SS ed ESP.
5- Carica selettore e stack poibnter rispettivamente in SS e ESP.
6- Pusha i valori salvati di SS ed ESP nel nuovo stack.
7- Copia dal vecchio stack il numero di parametri specificati nel count field
del call gate nel nuovo stack.
8- Pusha il contenuto di CS ed EIP nel nuovo stack.
9- Carica il selettore per il nuovo code segment e il nuovo instruction pointer
dal call gate rispettivamente in CS:EIP e inizia l'esecuzione della nuova
procedura.
Calling Procedure's Stack Called Procedure's Stack | | | | +-------------------+ +-----------------+ | Parameter 1 | | Calling SS | +-------------------+ +-----------------+ | Parameter 2 | | Calling ESP | +-------------------+ +-----------------+ | Parameter 3 | | Parameter 1 | +-------------------+ +-----------------+ | | | Parameter 2 | +-----------------+ | Parameter 3 | +-----------------+ | Calling CS | +-----------------+ | Calling EIP | +-----------------+ | |Per tornare dalla chiamata piu' privilegiata a quella meno privilegia bastera' utilizzare un semplice RET, naturalmente se non si e' usato un JMP per chiamate la procedura a ring piu' basso.
Trap Gate & Interrupt Gate
Premetto che questi 2 metodi sono molto simili tra loro, i gate da installare
sono identici, spieghero' la differenza alla fine della descrizione.
Prima di iniziare a (s)parlare della IDT, di interrupt e di exception, btw,
avviso che cerchero' di essere piu' conciso che in precedenza;) anche perche'
questi 2 metodi, oltre ad essere quasi uguali tra loro, sono simili a loro volta
ai call-gate.
Un interrupt, come saprete, puo' essere generato dall'hardware del picci'
(attraverso l'APIC serial bus) o da un software che gira su di esso (la classica
istruzione int n). Un'exception, invece, puo' esser generata da un software
(istruzioni INTO, INT 3 e BOUND), ma anche dal processore (che vede un errore
durante l'esecuzione di un programma, ad ex durante un privilege level check) e
da un machine-check (sia esterni che interni, servono per controllare le
operazioni dell'hardware chip interno e le comunicazioni del bus).
Per ogni exception o interrupt il processore definisce un vettore, (che in
pratica corrisponde al numero di int o exception, ad ex il vettore del page
fault e' il 14) ed le exception sono classificate e suddivise in fault, trap e
abort (no non vi preoccupate non sto qui a spiegarne la differenza ;) ).
La IDT (Interrupt Descriptor Table) associa a ogni exception o interrupt un gate
descriptor (int gate o trap gate) per la procedura da eseguire (chiamata
handler) quando tale int o tale exception si verifica. Come la GDT e la LDT, la
IDT e' un'array di descrittori di 8 byte.
Il base address della IDT e' contenuto nei bit 47 --> 16 del registro IDTR, il
suo limite nei bit 15 --> 0 dello stesso registro.
Ecco come si presenta un int gate o un trap gate descriptor installato nella
IDT.
31 16 15 1413 12 8 7 5 4 0 +--------------------------------+--+----+---------+-----+--------+ | | | D | | | | | Offset 31..16 | P| P |0 D 1 1 1|0 0 0|Reserved| | | | L| | | | +--------------------------------+--+----+---------+-----+--------+ 31 16 15 0 +--------------------------------+--------------------------------+ | | | | Segment Selector | Offset 15..0 | | | | +--------------------------------+--------------------------------+Non penso servano troppe spiegazioni sulla struttura di questi gate: il selettore punta al descrittore nella GDT / LDT, l'offset e' la "distanza", in positivo naturalmente, dal base address, ovvero il punto in cui dovra' iniziare l'esecuzione dell'handler.
Procedure di Exception e Interrupt Handling
Quando il processore effettua una chiamata a un handler di un'exception /
interrupt, salva lo stato del registro di EFALG, CS e EIP nello stack. Se il
livello di privilegio dell'handler e' lo stesso, si utilizza lo stesso stack,
altrimenti avviene uno stack-switching.
Per ritornare dall'int / exc procedure, l'handler utilizza l'istruzione IRET
(che rimette a posto i flag in EFLAG)... naturalmente durante tale processo di
ritorno se era avvenuto uno stack swithing ora si torna allo stack di partenza.
IDT Dest Code Segment +-------------------+ +-------------------+ | | | | +-------------------+ | | | | +-------------------+ +-------------------+ | | | | |Interrupt Procedure| +-------------------+Offset +---+ | | | Int/Trap Gate +-----> | + | ------> +-------------------+ +-------------------+---+ +---+ | | | | | ^ | | +-------------------+ | | | | | | | | | | +-------------------+ | | | | | | | | | | +-------------------+ | | | | | | | | | | +-------------------+ | +---------> +-------------------+ | | Segmente Selector | | +-----------------------+ | | | | | | GDT / LDT | Base | +-------------------+ | Address | | | | | +-------------------+ | | | | | | +-------------------+ | | | | | | +-------------------+ | +--> | Segment Descriptor+----+ +-------------------+ | | +-------------------+ | | +-------------------+ | | +-------------------+ | | +-------------------+Come detto all'inzio, Trap e Int Gates (sebbene abbiano gate identici) sono leggermente diversi: infatti, quando si accede a un int/exc handler attraverso un int gate il processore pulisce il flag IF per impedire ad altri int di interferire con corrente handler. Fare lo stesso tramite un trap gate non modifica tale flag.
Task Gate
Un altro metodo utilizzabile per passare l'esecuzione a una procedura con un
privilegio maggiore e' utilizzare un cosiddetto "task gate". Prima di spiegare
cos'e' un task gate, pero', tentero' di introdurre al funzionamento dei task in
architetture i386. Per definizione, un task e' un'unita' di lavoro che un
processore puo' terminare, eseguire e sospendere. Esso puo' essere utilizzato
per eseguire un programma, un altro task o processo, un'utility di servizio del
SO, un interrupt o exception handler, un'utility del kernel stesso.
Ogni task e' formato da 2 parti: uno spazio di esecuzione e un task-state
segment, TSS. Lo spazio di esecuzione consiste in un segmento addetto
all'esecuzione di codice, a uno stack segment (o, meglio, a tanti stack segment
quanti sonoi livelli di privilegio) e uno o + data segment; il TSS, invece,
specifica i segmenti del task e offre uno spazio in cui memorizzare le
informazioni del task stesso.
Un task e' identificato da un selettore alla sua TSS. Quando un task e' caricato
per l'esecuzione, tale selettore, i limite e un segment descriptor della TSS
sono messi nel task register.
TSS +---+ +---------------+ <------| + | <-------------+ | | +---+ | | | ^ | | | | | | | | | | | | | | | | | +---------------+ <--------+ | | | | | | | | Invisible Part | +----------------+---------+--------+--------+------+ | Selector | Base Address | Segment Limit | +-------+--------+------------------+--------+------+ | ^ | | | | | +-----------+ | | | | | | | | | | | | | | | | | GDT | | | +---------------+ | | | | | | | | +---------------+ | | | | | | | | +---------------| | | +-------> | TSS Descriptor|----+-----+ +---------------+ | | +---------------+ | | +---------------+Sfruttando proprio il funzionamento dei task e' possibile switchare tra 2 task con differenti livelli di privilegio il 2 modi: e' possibile optare per un trasferimento diretto a un descrittore TSS installato nella GDT che fara' poi riferimento al TSS corrispondente o utilizzare un task gate installato nella GDT, LDT o IDT. Tale task gate puntera' a sua volta a un TSS descriptor nella GDT che portera' ancora una volta al TSS del nuovo task.
31 24 23 22 21 20 19 16 15 1413 12 11 8 7 0 +---------------+--+--+--+--+--------+--+----+--+--------+----------------+ | | | | | A| Limit | | D | | Type | | | Base 31:24 | G| 0| 0| V| 19:16 | P| P | 0| | Base 23:16 | | | | | | L| | | L| |1|0|B|1 | | +---------------+--+--+--+--+--------+--+----+--+-+-+-+--+----------------+ 31 16 15 0 +------------------------------------+------------------------------------+ | | | | Base Address 15:00 | Segment Limit 15:00 | | | | +------------------------------------+------------------------------------+I campi base, limit, DPL, i flag G e P hanno funzioni simili a quelle dei data descriptor. Il campo "limit" deve avere una grandezza minima di 0x67 per un TSS a 32bit, un byte meno della grandezza minima di un TSS.
31 16 15 1413 12 11 8 7 0 +-------------------------------------+--+----+--+--------+----------------+ | | | D | | Type | | | Reserved | P| P | 0| | Reserved | | | | L| |1|1|0|0 | | +-------------------------------------+--+----+--+-+-+-+--+----------------+ 31 16 15 0 +------------------------------------+-------------------------------------+ | | | | TSS Segment Selector | Reserved | | | | +------------------------------------+-------------------------------------+Il DPL e' come sempre il livello di privilegio del descrittore, che deve essere maggiore o uguale al CPL e all'RPL del gate selector.
Conclusioni
Questo tute lo dedico a una persona del tutto speciale =).
Un saluto particolare va invece a tutti quelli del racl.
Byz, Ritz for *