Un PC basato su chip Intel o AMD può eseguire Windows oppure Linux senza cambiare CPU. E allora viene spontanea una domanda: se un programma Windows contiene istruzioni x86-64 che quel processore conosce già, perché un file EXE compilato per Windows non può partire direttamente su Linux?
La risposta breve è che un programma non è composto soltanto dalle istruzioni destinate alla CPU. Un’applicazione Windows porta con sé una serie di aspettative tecniche precise: presume che il sistema operativo sappia come aprire il suo file eseguibile, dove sistemarne il codice in memoria, quali librerie caricare e che cosa fare quando il programma chiede di aprire una finestra, leggere un file o creare un thread.
Linux organizza tutte queste operazioni in modo diverso rispetto a Windows: ed è proprio qui che entrano in gioco Wine, Proton ma anche alcuni progetti sperimentali che stanno tentando una strada alternativa: eseguire direttamente il codice Windows sulla CPU e ricostruire soltanto i pezzi necessari per farlo dialogare con Linux.
Un EXE non contiene semplicemente “codice Windows”
Non esistono istruzioni della CPU riservate a Windows e altre riservate a Linux. Se un programma Windows x64 esegue una normale istruzione Assembly ADD, MOV o JMP, il processore la interpreta nello stesso modo indipendentemente dal sistema operativo installato. La differenza, come accennato in apertura, nasce da come il programma è confezionato e dai servizi che si aspetta di trovare attorno a sé.
Su Windows, i programmi eseguibili utilizzano normalmente il formato PE, Portable Executable. È il formato dei classici file .exe e .dll: contiene il codice macchina, ma anche una sorta di indice che dice al sistema operativo quali parti del file contengono il programma vero e proprio, quali contengono dati, quali librerie servono e da quale punto bisogna iniziare l’esecuzione.
Linux utilizza invece soprattutto un altro formato, chiamato ELF. Anche ELF contiene codice, dati e informazioni utili al caricamento, ma li organizza secondo regole differenti. Il problema iniziale non è che Linux “non capisca” le istruzioni contenute nell’EXE: è il sistema operativo che non tratta normalmente quel file come un proprio programma nativo.
Quando facciamo doppio clic su un programma Windows o lanciamo un eseguibile Linux dal terminale, il sistema operativo fa intervenire un componente chiamato loader, cioè il caricatore degli eseguibili. Il suo compito è leggere la struttura del file, capire quali parti devono finire in memoria, assegnare le corrette autorizzazioni e preparare tutto ciò che serve prima di consegnare il controllo al programma.
Nel caso di un EXE Windows il loader legge il formato PE. Trova, ad esempio, la sezione che contiene il codice del programma, quelle che ospitano i dati e l’indicazione del cosiddetto entry point, cioè l’indirizzo dal quale deve iniziare l’esecuzione. Un loader PE per Linux può quindi fare qualcosa che, a prima vista, sembra sorprendente: aprire un EXE Windows, copiarne correttamente il codice in memoria e farlo partire. Ed è esattamente ciò che sperimentano progetti come PELoader, LSW (Linux Subsystem for Linux) e, più recentemente, Tawi.
Far partire il codice è la parte relativamente facile
Supponiamo di avere un programma Windows molto semplice che deve soltanto eseguire alcune operazioni matematiche. Se il codice è compilato per x86-64 e la macchina Linux usa la stessa architettura, la CPU può eseguirlo direttamente. I problemi iniziano quando il programma deve fare qualcosa che coinvolge Windows.
Per esempio, un’applicazione non scrive normalmente i propri dati direttamente sul disco impartendo comandi alla CPU. Chiede invece al sistema operativo di aprire un file. Su Windows può utilizzare una funzione chiamata CreateFileW(); per scrivere dati può usare WriteFile(). Linux non offre quelle funzioni. Dispone di proprie interfacce per aprire e scrivere file, con nomi e regole differenti. Ecco perché serve uno strato di compatibilità.
Le librerie DLL sono uno dei primi ostacoli
Un normale programma Windows fa largo uso delle DLL, Dynamic Link Library. Possiamo considerarle raccolte di funzioni che più programmi possono utilizzare senza includerne ogni volta il codice nell’EXE.
Una delle librerie storicamente più importanti è KERNEL32.dll; altre applicazioni possono richiedere USER32.dll per l’interfaccia grafica, ADVAPI32.dll per numerose funzioni di sistema o WS2_32.dll per le comunicazioni di rete.
L’EXE contiene quindi informazioni che indicano che, per funzionare, l’eseguibile ha bisogno di una serie di funzioni presenti nelle DLL: Windows, durante il caricamento, cerca le librerie necessarie e collega le richieste del programma alle funzioni corrette.
Linux, da solo, non sa cosa fare quando un programma Windows gli chiede WriteFile() da KERNEL32.dll. Un’alternativa a Wine deve intervenire proprio su questi aspetti.
Il trucco: intercettare le richieste del programma Windows
Il formato PE contiene una struttura chiamata Import Address Table, o IAT. Il nome può sembrare complicato, ma l’idea è semplice: è una tabella attraverso la quale l’EXE raggiunge molte delle funzioni esterne che utilizza. Immaginiamo che il programma voglia chiamare WriteFile(): Windows inserisce nella IAT l’indirizzo della funzione appropriata.
Un loader alternativo può invece sostituirlo con l’indirizzo di una propria funzione. Il percorso diventa quindi qualcosa del genere:
programma Windows ⇒ WriteFile compatibile ⇒ funzione Linux ⇒ kernel Linux
Il programma crede di parlare con Windows, in realtà la sua richiesta viene intercettata e trasformata in un’operazione equivalente per Linux. Non è quindi necessario tradurre tutte le istruzioni del programma; il codice dell’EXE continua a essere eseguito direttamente dal processore. Ciò che bisogna fare è intervenire soprattutto nei punti in cui chiede servizi al sistema operativo.
Proseguiamo però con l’esempio della semplice apertura di un file: Windows usa percorsi come C:\Users\Luigi\documento.txt mentre Linux utilizza invece percorsi simili al seguente: /home/luigi/documento.txt Un layer di compatibilità potrebbe tradurre il primo percorso nel secondo (abbiamo visto le regole dei percorsi Linux a livello di filesystem) ma Windows e Linux hanno regole differenti su permessi, condivisione dei file, attributi, nomi dei dispositivi e gestione degli errori.
Una funzione compatibile con CreateFileW() deve quindi capire che cosa voleva realmente ottenere l’applicazione Windows e riprodurre quel comportamento usando gli strumenti offerti da Linux. Non basta insomma limitarsi a tradurre le API: bisogna tradurne il significato!
Perché Wine è molto più di un semplice loader
Wine risolve proprio il problema descritto in precedenza. L’acronimo Wine significa “Wine Is Not an Emulator“: non ricrea un intero PC virtuale e, quando l’architettura coincide, non deve tradurre una per una le istruzioni della CPU. Un’applicazione Windows x86-64 eseguita su Linux x86-64 può quindi far girare direttamente gran parte del proprio codice macchina sul processore.
La parte complessa è tutto ciò che sta attorno a quel codice. Wine si comporta come un vero e proprio layer di compatibilità: ricostruisce le interfacce che un programma Windows si aspetta di trovare e le collega ai servizi disponibili sul sistema Linux.
Wine implementa componenti equivalenti a numerose DLL Windows, tra cui ntdll.dll, kernel32.dll, user32.dll, gdi32.dll, advapi32.dll e molte altre.
Quando un programma invoca una funzione Win32, Wine deve rispettarne parametri, valori restituiti, codici di errore e comportamento previsto; solo dopo può tradurre l’operazione verso primitive POSIX, librerie Linux o chiamate al kernel.
I componenti trasferiti su Linux dal layer di compatibilità Wine
Un ruolo particolarmente importante spetta a NTDLL. Nella struttura di Windows si trova molto vicino al confine tra applicazioni in user space e servizi del kernel NT; Wine ne fornisce una propria implementazione e ricostruisce una parte consistente delle API NT utilizzate dai livelli superiori. KERNEL32, USER32 e le altre librerie possono così appoggiarsi a un’infrastruttura che presenta al programma una struttura molto simile a quella attesa su Windows.
Wine deve inoltre mantenere concetti che su Linux non esistono nello stesso modo. Gli handle Windows, per esempio, non corrispondono semplicemente ai file descriptor Unix: possono rappresentare file, processi, thread, eventi, mutex e molti altri oggetti. Wine deve conservarne stato, proprietà e regole di accesso facendo poi da ponte verso le primitive disponibili sul sistema host.
Lo stesso vale per il registro di Windows. Un’applicazione può leggere chiavi sotto HKEY_CURRENT_USER o HKEY_LOCAL_MACHINE aspettandosi struttura e semantica tipiche di Windows. Il fatto è che su Linux il registro di sistema proprio non esiste: Wine ne mantiene quindi una rappresentazione propria, memorizzata su disco, così che il programma continui a usare le normali API del registro senza sapere che il sistema sottostante è Linux.
Un’altra parte delicata riguarda processi, thread e sincronizzazione. C’è poi tutto la parte grafica: USER32 gestisce finestre, messaggi e input; GDI riguarda il disegno 2D; per Direct3D Wine utilizza componenti dedicati e, nel caso dei giochi, progetti come DXVK e vkd3d-proton possono tradurre rispettivamente Direct3D 9/10/11 e Direct3D 12 verso Vulkan. È uno dei motivi per cui oggi software molto complesso può funzionare su Linux pur essendo stato compilato esclusivamente per Windows.
Wine deve occuparsi anche di audio, socket, COM, OLE, servizi, stampa, clipboard, shell integration, codici di errore Windows e numerosi dettagli minori che diventano essenziali.
Cos’è winserver e a che cosa serve
Un componente caratteristico dell’architettura Wine è wineserver, un processo separato che coordina diversi oggetti condivisi tra i processi Windows portati su Linux grazie al layer di compatibilità. Gestisce, tra le altre cose, sincronizzazione, handle e comunicazione tra processi, permettendo di mantenere comportamenti più vicini a quelli del modello Windows.
Ecco perché Wine è un progetto sviluppato da oltre 30 anni. Far apparire una scritta sul terminale richiede poche API; eseguire correttamente un browser, un programma professionale o un gioco significa riprodurre migliaia di comportamenti e interazioni che gli sviluppatori Windows danno normalmente per scontati.
La differenza rispetto a un semplice loader PE è quindi netta: il loader porta l’eseguibile in memoria e lo avvia; Wine cerca di costruire attorno a quell’eseguibile un ambiente abbastanza simile a Windows da permettergli di continuare a funzionare.
Un approccio più compatto rispetto a Wine può comunque essere utile
Non tutti i programmi hanno però bisogno dell’intera piattaforma Windows.
PELoader, per esempio, carica eseguibili PE direttamente nello spazio utente su Linux e implementa una parte delle funzioni normalmente offerte da Windows. Non cerca di sostituire completamente Wine: punta a fornire quanto basta per eseguire determinati programmi.
Ancora più interessante è LSW, Linux Subsystem for Windows, che si presenta come una sorta di WSL (Windows Subsystem for Linux) al contrario. Il progetto carica eseguibili Windows PE x64 e collega le loro chiamate Win32 a implementazioni costruite sopra librerie e funzionalità Linux.
Gli sviluppatori dichiarano oltre 1.200 stub, cioè funzioni che rappresentano altrettanti punti di ingresso compatibili con API Windows. Il progetto mostra già l’esecuzione di utility come hostname.exe, whoami.exe, ipconfig.exe e altri programmi Windows.
Non significa che possa eseguire qualsiasi software. Significa però che il modello funziona: per una determinata categoria di applicazioni non è necessario ricostruire tutto Windows.
L’archiviazione di LSW su GitHub
Va però aggiunta una precisazione importante: il repository GitHub di LSW risulta archiviato dal 9 luglio 2026. Nei “termini GitHub” significa che il progetto è stato impostato dall’amministratore in modalità “sola lettura”: il sorgente resta pubblico, può essere scaricato, studiato, compilato e sottoposto a fork, ma lo sviluppo su quel repository si considera sostanzialmente interrotto.
L’archiviazione non rende quindi LSW “non funzionante” e non cancella quanto già realizzato. Il codice disponibile continua a rappresentare una dimostrazione tecnica particolarmente interessante dell’approccio. Il progetto include persino un modulo kernel opzionale per instradare alcune operazioni a un livello più basso.
Cambia però molto la prospettiva per chi volesse adottarlo oggi. LSW va considerato soprattutto un proof of concept avanzata e una base di studio, non un’alternativa a Wine mantenuta attivamente.
Anche Windows e Linux parlano due “dialetti” diversi della stessa CPU
C’è un problema meno visibile che rende il lavoro ancora più interessante. Windows e Linux possono utilizzare la stessa architettura x86-64, ma non sempre seguono le stesse regole quando una funzione passa dei parametri a un’altra. Queste regole prendono il nome di ABI, Application Binary Interface.
Per fare un esempio concreto, una funzione Windows x64 si aspetta normalmente di ricevere i primi argomenti in alcuni registri della CPU; una funzione compilata secondo le convenzioni normalmente usate da Linux li cerca in registri differenti.
Se un programma Windows chiamasse direttamente una funzione Linux, quest’ultima potrebbe quindi leggere valori completamente sbagliati.
Il layer di compatibilità deve fare da interprete anche qui: riceve la chiamata usando le convenzioni di Windows e poi la trasforma in una chiamata compatibile con Linux.
Compilatori come GCC e Clang dispongono già di strumenti che facilitano questa operazione. LSW, ad esempio, li utilizza proprio per creare un ponte tra le convenzioni Microsoft e quelle adottate normalmente dal software Linux.
Project Tawi prova a spostare una parte del lavoro nel kernel
Un esperimento recente chiamato Project Tawi affronta il problema da un’angolazione leggermente differente. L’autore sta sperimentando con un gestore del formato PE integrato più direttamente nei meccanismi con cui il kernel Linux riconosce gli eseguibili.
La demo pubblicata è molto semplice: carica un PE32+ x86-64, sistema alcune sezioni in memoria, intercetta tre funzioni di KERNEL32.dll e permette al programma di stampare “Hello from Windows!”. Il progetto è ancora, ovviamente, lontanissimo da una soluzione utilizzabile, ma aiuta a mettere a fuoco un aspetto importante: Linux può certamente imparare a riconoscere la struttura di un EXE. Il problema vero arriva dopo, quando quell’EXE comincia a chiedere servizi Windows.
Linux possiede anche un meccanismo chiamato binfmt_misc, pensato proprio per gestire formati eseguibili che il kernel non tratta normalmente in modo autonomo.
È possibile configurarlo, per esempio, affinché riconosca un file Windows dalla sua firma e lo passi automaticamente a Wine. Dal terminale l’utente può quindi arrivare a eseguire direttamente ./programma.exe senza digitare esplicitamente wine programma.exe.
Non significa che il kernel abbia improvvisamente implementato Windows: ha semplicemente riconosciuto il tipo di file e scelto il programma incaricato di gestirlo. Anche perché, come abbiamo raccontato in un altro articolo, ogni eseguibile Windows contiene un header che comincia per MZ (iniziali del suo storico autore in epoca DOS).
Una soluzione come Tawi prova invece a portare parte del caricamento PE più vicino al kernel stesso: può tecnicamente interessante, anche se non è detto che rappresenti la scelta migliore per una soluzione pratica.
Quindi si può creare davvero un’alternativa a Wine?
Se si vuole eseguire un gruppo ristretto di programmi console che utilizzano poche API, un loader PE accompagnato da alcune centinaia o migliaia di funzioni compatibili può essere sufficiente. Progetti come LSW dimostrano che l’idea funziona.
Se invece l’obiettivo diventa “eseguire quasi qualsiasi programma Windows su Linux“, il progetto comincia inevitabilmente ad assomigliare sempre di più a Wine.
Serve gestire file, processi, thread, sincronizzazione, rete, interfaccia grafica, registro, audio, grafica 3D, COM e una lunga serie di peculiarità accumulate in decenni di evoluzione di Windows. Ed è proprio qui che Wine mostra il valore del lavoro svolto in oltre 30 anni.