Positionsunabhängiger Code - Position-independent code

In der Datenverarbeitung ist positionsunabhängiger Code ( PIC ) oder positionsunabhängige ausführbare Datei ( PIE ) ein Körper von Maschinencode , der irgendwo im Primärspeicher platziert wird und unabhängig von seiner absoluten Adresse ordnungsgemäß ausgeführt wird . PIC wird häufig für gemeinsam genutzte Bibliotheken verwendet , sodass derselbe Bibliothekscode an einer Stelle in jedem Programmadressraum geladen werden kann, an der er sich nicht mit anderem verwendeten Speicher überschneidet (z. B. anderen gemeinsam genutzten Bibliotheken). PIC wurde auch auf älteren Computersystemen verwendet, denen eine MMU fehlte , damit das Betriebssystem Anwendungen selbst innerhalb des einzelnen Adressraums eines MMU-losen Systems voneinander fernhalten konnte .

Positionsunabhängiger Code kann ohne Modifikation an jeder Speicheradresse ausgeführt werden. Dies unterscheidet sich von absolutem Code , der an einer bestimmten Stelle geladen werden muss, um korrekt zu funktionieren, und von LTL-Code ( Load-Time Locatable ), bei dem ein Linker oder Programmlader ein Programm vor der Ausführung so ändert, dass es nur von einem bestimmten Speicher aus ausgeführt werden kann Lage. Positionsunabhängigen Code zu erzeugen ist oft das Standardverhalten für Compiler , aber sie können Beschränkungen für die Verwendung einiger Sprachfunktionen platzieren, wie die Verwendung von absoluten Adressen disallowing (positionsunabhängigen Code muss verwenden relative Adressierung ). Befehle, die sich direkt auf bestimmte Speicheradressen beziehen, werden manchmal schneller ausgeführt, und das Ersetzen durch entsprechende Befehle mit relativer Adressierung kann zu einer etwas langsameren Ausführung führen, obwohl moderne Prozessoren den Unterschied praktisch vernachlässigen.

Geschichte

In frühen Computern wie dem IBM 701 (29. April 1952) oder dem UNIVAC I (31. März 1951) war der Code positionsabhängig: Jedes Programm wurde so gebaut, dass es in eine bestimmte Adresse geladen und von dort ausgeführt wurde. Diese frühen Computer hatten kein Betriebssystem und waren nicht multitaskingfähig. Programme wurden in den Hauptspeicher geladen (oder sogar auf einer Magnettrommel gespeichert, um direkt von dort ausgeführt zu werden) und nacheinander ausgeführt. In einem solchen Betriebskontext war kein positionsunabhängiger Code erforderlich.

Das IBM System/360 (7. April 1964) wurde mit verkürzter Adressierung ähnlich der des UNIVAC III entwickelt , wobei die Codepositionsunabhängigkeit berücksichtigt wurde. Bei der verkürzten Adressierung werden Speicheradressen aus einem Basisregister und einem Offset berechnet . Zu Beginn eines Programms muss der Programmierer die Adressierbarkeit herstellen, indem er ein Basisregister lädt; normalerweise informiert der Programmierer den Assembler auch mit einem USING- Pseudo-Op. Der Programmierer kann das Basisregister aus einem Register laden, von dem bekannt ist, dass es die Einstiegspunktadresse enthält, typischerweise R15, oder den BALR-Befehl (Branch And Link, Registerform) (mit einem R2-Wert von 0) verwenden, um die Adresse des nächsten sequentiellen Befehls zu speichern in das Basisregister, das dann explizit oder implizit in jedem Befehl codiert wurde, der sich auf einen Speicherplatz innerhalb des Programms bezog. Mehrere Basisregister könnten für Code oder für Daten verwendet werden. Solche Befehle benötigen weniger Speicher, da sie keine volle 24-, 31-, 32- oder 64-Bit-Adresse (4 oder 8 Byte) enthalten müssen, sondern eine Basisregisternummer (codiert in 4 Bit) und einen 12-Bit-Adressoffset (kodiert in 12 Bit) und benötigt nur zwei Bytes.

