GCC -fPIC Option

Antworten:

525

Positionsunabhängiger Code bedeutet, dass der generierte Maschinencode nicht davon abhängt, dass er sich an einer bestimmten Adresse befindet, um arbeiten zu können.

ZB würden Sprünge eher relativ als absolut erzeugt.

Pseudo-Assemblierung:

PIC: Dies würde funktionieren, unabhängig davon, ob sich der Code an der Adresse 100 oder 1000 befindet

100: COMPARE REG1, REG2
101: JUMP_IF_EQUAL CURRENT+10
...
111: NOP

Nicht-PIC: Dies funktioniert nur, wenn sich der Code an der Adresse 100 befindet

100: COMPARE REG1, REG2
101: JUMP_IF_EQUAL 111
...
111: NOP

EDIT: Als Antwort auf einen Kommentar.

Wenn Ihr Code mit -fPIC kompiliert wurde, ist er für die Aufnahme in eine Bibliothek geeignet. Die Bibliothek muss von ihrem bevorzugten Speicherort an eine andere Adresse verschoben werden können. Möglicherweise befindet sich eine andere bereits geladene Bibliothek an der von Ihrer Bibliothek bevorzugten Adresse.

Erik
quelle
36
Dieses Beispiel ist klar, aber was ist als Benutzer der Unterschied, wenn ich eine gemeinsam genutzte Labrary-Datei (.so) ohne diese Option erstelle? Gibt es Fälle, in denen meine Bibliothek ohne -fPIC ungültig ist?
Narek
16
Ja, das Erstellen einer gemeinsam genutzten Bibliothek, die kein PIC ist, kann ein Fehler sein.
John Zwinck
92
Genauer gesagt sollte die gemeinsam genutzte Bibliothek von Prozessen gemeinsam genutzt werden, es ist jedoch möglicherweise nicht immer möglich, die Bibliothek in beiden Fällen an derselben Adresse zu laden. Wenn der Code nicht positionsunabhängig wäre, würde jeder Prozess eine eigene Kopie erfordern.
Simon Richter
19
@Narek: Der Fehler tritt auf, wenn ein Prozess mehr als eine gemeinsam genutzte Bibliothek an derselben virtuellen Adresse laden möchte. Da Bibliotheken nicht vorhersagen können, welche anderen Bibliotheken geladen werden könnten, ist dieses Problem mit dem herkömmlichen Konzept der gemeinsam genutzten Bibliothek unvermeidbar. Der virtuelle Adressraum hilft hier nicht weiter.
Philipp
6
Sie können -fPICbeim Kompilieren eines Programms oder einer statischen Bibliothek darauf verzichten, da in einem Prozess nur ein Hauptprogramm vorhanden ist und daher keine Laufzeitverschiebung erforderlich ist. Auf einigen Systemen werden Programme zur Erhöhung der Sicherheit immer noch positionsunabhängig gemacht.
Simon Richter
61

Ich werde versuchen, das, was bereits gesagt wurde, auf einfachere Weise zu erklären.

Immer wenn eine gemeinsam genutzte Bibliothek geladen wird, ändert der Loader (der Code auf dem Betriebssystem, der ein von Ihnen ausgeführtes Programm lädt) einige Adressen im Code, je nachdem, wohin das Objekt geladen wurde.

Im obigen Beispiel wird die "111" im Nicht-PIC-Code beim ersten Laden vom Loader geschrieben.

Für nicht freigegebene Objekte möchten Sie möglicherweise, dass dies so ist, da der Compiler einige Optimierungen an diesem Code vornehmen kann.

Wenn ein gemeinsam genutztes Objekt bei einem gemeinsam genutzten Objekt mit diesem Code "verknüpfen" möchte, muss er ihn an denselben virtuellen Adressen lesen. Andernfalls macht "111" keinen Sinn. Dieser virtuelle Raum wird jedoch möglicherweise bereits im zweiten Prozess verwendet.

Roee Gavirel
quelle
Whenever a shared lib is loaded, the loader changes some addresses in the code depending on where the object was loaded to.Ich denke, dies ist nicht korrekt, wenn es mit -fpic kompiliert wurde und der Grund, warum -fpic existiert, z. B. aus Leistungsgründen oder weil Sie einen Loader haben, der nicht verschoben werden kann, oder weil Sie mehrere Kopien an verschiedenen Orten oder aus vielen weiteren Gründen benötigen.
Robsn
Warum nicht immer -fpic verwenden?
Jay
1
@ Jay - weil für jeden Funktionsaufruf eine weitere Berechnung (die Funktionsadresse) erforderlich ist. Wenn es also nicht benötigt wird, ist es besser, es nicht zu verwenden.
Roee Gavirel
45

Code, der in gemeinsam genutzte Bibliotheken integriert ist, sollte normalerweise positionsunabhängiger Code sein, damit die gemeinsam genutzte Bibliothek problemlos an (mehr oder weniger) jede Adresse im Speicher geladen werden kann. Die -fPICOption stellt sicher, dass GCC einen solchen Code erzeugt.

