Portable Executable

Il formato Portable Executable (PE) è un formato di file per file eseguibili, file oggetto, librerie condivise e device drivers, usato nelle versioni a 32-bit e 64-bit del sistema operativo Microsoft Windows. Il termine "portable" si riferisce alla versatilità del formato per numerose architetture differenti. Il formato PE è praticamente una struttura dati che incapsula le informazioni necessarie al loader di Windows per gestire il codice eseguibile.
Ciò include la risoluzione delle dipendenze dalle librerie condivise, tabelle di import ed export delle API, dati per la gestione delle risorse e dati thread-local storage (TLS). Sui sistemi operativi della famiglia Windows NT, il formato PE è usato per EXE, DLL, OBJ, SYS (driver dei dispositivi), OCX (controlli ActiveX) e altri tipi di file. Le specifiche EFI (Extensible Firmware Interface) stabiliscono che il formato PE è il formato eseguibile standard in ambienti EFI.

Il formato PE è una versione modificata del formato COFF di Unix. Infatti molto spesso viene anche chiamato PE/COFF.

Sul sistema operativo Windows NT, PE supporta correntemente le architetture IA-32, IA-64 e x86-64 (AMD64/Intel64). Prima di Windows 2000, Windows NT (e quindi PE) supportava le architetture MIPS, DEC Alpha e PowerPC. A causa del fatto che PE è usato anche su Windows CE, continua a supportare numerose varianti delle architetture MIPS, ARM (incluso Thumb) e SuperH.

Storia modifica

Microsoft è passata al formato PE con l'introduzione del sistema operativo Windows NT 3.1. Tutte le versioni successive di Windows, compresi Windows 95, 98 e ME, supportano PE. Per rendere gli eseguibili prodotti prima dell'introduzione del formato PE compatibili con i sistemi operativi prodotti dopo, si è scelto di affiancare la testata (header) di un file PE alla testata di un file DOS. In questo modo viene prima letto ed interpretato il dos header per poi passare all'interpretazione del PE header (se presente). Inoltre buona parte degli strumenti di compilazione, nella creazione di un file PE, permettono di specificare un "DOS stub", ovvero una porzione di codice che viene eseguita qualora il sistema operativo su cui viene eseguito il file non è compatibile con lo standard PE. In genere il DOS stub di default stampa una stringa di avvertimento del tipo "This program cannot be run in DOS mode" (sebbene sia possibile specificare un diverso comportamento). PE, per ora, continua ad essere utilizzato anche con le modifiche alla piattaforma di Windows. Alcune estensioni al formato di esempio sono il formato .NET PE per le applicazioni .NET, una versione a 64-bit chiamata PE32+ (anche chiamato PE+) e una versione sviluppata specificamente per Windows CE.

Dettagli tecnici modifica

Layout modifica

 
Struttura di un Portable Executable a 32 bit in SVG

Un file PE consiste di un certo numero di headers e sezioni che dicono al dynamic linker come mappare il file nella memoria. Un'immagine eseguibile consiste di varie differenti regioni, ognuna delle quali richiede differente protezione della memoria. Perciò l'inizio di ogni sezione dev'essere allineata con la grandezza di una pagina (4KB in IA-32). Per esempio, tipicamente la sezione .text (che contiene il codice macchina da eseguire) è mappato in memoria come read-only (perché il codice non può essere modificato), la sezione .data (che contiene le variabili globali) è mappata come read-write. Comunque, per evitare uno spreco di spazio, le sezioni non sono allineate anche sul disco ma solo virtualmente (cioè solo negli headers). Parte del lavoro del dynamic linker è quello di mappare ogni sezione al suo spazio in memoria e assegnare i corretti permessi alle regioni risultanti, prendendo le informazioni necessarie dagli headers del file. Più o meno allo stesso modo lavora il formato ELF di Linux.

Import Table modifica

Una sezione importante è la import address table (IAT), che è usata come una tabella di lookup quando l'applicazione cerca di usare una funzione di una libreria condivisa esterna. Infatti un programma compilato non può sapere dove saranno caricate in memoria le librerie dalle quali dipende, così un indirect jump è necessario ogniqualvolta è effettuata una chiamata esterna. Quando il dynamic linker carica i moduli e li unisce nello stesso address space, scrive istruzioni di jump negli spazi della tabella IAT, cosicché questi puntino alle locazioni di memoria delle corrispondenti funzioni di libreria. Anche se questo aggiunge un jump extra anche nelle chiamate interne delle librerie (infatti anche le librerie useranno la IAT e non chiameranno direttamente le loro stesse funzioni), il decadimento delle prestazioni è veramente basso e vale la flessibilità delle librerie condivise. Se il compilatore sa prima del tempo che una chiamata sarà interna alla libreria (con l'attributo dllimport) può produrre codice più ottimizzato che risulterà semplicemente in un indirect call opcode.

