Mettiamo per esempio che stiamo utilizzando un sistema operativo multi-thread che gestisce i thread a livello kernel secondo il modello uno ad uno, quando il mio programma raggiunge un istruzione che implementa una chiamata di sistema,
in che modo avviene il passaggio dalla modalità utente alla modalità kernel?
in che modo funziona l'eccezione che passa il controllo al sistema operativo?
o meglio il sistema operativo come se ne "accorge" che deve servire una chiamata di sistema?
come avviene il passaggio dei parametri da una funzione a una chiamata di sistema?
- vengono passati nello stack del thread ?
- o in altri modi?
Prmia di rispondere a questa domanda, occorrono alcune precisazioni. Il discorso è piuttosto lungo e complesso, cercherò di spiegarmi comunque chiaramente.
La differenza c'è anche tra un OS a 32bit ed uno a 64bit.
Prendo come esempio Windows x86 su un Pentium II (o precedente). Windows ha una tabella nella quale mappa dei numeri (entry) a dei valori, degli indirizzi. La tabella è nota come IDT (Interrupt Dispatch Table).
Una di queste voci punta al
system service dispatcher. L'entrata in questione è la 0x2E; l'invocazione avviene utilizzando proprio "int 0x2e". La sua esecuzione provoca una transizione da user mode a kernel mode. Vengono usati due registri della CPU per il passaggio di informazioni: EAX, che contiene il numero del servizio che è stato richiesto; e EDX che punta ai parametri passati dal chiamante.
Sui processori x86 superiori al Pentium II la situazione cambia. Windows non utilizza più int 0x2E, ma utilizza l'istruzione sysenter (notare che è la Intel a supportarla). La situazione qui è differente: Windows durante il boot salva l'indirizzo del system service dispatchar all'interno di un registro speciale, chiamato
machine specific register, noto come MSR, associato con l'istruzione (sysenter). L'esecuzione di questa istruzione provoca l'entrata nella kernel mode e l'esecuzione del system service dispatcher. Vengono usati EAX ed EDX esattamente come in precedenza, e con gli stessi scopi.
Ora non sono su un 32bit Windows, ma su un 64bit. Comunque la funzione la si può vedere da kernel debugger (abilitando prima il debug).
Su 64bit la situazione è differente. In questo caso l'istruzione utilizzata è
syscall. Come sempre, il numero della syscall è in EAX; i primi 4 parametri nei registri disponibili. Se non dovessero essere sufficienti viene utilizzato lo stack.
Nel caso di un 64bit quindi, il codice a seguito ad una chiamata di sistema (come WriteFile, ReadFile etc), sarà il seguente (o qualcosa di simile):
Codice:
mov r10, rcx
mov eax, 0x102
syscall
ret
Su un 64bit è bene notare che questo codice è direttamente in NTDLL.DLL. Su un 32bit c'è un passaggio in più (se sei interessato questa sera o domani ti mostro qualche screen preso dal mio "vecchio" 32bit e dall'attuale 64bit).
Su 32bit, la chiamata alla 0x2e o alla sysenter, provoca da parte del kernel una lettura nella
system service dispatch table, una tabella simile alla IDT (se vuoi, posso mostrarti anche questa). Questa tabella però, al contrario della IDT, non punta a delle ISR ma a dei servizi di sistema.
Su 64bit invece non ci sono puntatori alle funzioni, ma offset relativi (relativi alla system service dispatch table stessa).
Qui c'è la risposta ad un'altra tua domanda, riguardo allo stack.
Il system service dispatcher è incaricato ad un'operazione molto delicata: copia gli argomenti dallo stack del thread in user-mode al suo in kernel-mode (così non possono essere modificati). A questo punto viene invocato il servizio di sistema.
Tutto ciò avviene nel caso in cui la chiamata di sistema avvenga da user mode, appunto.
Il corso è più o meno questo comunque:
- Chiamata di una funzione della WinAPI (ReadFile, ad esempio);
- viene chiamata ReadFile in Kernelbase.dll (e chiama NtReadFile);
- NtReadFile è in Ntdll.dll, ed esegue sysenter.
A questo punto l'esecuzione passa in kernel-mode:
- KiSystemService in Ntoskrnl.exe che chiama NtReadFile;
- NtReadFile in Ntoskrnl.exe esegue l'operazione, e torna al chiamante (il tutto procede a ritroso).
Questo vale solo per la kernel API; user32.dll e gdi32.dll fanno eccezione e saltano un passaggio (non c'è lo step di Kernelbase.dll).
Piccola nota, una curiosità: la tabella che ho citato prima, su un 32bit, può essere alterata. Può essere alterata da tutto il codice in kernel mode (drivers compresi). C'erano rootkit infatti che patchavano questa tabella cambiando i puntatori. Le versioni di Windows 64bit invece utilizzano una sorta di "guardiano", una protezione, chiamata Kernel Patch Protection, ed in caso di un tentativo di modifica... crasha il sistema (anche qui, era stata trovata una falla).
Se hai altre domande o vuoi chiarimenti, scrivi qui. Vedrò se riesco a fare qualche screen sull'OS a 32bit già questa sera, mettere assieme qualcosa con il 64bit anche, e poi postarli qui.