Opzione GCC -fPIC


Risposte:


526

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.


36
Questo esempio è chiaro, ma come utente quale sarà la differenza se creo un file labrary condiviso (.so) senza l'opzione? Ci sono alcuni casi che senza -fPIC la mia lib non sarà valida?
Narek,

16
Sì, la creazione di una libreria condivisa che non è PIC potrebbe essere un errore.
John Zwinck,

92
Per essere più specifici, si suppone che la libreria condivisa sia condivisa tra processi, ma potrebbe non essere sempre possibile caricare la libreria allo stesso indirizzo in entrambi. Se il codice non fosse indipendente dalla posizione, ogni processo richiederebbe una propria copia.
Simon Richter,

19
@Narek: l'errore si verifica se un processo vuole caricare più di una libreria condivisa nello stesso indirizzo virtuale. Poiché le librerie non sono in grado di prevedere quali altre librerie potrebbero essere caricate, questo problema è inevitabile con il tradizionale concetto di libreria condivisa. Lo spazio di indirizzi virtuali non aiuta qui.
Philipp

6
È possibile omettere -fPICdurante 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.
Simon Richter,

61

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.
robsn,

Perché non usare sempre -fpic?
Jay,

1
@Jay - perché richiederà un ulteriore calcolo (l'indirizzo della funzione) per ogni chiamata di funzione. Quindi, per quanto riguarda le prestazioni, se non necessario è meglio non usarlo.
Roee Gavirel,

45

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' -fPICopzione garantisce che GCC produca tale codice.


Perché una libreria condivisa non dovrebbe essere caricata in nessun indirizzo in memoria senza il -fPICflag acceso? non è collegato al programma? quando il programma è in esecuzione, il sistema operativo lo carica in memoria. Mi sto perdendo qualcosa?
Tony Tannous,

1
Viene -fPICutilizzato 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.
Tony Tannous,

1
Distinguere tra la creazione della libreria condivisa (creazione 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 -fPICche 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 ".
Jonathan Leffler,

Quindi in precedenza dovevi aggiungere il -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?
Tony Tannous,

1
Sì, perché se non si utilizzava il flag PIC, il codice non era trasferibile in modo affidabile. Cose come l'ASLR (randomizzazione del layout dello spazio degli indirizzi) non sono possibili se il codice non è PIC (o, almeno, è così difficile da raggiungere da essere effettivamente impossibile).
Jonathan Leffler,

21

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.


1
Grazie per la spiegazione addr virtuale.
Hot.PxL il

2
Qualcuno può spiegare come questo non sia un problema con una libreria statica, perché non devi usare -fPIC su una libreria statica? Comprendo che il collegamento viene eseguito in fase di compilazione (o subito dopo effettivamente), ma se si dispone di 2 librerie statiche con codice dipendente dalla posizione, come verranno collegate?
Michael P,

3
Il file oggetto @MichaelP ha una tabella delle etichette dipendenti dalla posizione e quando un particolare file obj è collegato tutte le etichette vengono aggiornate di conseguenza. Questo non può essere fatto alla libreria condivisa.
Slava,

16

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


9

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.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.