Diese Programmiertechnik ist bei Systemen vom Typ IBM S/360 Standard. Es ist bis zum heutigen IBM System/z im Einsatz. Beim Codieren in Assemblersprache muss der Programmierer die Adressierbarkeit für das Programm wie oben beschrieben herstellen und auch andere Basisregister für dynamisch zugewiesenen Speicher verwenden. Compiler kümmern sich automatisch um diese Art der Adressierung.

IBMs frühes Betriebssystem DOS/360 (1966) verwendete keinen virtuellen Speicher (da die frühen Modelle von System S/360 dies nicht unterstützten), aber es hatte die Möglichkeit, Programme an einem beliebigen (oder automatisch gewählten) Speicherort zu platzieren beim Laden über den PHASE-Namen,* JCL-Anweisung (Job Control Language).

Auf S/360-Systemen ohne virtuellen Speicher konnte ein Programm an einem beliebigen Speicherort geladen werden, dies erforderte jedoch einen zusammenhängenden Speicherbereich, der groß genug war, um dieses Programm aufzunehmen. Manchmal kam es zu einer Speicherfragmentierung durch das Laden und Entladen von Modulen unterschiedlicher Größe. Virtueller Speicher hat – konstruktionsbedingt – diese Einschränkung nicht.

Während DOS/360 und OS/360 PIC nicht unterstützten, konnten transiente SVC-Routinen in OS/360 keine verschiebbaren Adresskonstanten enthalten und in allen Übergangsbereichen ohne Verschiebung ausgeführt werden .

Virtueller Speicher wurde zuerst auf IBM System/360 Modell 67 im Jahr (1965) eingeführt, um IBMs erstes Multitasking-Betriebssystem und Timesharing-Betriebssystem TSS/360 zu unterstützen. Spätere Versionen von DOS/360 (DOS/VS usw.) und spätere IBM-Betriebssysteme verwendeten alle virtuellen Speicher. Die abgeschnittene Adressierung blieb als Teil der Basisarchitektur erhalten und ist immer noch von Vorteil, wenn mehrere Module in denselben virtuellen Adressraum geladen werden müssen.

Andere frühe segmentierte Systeme wie Burroughs MCP auf dem Burroughs B5000 (1961) und Multics (1964), Paging-Systeme wie IBM TSS/360 (1967) oder Base-and-Bounds- Systeme wie GECOS auf dem GE 625 und EXEC auf dem UNIVAC 1107 , Code war auch von Natur aus positionsunabhängig, da Adressen in einem Programm relativ zum aktuellen Segment und nicht absolut waren.

Die Erfindung der dynamischen Adressübersetzung (die von einer MMU bereitgestellte Funktion ) reduzierte ursprünglich den Bedarf an positionsunabhängigem Code, da jeder Prozess seinen eigenen unabhängigen Adressraum (Adressbereich) haben könnte. Mehrere gleichzeitige Jobs mit demselben Code verursachten jedoch eine Verschwendung von physischem Speicher. Wenn zwei Jobs völlig identische Programme ausführen, bietet die dynamische Adressübersetzung eine Lösung, indem es dem System ermöglicht, einfach die Adresse 32 K von zwei unterschiedlichen Jobs auf die gleichen Bytes des Realspeichers abzubilden, der die einzige Kopie des Programms enthält.

Verschiedene Programme können einen gemeinsamen Code verwenden. Beispielsweise können sowohl das Gehaltsabrechnungsprogramm als auch das Debitorenbuchhaltungsprogramm ein identisches Sortierunterprogramm enthalten. Ein gemeinsam genutztes Modul (eine gemeinsam genutzte Bibliothek ist eine Form eines gemeinsam genutzten Moduls) wird einmal geladen und in die beiden Adressräume abgebildet.

Technische Details

Prozeduraufrufe innerhalb einer gemeinsam genutzten Bibliothek werden normalerweise über kleine Prozedur-Linkage-Tabellen- Stubs durchgeführt , die dann die endgültige Funktion aufrufen. Dies ermöglicht insbesondere einer gemeinsam genutzten Bibliothek, bestimmte Funktionsaufrufe von zuvor geladenen Bibliotheken zu erben, anstatt ihre eigenen Versionen zu verwenden.

