Quest’oggi riprendiamo il discorso delle chiamate di sistema – dette anche syscall – che abbiamo introdotto la volta scorsa.
Abbiamo introdotto le syscall definendole come i “punti di ingresso in funzioni predefinite che il kernel esporta e sono accessibili in usermode tramite link a /usr/lib/libSystem.B.dylib”. Abbiamo poi visto come macOS gestisce le syscall in un modo particolare, perché esporta due tipi di chiamate di sistema diverse, le Mach e le POSIX syscall. In cosa differiscono? Lo vediamo immediatamente!
POSIX syscall
Come già detto in un precedente articolo, già a partire da Leopard, macOS è risultato un’implementazione UNIX certificata a tutti gli effetti: ciò significa che è pienamente compatibile con POSIX che è un’API standard che definisce, in particolare:
- Prototipo per le syscall: tutte le chiamate di sistema POSIX, indipendentemente dall’implementazione, hanno lo stesso prototipo, cioè gli stessi argomenti e valore restituito.
Questa particolarità garantisce che il codice compatibile POSIX possa essere portato, a livello di sorgente, in qualsiasi sistema operativo POSIX compatibile. Infatti il codice macOS può essere trasferito su Linux, Free-BSD e persino su Solaris, a condizione che non faccia affidamento su nient’altro che alle chiamate POSIX e le librerie standard del C/C ++. - Numeri di chiamata delle syscall: le funzioni POSIX, hanno numeri di chiamata di sistema ben definiti. Questo consente la portabilità binaria (infatti un binario compilato con POSIX può essere spostato tra vari sistemi POSIX della stessa architettura; Solaris ad esempio può eseguire binari nativi di Linux – ed entrambi sono ELF). macOS purtroppo non supporta questa modalità, poiché il formato di oggetto è Mach-O ed è incompatibile con ELF. Inoltre, i numeri di chiamata di sistema si discostano da quelli definiti nello standard.
La compatibilità POSIX è fornita quindi dal livello BSD di XNU ed i prototipi di chiamata di sistema si trovano in <unistd.h>. Discuteremo più avanti di queste implementazioni!
Mach syscall
Ricordando che macOS è costruito su kernel Mach, il livello BSD avvolge questo kernel, ma non nasconde le sue chiamate di sistema che sono perfettamente accessibili in usermode. In effetti, senza le chiamate di sistema Mach, i comandi più comuni, non funzionerebbero. Nei sistemi a 32 bit, le chiamate di sistema Mach sono negative (forse semplificando troppo – partono da un indirizzo sotto lo zero, negativo) e questo trucco ingegnoso consente di far coesistere contemporaneamente sia le chiamate di sistema POSIX che quelle Mach. Poiché POSIX definisce come standard che le chiamate di sistema siano non negative, lo spazio negativo viene lasciato indefinito e quindi completamente a disposizione per le chiamate Mach.
Nei sistemi a 64 bit, anche le chiamate di sistema Mach sono positive, ed hanno il prefisso 0x2000000, che le separa e le differenzia dalle chiamate POSIX, che sono sempre positive, ma hanno 0x1000000 come prefisso.
Nota per smanettoni: come sono fatte le syscall?
Per essere eseguite le syscall non vengono chiamate direttamente ma attraverso un wrapper, che è un intermediario tra la chiamata alla syscall e la syscall stessa.
Per visualizzare questo concetto basta fare un esempio estremo: immaginate di essere davanti ad un erogatore di birra. La birra è la vostra syscall, l’erogatore è il wrapper. Voi non potete interagire direttamente con la birra, ma potete erogarla, quindi è il wrapper (cioè l’erogatore) che gestisce le syscall (la birra). Semplice, no?
Questo wrapper si trova in libSystem.B.dylib, e usando uno strumento che manipola e disassembla i file Mach-o (gli eseguibili di macOS) è possibile ‘spiare’ come funziona l’interfaccia di chiamata per le syscall, per capire come vengono gestite dal wrapper.
Volendo è possibile visualizzare anche le syscall di iOS e data l’architettura ARM, usando GDB piuttosto che otool, notiamo che il numero di chiamata della syscall viene caricato in un registro-intraprocedurale r12 e solo successivamente la syscall può essere eseguita, con il comando svc.
Questo ad esempio è il dump della funzione mach_reply_port in iOS, che sostanzialmente crea una nuova porta per il task in esecuzione e restituisce il nome assegnato dal kernel a questa porta: sempre il kernel registra poi il nome all’interno dello spazio dei nomi delle porte del task e concede il diritto di ricezione alla porta.
0x32f99bbc <mach_reply_port+0>: mvn r12, #25
0x32f99bc0 <mach_reply_port+4>: svc 0x00000080
Anche per questo articolo è tutto! Per eventuali domande, curiosità o feedback potete lasciare un commento qui in basso, a presto!