Ho letto delle Opzioni di GCC per le convenzioni di generazione del codice , ma non sono riuscito a capire cosa faccia "Genera codice indipendente dalla posizione (PIC)". Per favore, fai un esempio per spiegarmi cosa significa.
Ho letto delle Opzioni di GCC per le convenzioni di generazione del codice , ma non sono riuscito a capire cosa faccia "Genera codice indipendente dalla posizione (PIC)". Per favore, fai un esempio per spiegarmi cosa significa.
Risposte:
Posizionare il codice indipendente significa che il codice macchina generato non dipende dal fatto di trovarsi in un indirizzo specifico per funzionare.
Ad esempio i salti verrebbero generati come relativi piuttosto che assoluti.
Pseudo-assemblaggio:
PIC: Funzionerebbe se il codice era all'indirizzo 100 o 1000
100: COMPARE REG1, REG2
101: JUMP_IF_EQUAL CURRENT+10
...
111: NOP
Non-PIC: funzionerà solo se il codice si trova all'indirizzo 100
100: COMPARE REG1, REG2
101: JUMP_IF_EQUAL 111
...
111: NOP
EDIT: in risposta al commento.
Se il tuo codice è compilato con -fPIC, è adatto per l'inclusione in una libreria - la libreria deve essere in grado di essere trasferita dalla sua posizione preferita in memoria a un altro indirizzo, potrebbe esserci un'altra libreria già caricata all'indirizzo che preferisci.
-fPIC
durante la compilazione di un programma o di una libreria statica, poiché in un processo esisterà un solo programma principale, quindi non è mai necessario spostare il runtime. Su alcuni sistemi, i programmi sono ancora resi indipendenti dalla posizione per una maggiore sicurezza.
Proverò a spiegare ciò che è già stato detto in modo più semplice.
Ogni volta che viene caricata una libreria condivisa, il caricatore (il codice sul sistema operativo che carica qualsiasi programma eseguito) cambia alcuni indirizzi nel codice a seconda di dove è stato caricato l'oggetto.
Nell'esempio sopra, "111" nel codice non PIC viene scritto dal caricatore la prima volta che è stato caricato.
Per gli oggetti non condivisi, è possibile che sia così perché il compilatore può apportare alcune ottimizzazioni su quel codice.
Per l'oggetto condiviso, se un altro processo vorrà "collegarsi" a quel codice, dovrà leggerlo agli stessi indirizzi virtuali o "111" non avrà alcun senso. ma quello spazio virtuale potrebbe essere già in uso nel secondo processo.
Whenever a shared lib is loaded, the loader changes some addresses in the code depending on where the object was loaded to.
Penso che questo non sia corretto se compilato con -fpic e il motivo per cui -fpic esiste, ad esempio per motivi di prestazioni o perché hai un caricatore che non è in grado di trasferirsi o perché hai bisogno di più copie in posizioni diverse o per molte altre ragioni.
Il codice incorporato nelle librerie condivise dovrebbe normalmente essere un codice indipendente dalla posizione, in modo che la libreria condivisa possa essere prontamente caricata su (più o meno) qualsiasi indirizzo in memoria. L' -fPIC
opzione garantisce che GCC produca tale codice.
-fPIC
flag acceso? non è collegato al programma? quando il programma è in esecuzione, il sistema operativo lo carica in memoria. Mi sto perdendo qualcosa?
-fPIC
utilizzato il flag, per garantire che questa libreria possa essere caricata su qualsiasi indirizzo virtuale nel processo che la sta collegando? scusa per i commenti doppi 5 minuti trascorsi non è possibile modificare quello precedente.
libwotnot.so
) e il collegamento con essa ( -lwotnot
). Durante il collegamento, non è necessario preoccuparsi -fPIC
. In passato, quando si creava la libreria condivisa, era necessario assicurarsi -fPIC
che tutti i file oggetto fossero integrati nella libreria condivisa. Le regole potrebbero essere cambiate perché al giorno d'oggi i compilatori compilano con codice PIC per impostazione predefinita. Quindi, ciò che era critico 20 anni fa e che avrebbe potuto essere importante 7 anni fa, è meno importante in questi giorni, credo. Gli indirizzi esterni al kernel o / s sono "sempre" indirizzi virtuali ".
-fPIC
. Senza passare questo flag, il codice generato durante la creazione di .so deve essere caricato su indirizzi virtuali specifici che potrebbero essere in uso?
Aggiunta ulteriore ...
Ogni processo ha lo stesso spazio di indirizzi virtuali (se la randomizzazione dell'indirizzo virtuale viene interrotta utilizzando un flag nel sistema operativo Linux) (Per ulteriori dettagli Disabilitare e riattivare la randomizzazione del layout dello spazio di indirizzi solo per me stesso )
Quindi, se si tratta di un exe senza collegamento condiviso (scenario ipotetico), allora possiamo sempre fornire lo stesso indirizzo virtuale alle stesse istruzioni senza alcun danno.
Ma quando vogliamo collegare un oggetto condiviso all'esempio, non siamo sicuri dell'indirizzo iniziale assegnato all'oggetto condiviso poiché dipenderà dall'ordine in cui gli oggetti condivisi sono stati collegati. indirizzo virtuale diverso a seconda del processo a cui è collegato.
Quindi un processo può dare l'indirizzo iniziale a .so come 0x45678910 nel suo spazio virtuale e altri processi allo stesso tempo possono dare l'indirizzo iniziale di 0x12131415 e se non usano l'indirizzamento relativo, .so non funzionerà affatto.
Quindi devono sempre usare la modalità di indirizzamento relativo e quindi l'opzione fpic.
Il collegamento a una funzione in una libreria dinamica viene risolto quando la libreria viene caricata o in fase di esecuzione. Pertanto, sia il file eseguibile che la libreria dinamica vengono caricati in memoria all'avvio del programma. L'indirizzo di memoria in cui viene caricata una libreria dinamica non può essere determinato in anticipo, poiché un indirizzo fisso potrebbe scontrarsi con un'altra libreria dinamica che richiede lo stesso indirizzo.
Esistono due metodi comunemente usati per affrontare questo problema:
1.Relocation. Tutti i puntatori e gli indirizzi nel codice vengono modificati, se necessario, per adattarli all'indirizzo di caricamento effettivo. Il trasferimento viene eseguito dal linker e dal caricatore.
2. Codice indipendente dalla posizione. Tutti gli indirizzi nel codice sono relativi alla posizione corrente. Gli oggetti condivisi in sistemi simili a Unix utilizzano per impostazione predefinita codice indipendente dalla posizione. Questo è meno efficiente del trasferimento se il programma viene eseguito a lungo, specialmente in modalità a 32 bit.
Il nome " codice indipendente dalla posizione " implica effettivamente quanto segue:
La sezione del codice non contiene indirizzi assoluti che richiedono il trasferimento, ma solo indirizzi relativi. Pertanto, la sezione del codice può essere caricata in un indirizzo di memoria arbitrario e condivisa tra più processi.
La sezione dei dati non è condivisa tra più processi perché spesso contiene dati scrivibili. Pertanto, la sezione dati può contenere puntatori o indirizzi che richiedono il trasferimento.
Tutte le funzioni pubbliche e i dati pubblici possono essere sovrascritti in Linux. Se una funzione nell'eseguibile principale ha lo stesso nome di una funzione in un oggetto condiviso, allora la versione in main avrà la precedenza, non solo quando viene chiamata da main, ma anche quando viene chiamata dall'oggetto condiviso. Allo stesso modo, quando una variabile globale in main ha lo stesso nome di una variabile globale nell'oggetto condiviso, verrà utilizzata l'istanza in main, anche quando vi si accede dall'oggetto condiviso.
Questa cosiddetta interposizione di simboli ha lo scopo di imitare il comportamento delle librerie statiche.
Un oggetto condiviso ha una tabella di puntatori alle sue funzioni, denominata tabella di collegamento di procedura (PLT) e una tabella di puntatori alle sue variabili chiamata tabella di offset globale (GOT) per implementare questa funzione di "sostituzione". Tutti gli accessi a funzioni e variabili pubbliche passano attraverso queste tabelle.
ps Laddove il collegamento dinamico non può essere evitato, ci sono vari modi per evitare le caratteristiche di consumo di tempo del codice indipendente dalla posizione.
Puoi leggere di più da questo articolo: http://www.agner.org/optimize/optimizing_cpp.pdf
Una piccola aggiunta alle risposte già pubblicate: i file oggetto non compilati per essere indipendenti dalla posizione sono trasferibili; contengono voci della tabella di rilocazione.
Queste voci consentono al caricatore (quel bit di codice che carica un programma in memoria) di riscrivere gli indirizzi assoluti da regolare per l'indirizzo di caricamento effettivo nello spazio degli indirizzi virtuali.
Un sistema operativo tenterà di condividere una singola copia di una "libreria di oggetti condivisa" caricata in memoria con tutti i programmi collegati alla stessa libreria di oggetti condivisa.
Poiché lo spazio degli indirizzi di codice (diversamente dalle sezioni dello spazio dati) non deve necessariamente essere contiguo e poiché la maggior parte dei programmi che si collegano a una libreria specifica ha un albero delle dipendenze della libreria abbastanza fisso, ciò ha successo la maggior parte del tempo. In quei rari casi in cui esiste una discrepanza, sì, potrebbe essere necessario disporre di due o più copie di una libreria di oggetti condivisa in memoria.
Ovviamente, qualsiasi tentativo di randomizzare l'indirizzo di caricamento di una libreria tra programmi e / o istanze di programma (in modo da ridurre la possibilità di creare un modello sfruttabile) renderà tali casi comuni, non rari, quindi dove un sistema ha abilitato questa funzionalità, si dovrebbe fare ogni tentativo di compilare tutte le librerie di oggetti condivisi in modo che siano indipendenti dalla posizione.
Dato che anche le chiamate in queste librerie dal corpo del programma principale saranno rese trasferibili, ciò rende molto meno probabile che una biblioteca condivisa debba essere copiata.