Datenreferenzen aus positionsunabhängigem Code werden normalerweise indirekt über Global Offset Tables (GOTs) hergestellt, die die Adressen aller globalen Variablen speichern, auf die zugegriffen wird . Es gibt ein GOT pro Kompilierungseinheit oder Objektmodul, und es befindet sich an einem festen Offset vom Code (obwohl dieser Offset nicht bekannt ist, bis die Bibliothek verlinkt ist ). Wenn ein Linker Module verknüpft, um eine gemeinsam genutzte Bibliothek zu erstellen, führt er die GOTs zusammen und legt die endgültigen Offsets im Code fest. Es ist nicht erforderlich, die Offsets beim späteren Laden der Shared Library anzupassen.

Positionsunabhängige Funktionen, die auf globale Daten zugreifen, beginnen mit der Bestimmung der absoluten Adresse des GOT mit ihrem eigenen aktuellen Programmzählerwert. Dies geschieht oft in Form eines gefälschten Funktionsaufrufs, um den Rückgabewert auf dem Stack ( x86 ), in einem bestimmten Standardregister ( SPARC , MIPS ) oder einem speziellen Register ( POWER / PowerPC / Power ISA ) zu erhalten, das dann in ein vordefiniertes Standardregister verschoben oder in dieses Standardregister ( PA-RISC , Alpha , ESA/390 und z/Architecture ) aufgenommen werden. Einige Prozessorarchitekturen, wie Motorola 68000 , Motorola 6809 , WDC 65C816 , Knuths MMIX , ARM und x86-64 ermöglichen das Referenzieren von Daten durch Offset vom Programmzähler . Dies zielt speziell darauf ab, den positionsunabhängigen Code kleiner, weniger registerfordernd und damit effizienter zu machen.

Windows-DLLs

Dynamic-Link-Bibliotheken (DLLs) in Microsoft Windows verwenden die Variante E8 des CALL-Befehls (Call near, relative, Displacement relativ zum nächsten Befehl). Diese Anweisungen müssen nicht repariert werden, wenn eine DLL geladen wird.

Von einigen globalen Variablen (zB Arrays von String-Literalen, virtuelle Funktionstabellen) wird erwartet, dass sie eine Adresse eines Objekts im Datenbereich bzw. im Codebereich der dynamischen Bibliothek enthalten; Daher muss die gespeicherte Adresse in der globalen Variablen aktualisiert werden, um die Adresse widerzuspiegeln, in die die DLL geladen wurde. Der dynamische Lader berechnet die Adresse, auf die sich eine globale Variable bezieht, und speichert den Wert in einer solchen globalen Variablen; dies löst das Kopieren beim Schreiben einer Speicherseite aus, die eine solche globale Variable enthält. Seiten mit Code und Seiten mit globalen Variablen, die keine Zeiger auf Code oder globale Daten enthalten, bleiben von Prozessen gemeinsam genutzt. Dieser Vorgang muss in jedem Betriebssystem durchgeführt werden, das eine dynamische Bibliothek an einer beliebigen Adresse laden kann.

In Windows Vista und späteren Versionen von Windows erfolgt die Verlagerung von DLLs und ausführbaren Dateien durch den Kernel-Speicher-Manager, der die verschobenen Binärdateien für mehrere Prozesse freigibt. Bilder werden immer von ihren bevorzugten Basisadressen verschoben, wodurch eine Adressraum-Layout-Randomisierung (ASLR) erreicht wird.

Windows-Versionen vor Vista erfordern, dass System-DLLs zum Zeitpunkt der Verknüpfung an nicht kollidierenden festen Adressen vorverknüpft werden, um eine Verlagerung von Bildern zur Laufzeit zu vermeiden. Die Laufzeitverschiebung in diesen älteren Windows-Versionen wird vom DLL-Ladeprogramm im Kontext jedes Prozesses durchgeführt, und die resultierenden verschobenen Teile jedes Abbilds können nicht mehr zwischen Prozessen geteilt werden.