Jonathan Leffler
quelle
Warum sollte eine gemeinsam genutzte Bibliothek an keiner Adresse im Speicher geladen werden, ohne dass das -fPICFlag aktiviert ist? ist es nicht mit dem Programm verbunden? Wenn das Programm ausgeführt wird, lädt das Betriebssystem es in den Speicher hoch. Vermisse ich etwas
Tony Tannous
1
Wird das -fPICFlag verwendet, um sicherzustellen, dass diese Bibliothek in eine beliebige virtuelle Adresse in dem Prozess geladen werden kann, der sie verknüpft? Entschuldigung für doppelte Kommentare 5 Minuten verstrichen können vorherige nicht bearbeiten.
Tony Tannous
1
Unterscheiden Sie zwischen dem Erstellen der gemeinsam genutzten Bibliothek (Erstellen libwotnot.so) und dem Verknüpfen mit dieser ( -lwotnot). Während des Verlinkens müssen Sie sich keine Sorgen machen -fPIC. Früher mussten Sie beim Erstellen der gemeinsam genutzten Bibliothek sicherstellen -fPIC, dass alle Objektdateien in die gemeinsam genutzte Bibliothek integriert wurden. Die Regeln haben sich möglicherweise geändert, da Compiler heutzutage standardmäßig mit PIC-Code erstellt werden. Was vor 20 Jahren kritisch war und vor 7 Jahren wichtig gewesen sein könnte, ist heutzutage meiner Meinung nach weniger wichtig. Adressen außerhalb des O / S-Kernels sind "immer" virtuelle Adressen.
Jonathan Leffler
Also musste man vorher das hinzufügen -fPIC. Ohne dieses Flag zu übergeben, muss der beim Erstellen der .so generierte Code auf bestimmte virtuelle Adressen geladen werden, die möglicherweise verwendet werden.
Tony Tannous
1
Ja, denn wenn Sie das PIC-Flag nicht verwendet haben, war der Code nicht zuverlässig verschiebbar. Dinge wie ASLR (Address Space Layout Randomization) sind nicht möglich, wenn der Code kein PIC ist (oder zumindest so schwer zu erreichen ist, dass sie effektiv unmöglich sind).
Jonathan Leffler
21

Weitere ...

Jeder Prozess hat denselben virtuellen Adressraum (Wenn die Randomisierung der virtuellen Adresse mithilfe eines Flags in Linux OS gestoppt wird) (Weitere Informationen Deaktivieren und erneutes Aktivieren der Randomisierung des Adressraumlayouts nur für mich selbst )

Wenn es sich also um eine Exe ohne gemeinsame Verknüpfung handelt (hypothetisches Szenario), können wir derselben asm-Anweisung immer dieselbe virtuelle Adresse zuweisen, ohne Schaden zu nehmen.

Wenn wir jedoch ein freigegebenes Objekt mit der exe verknüpfen möchten, sind wir uns nicht sicher, welche Startadresse dem freigegebenen Objekt zugewiesen ist, da dies von der Reihenfolge abhängt, in der die freigegebenen Objekte verknüpft wurden. Allerdings hat die Anweisung asm in .so immer eine Unterschiedliche virtuelle Adresse je nach Prozess, mit dem die Verknüpfung hergestellt wird.

Ein Prozess kann also .so als 0x45678910 in seinem eigenen virtuellen Raum eine Startadresse geben, und ein anderer Prozess kann gleichzeitig die Startadresse 0x12131415 geben. Wenn sie keine relative Adressierung verwenden, funktioniert .so überhaupt nicht.

Sie müssen also immer den relativen Adressierungsmodus und damit die Option fpic verwenden.

Ritesh
quelle
1
Vielen Dank für die Erklärung der virtuellen Adresse.
Hot.PxL
2
Kann jemand erklären, dass dies kein Problem mit einer statischen Bibliothek ist, warum Sie -fPIC nicht für eine statische Bibliothek verwenden müssen? Ich verstehe, dass die Verknüpfung in der Kompilierungszeit (oder direkt danach) erfolgt. Wenn Sie jedoch zwei statische Bibliotheken mit positionsabhängigem Code haben, wie werden diese verknüpft?
Michael P
3
Die @ MichaelP-Objektdatei enthält eine Tabelle mit positionsabhängigen Beschriftungen. Wenn eine bestimmte obj-Datei verknüpft ist, werden alle Beschriftungen entsprechend aktualisiert. Dies kann nicht für die gemeinsam genutzte Bibliothek durchgeführt werden.
Slava
16

Die Verknüpfung zu einer Funktion in einer dynamischen Bibliothek wird beim Laden der Bibliothek oder zur Laufzeit aufgelöst. Daher werden sowohl die ausführbare Datei als auch die dynamische Bibliothek beim Ausführen des Programms in den Speicher geladen. Die Speicheradresse, unter der eine dynamische Bibliothek geladen wird, kann nicht im Voraus bestimmt werden, da eine feste Adresse möglicherweise mit einer anderen dynamischen Bibliothek kollidiert, die dieselbe Adresse benötigt.