Rilocazioni modifica

I file PE non contengono position-independent code (codice indipendente dalla posizione in memoria). Invece sono compilati a un base address (indirizzo di base) preferito, e tutti gli indirizzi creati dal compilatore/linker sono impostati in anticipo. Se un file PE non può essere caricato sul suo indirizzo di base preferito (perché su quell'indirizzo magari c'è qualcos'altro), il sistema operativo gli cambierà la base. Ciò implica il ricalcolo di tutti gli indirizzi assoluti e la modifica di tutto il codice per usare i nuovi valori. Il loader lo fa comparando l'indirizzo preferito e quello di caricamento effettivo, e calcolandone la differenza. Questa è poi sommata all'indirizzo preferito per ricavare il nuovo indirizzo della locazione di memoria. Le rilocazioni dell'indirizzo di base sono salvate in una lista e aggiunte, quando necessario, a un'esistente locazione di memoria. Il codice risultante è privato del processo e non può più essere condiviso, così molti dei benefici delle DLL di riduzione della memoria in uso sono, in questo scenario, persi. La rilocazione della base rallenta di molto anche il caricamento del modulo. Per questa ragione bisogna utilizzarla solo quando strettamente necessario e, sempre per questa ragione, le DLL di Microsoft hanno gli indirizzi di base calcolati apposta per non sovrapporsi. Nel caso che non ci siano rilocazioni il formato PE ha comunque il vantaggio di un codice veramente efficiente, ma in presenza di queste l'utilizzo della memoria diventa notevolmente maggiore. Il formato ELF invece supporta codice PIC (Position-Independent Code) utilizzando una GOT (Global Offset Table) e una PLT (Procedure Linkage Table), cosa che diminuisce di poco i tempi di esecuzione (una differenza davvero minimale) ma elimina del tutto i problemi della rilocazione, consentendo così di non sprecare memoria.

.NET, metadata, e il formato PE modifica

Il .NET Framework di Microsoft ha esteso il formato PE con caratteristiche che supportano il Common Language Runtime (CLR, un'implementazione della virtual machine .NET). Tra le aggiunte ci sono un Header CLR e una sezione Data CLR. Durante il caricamento di un file eseguibile, il loader del sistema operativo passa l'esecuzione al CLR grazie ad una reference nella tabella di IMPORT PE/COFF. Quindi la VM del CLR (Virtual Machine del CLR) carica l'Header CLR e la sezione Data CLR.

La sezione Data CLR contiene due importanti segmenti: Metadata e codice Intermediate Language (IL):

  • Metadata contiene informazioni rilevanti sull'assembly, incluso il manifest dell'assembly. Un manifest descrive l'assembly in dettaglio includendo identificazione univoca (con un hash, numero di versione, ecc.), informazioni sui componenti esportati, informazioni sui tipi (supportati dal Common Type System (CTS)), references esterne, e una lista dei file presenti nell'assembly. L'ambiente CLR fa un uso estensivo del metadata.
  • Il codice Intermediate Language (IL) è codice astratto e indipendente dal linguaggio di programmazione che soddisfa i requisiti del Common Intermediate Language (CIL) del CLR .NET. Il termine "Intermediate" si riferisce alla natura del codice IL che è indipendente dal linguaggio e dalla piattaforma. Questo "linguaggio intermedio", simile al bytecode in Java, permette a tutte le piattaforme e linguaggi di supportare il CLR .NET. IL supporta la programmazione orientata agli oggetti (polimorfismo, tipi astratti, ecc.), eccezioni, eventi e varie strutture dati. Il codice IL è assemblato in un file PE .NET per essere eseguito dal CLR.

Utilizzo su altri sistemi operativi modifica

Il formato PE è usato anche da ReactOS, visto che ReactOS è fatto per essere compatibile a livello binario con Windows. È stato anche utilizzato da altri sistemi operativi, come SkyOS e BeOS R3. Comunque, entrambi questi due sistemi operativi sono passati ad utilizzare il formato ELF. Il formato PE è utilizzato anche da Möbius.

Poiché la piattaforma di sviluppo MONO intende essere compatibile a livello binario con Microsoft .NET, usa lo stesso formato PE dell'implementazione Microsoft.

Sull'architettura x86 utilizzando sistemi operativi Unix-like, i binari Windows (in formato PE) possono essere eseguiti con Wine. Anche il sistema HX DOS Extender usa il formato PE per i binari nativi DOS a 32-bit, inoltre può anche in alcuni casi eseguire binari Windows in DOS, funzionando come una specie di Wine, ma per DOS.

Mac OS X Leopard può caricare e analizzare file PE, ma non è compatibile a livello ABI con Windows.

Voci correlate modifica

Altri progetti modifica

Collegamenti esterni modifica

  Portale Informatica: accedi alle voci di Wikipedia che trattano di Informatica