Die Handhabung von DLLs in Windows unterscheidet sich von der früheren OS/2- Prozedur, von der es abgeleitet ist. OS/2 bietet eine dritte Alternative und versucht, nicht positionsunabhängige DLLs in eine dedizierte "gemeinsam genutzte Arena" im Speicher zu laden und sie nach dem Laden zuzuordnen. Alle Benutzer der DLL können dieselbe speicherinterne Kopie verwenden.

Multics

In Multics hat jede Prozedur konzeptionell ein Codesegment und ein Verknüpfungssegment. Das Codesegment enthält nur Code und der Linkage-Abschnitt dient als Vorlage für ein neues Linkage-Segment. Zeigerregister 4 (PR4) zeigt auf das Verknüpfungssegment der Prozedur. Ein Aufruf einer Prozedur speichert PR4 im Stack, bevor es mit einem Zeiger auf das Verbindungssegment des Aufrufers geladen wird. Der Prozeduraufruf verwendet ein indirektes Zeigerpaar mit einem Flag, um beim ersten Aufruf einen Trap zu verursachen, damit der dynamische Verknüpfungsmechanismus die neue Prozedur und ihr Verknüpfungssegment zur Known Segment Table (KST) hinzufügen, ein neues Verknüpfungssegment konstruieren kann, put ihre Segmentnummern im Verbindungsabschnitt des Anrufers und setzen das Flag im indirekten Zeigerpaar zurück.

TSS

Im IBM S/360 Timesharing-System (TSS/360 und TSS/370) kann jede Prozedur eine schreibgeschützte öffentliche CSECT und einen beschreibbaren privaten Prototypabschnitt (PSECT) haben. Ein Aufrufer lädt eine V-Konstante für die Routine in das allgemeine Register 15 (GR15) und kopiert eine R-Konstante für die PSECT der Routine in das 19. Wort des Sicherungsbereichs, der als GR13 bezeichnet wird.

Der Dynamic Loader lädt keine Programmseiten oder löst keine Adresskonstanten bis zum ersten Seitenfehler auf.

Positionsunabhängige ausführbare Dateien

Positionsunabhängige ausführbare Dateien (PIE) sind ausführbare Binärdateien, die vollständig aus positionsunabhängigem Code bestehen. Während einige Systeme nur ausführbare PIC-Dateien ausführen, gibt es andere Gründe, warum sie verwendet werden. PIE Binärdateien werden in einigen verwendet sicherheitsorientierten Linux - Distributionen ermöglichen PaX oder Exec - Schild zu verwenden Adressraum Layout Randomisierung Angreifer daran zu hindern, zu wissen , wo vorhandenen ausführbaren Code während eines Angriffs auf der Sicherheit ist mit Exploits , die den Offset des ausführbaren Codes auf Erkennen verlassen in die Binärdatei, wie z. B. Return-to-libc-Angriffe .

Apples macOS und iOS unterstützen ab Version 10.7 bzw. 4.3 ausführbare PIE-Dateien vollständig; eine Warnung wird ausgegeben, wenn Nicht-PIE-iOS-Ausführungsdateien zur Genehmigung an den App Store von Apple übermittelt werden, es jedoch noch keine zwingenden Anforderungen gibt und Nicht-PIE-Anwendungen nicht abgelehnt werden.

OpenBSD hat PIE auf den meisten Architekturen seit OpenBSD 5.3, veröffentlicht am 1. Mai 2013, standardmäßig aktiviert. Unterstützung für PIE in statisch verknüpften Binärdateien, wie den ausführbaren Dateien in /binund /sbinVerzeichnissen, wurde gegen Ende 2014 hinzugefügt. openSUSE hat PIE als Standard in hinzugefügt 2015-02. Beginnend mit Fedora  23 beschlossen Fedora-Betreuer, Pakete mit aktiviertem PIE als Standard zu erstellen. Ubuntu 17.10 hat PIE standardmäßig auf allen Architekturen aktiviert. Gentoos neue Profile unterstützen jetzt standardmäßig PIE. Etwa im Juli 2017 hat Debian PIE standardmäßig aktiviert.

Android hat die Unterstützung für PIEs in Jelly Bean aktiviert und die Unterstützung für Nicht-PIE-Linker in Lollipop entfernt .

Siehe auch

Anmerkungen

Verweise

Externe Links