Es gibt zwei häufig verwendete Methoden, um dieses Problem zu lösen:

1. Umzug. Alle Zeiger und Adressen im Code werden bei Bedarf an die tatsächliche Ladeadresse angepasst. Der Umzug erfolgt durch den Linker und den Loader.

2.Positionsunabhängiger Code. Alle Adressen im Code beziehen sich auf die aktuelle Position. Freigegebene Objekte in Unix-ähnlichen Systemen verwenden standardmäßig positionsunabhängigen Code. Dies ist weniger effizient als eine Verlagerung, wenn das Programm längere Zeit ausgeführt wird, insbesondere im 32-Bit-Modus.


Der Name " positionsunabhängiger Code " impliziert tatsächlich Folgendes:

  • Der Codeabschnitt enthält keine absoluten Adressen, die verschoben werden müssen, sondern nur selbst relative Adressen. Daher kann der Codeabschnitt an einer beliebigen Speicheradresse geladen und von mehreren Prozessen gemeinsam genutzt werden.

  • Der Datenabschnitt wird nicht von mehreren Prozessen gemeinsam genutzt, da er häufig beschreibbare Daten enthält. Daher kann der Datenabschnitt Zeiger oder Adressen enthalten, die verschoben werden müssen.

  • Alle öffentlichen Funktionen und öffentlichen Daten können unter Linux überschrieben werden. Wenn eine Funktion in der ausführbaren Hauptdatei denselben Namen wie eine Funktion in einem gemeinsam genutzten Objekt hat, hat die Version in main Vorrang, nicht nur beim Aufruf von main, sondern auch beim Aufruf vom gemeinsam genutzten Objekt. Wenn eine globale Variable in main denselben Namen wie eine globale Variable im gemeinsam genutzten Objekt hat, wird die Instanz in main ebenfalls verwendet, selbst wenn vom gemeinsam genutzten Objekt aus darauf zugegriffen wird.


Diese sogenannte Symbolinterposition soll das Verhalten statischer Bibliotheken nachahmen.

Ein gemeinsam genutztes Objekt verfügt über eine Zeigertabelle für seine Funktionen, die als Prozedurverknüpfungstabelle (PLT) bezeichnet wird, und eine Zeigertabelle für seine Variablen, die als globale Offset-Tabelle (GOT) bezeichnet wird, um diese Funktion zum Überschreiben zu implementieren. Alle Zugriffe auf Funktionen und öffentliche Variablen werden in diesen Tabellen durchgeführt.

ps Wenn eine dynamische Verknüpfung nicht vermieden werden kann, gibt es verschiedene Möglichkeiten, um die zeitaufwendigen Funktionen des positionsunabhängigen Codes zu vermeiden.

Sie können mehr aus diesem Artikel lesen: http://www.agner.org/optimize/optimizing_cpp.pdf

Bruziuz
quelle
9

Eine kleine Ergänzung zu den bereits veröffentlichten Antworten: Objektdateien, die nicht positionsunabhängig kompiliert wurden, können verschoben werden. Sie enthalten Einträge in der Verschiebungstabelle.

Mit diesen Einträgen kann der Loader (das Codebit, das ein Programm in den Speicher lädt) die absoluten Adressen neu schreiben, um sie an die tatsächliche Ladeadresse im virtuellen Adressraum anzupassen.

Ein Betriebssystem versucht, eine einzelne Kopie einer in den Speicher geladenen "gemeinsam genutzten Objektbibliothek" für alle Programme freizugeben, die mit derselben gemeinsam genutzten Objektbibliothek verknüpft sind.

Da der Code-Adressraum (im Gegensatz zu Abschnitten des Datenraums) nicht zusammenhängend sein muss und die meisten Programme, die mit einer bestimmten Bibliothek verknüpft sind, einen ziemlich festen Bibliotheksabhängigkeitsbaum haben, ist dies meistens erfolgreich. In den seltenen Fällen, in denen eine Diskrepanz besteht, kann es erforderlich sein, zwei oder mehr Kopien einer gemeinsam genutzten Objektbibliothek im Speicher zu haben.

Offensichtlich wird jeder Versuch, die Ladeadresse einer Bibliothek zwischen Programmen und / oder Programminstanzen zufällig zu sortieren (um die Möglichkeit der Erstellung eines ausnutzbaren Musters zu verringern), solche Fälle häufig und nicht selten machen, wenn ein System diese Funktion aktiviert hat. Man sollte jeden Versuch unternehmen, alle gemeinsam genutzten Objektbibliotheken positionsunabhängig zu kompilieren.

Da Aufrufe dieser Bibliotheken aus dem Hauptteil des Hauptprogramms ebenfalls verschoben werden können, ist es weniger wahrscheinlich, dass eine gemeinsam genutzte Bibliothek kopiert werden muss.

user1016759
quelle