Position-independent code

Position-independent code (PIC)[1] (kód nezávislý na umístění) nebo position-independent executable (PIE)[2] (spustitelný program nezávislý na umístění) je ve výpočetní technice strojový kód, který lze provádět při umístění na libovolné adrese ve vnitřní paměti. PIC se často používá pro sdílené knihovny, takže stejný kód z knihovny může být zaveden na libovolnou adresu, která se nepřekrývá s jinou používanou pamětí (například jinou sdílenou knihovnou). PIC se také používal na starších počítačových systémech, které neměly jednotku správy paměti (MMU),[3] aby na systémech bez MMU mohl operační systém chránit aplikace i když jsou v jednom adresním prostoru.

Kód nezávislý na umístění lze provádět bez úprav při umístění na libovolnou adresu v paměti. Tím se liší od absolutního kódu,[1] který musí být zaveden na určitou adresu, aby fungoval správně,[1] a kódu relokabilního při zavedení (LTL),[1] v němž linker nebo zavaděč upraví adresní konstanty v programu po jeho načtení na určitou adresu, aby odpovídaly umístění na této adresa a program bylo možné spustit.[1] Generování kódu nezávislého na umístění je často implicitním chováním překladačů, ale může představovat omezení pro použití některých vlastností určitých jazyků, např. zákaz používání absolutních adres (kód nezávislý na umístění musí používat relativní adresy). Instrukce, které se odkazují na absolutní adresy se někdy provádějí rychleji, a jejich nahrazení ekvivalentními instrukcemi, které používají relativní adresy, může způsobit poněkud pomalejší provádění, i když u moderních procesorů je rozdíl prakticky zanedbatelný.[4]

Historie

Strojový kód prvních počítačů, např. IBM 701[5] (uvedeného 29. dubna 1952) nebo UNIVAC I (uvedeného 31. března 1951), byl pozičně závislý: program musel být sestaven tak, aby se mohl načíst a spustit na jedné určité adrese. Tyto počítače neměly operační systém a neumožňovaly multitasking. Programy se načítaly do hlavní paměti (nebo byly dokonce uloženy na magnetickém bubnu a prováděny přímo z něj) a v libovolném okmžiku mohl být spuštěn pouze jeden program. V takovém operačním kontextu nebyl kód nezávislý na umístění potřebný.

IBM System/360 (uvedený 7. dubna 1964) byl již navržen s adresováním podobným jako UNIVAC III,[6] které umožňovalo použití kódu nezávislého na umístění; používá tak zvané zkrácené adresování, při němž se adresy v paměti počítají jako součet obsahu bázového registru a posunutí. Na začátku programu musí programátor zajistit adresovatelnost dat naplněním bázového registru; o obsahu bázového registru programátor informuje také assembler pseudoinstrukcí USING. Správnou hodnotu bázového registru lze získat z registru, který obsahuje adresu vstupního bodu programu (typicky R15), nebo je možné použít instrukci BALR (Branch And Link, Registr form) (s hodnotou 0 v R2), která uloží adresu následující instrukce do bázového registru, který se pak explicitně nebo implicitně používá v každé instrukci, která se odkazuje na adresy v programu. Může se používat více bázových registrů pro kód nebo data. Takové instrukce zabírají méně paměti, protože místo úplné 24-, 31-, 32- nebo 64bitové adresy (která by zabrala 4 nebo 8 bytů) obsahují číslo bázového registru (kódované 4 bity) a 12bitové posunutí, čili odkaz na dresu vyžaduje pouze dva byty.

Tato technika programování je standardem na systémech IBM S/360 a je dostupná i na novějších systémech až po IBM System/z. Při kódování v jazyce symbolických adres programátor musí zajistit adresovatelnost dat, jak je popsáno výše a další bázové registry používat pro přístup k datům v dynamicky alokované paměti. Překladače automaticky zabezpečují tento způsob adresování.

První operační systémy pro IBM System/360 (uvedené v roce 1966) nepoužívaly virtuální paměť (protože ji první modely System S/360 nepodporovaly), ale umožňovaly umístění programů na libovolnou (nebo automaticky zvolenou) adresu v paměti při zavádění příkazem PHASE jazyka Job Control Language.

Na systémech S/360 bez virtuální paměti tak program mohl být zaveden na jakoukoli adresu v paměti, vyžadoval však souvislý úsek paměti tak velký, aby se do něj vešel. Kvůli načítání a uvolňování paměti pro různě velké moduly mohlo docházet k fragmentaci paměti. Fragmentaci odstraňuje (resp. redukuje na velikost stránky) použití virtuální paměti.

DOS/360 a OS/360 nepodporoval PIC; tranzientní volání supervisoru (SVC) v OS/360 nemohly obsahovat relokabilní adresní konstanty a mohly se spustit v libovolnému tranzientní oblasti bez provedení relokace.

V řadě IBM System/360 byla virtuální paměť poprvé použita na Modelu 67 (v roce 1965), pro podporu prvního víceúlohového operačního systému se sdílením času TSS/360. Pozdější verze DOS/360 (DOS/VS atd.) a pozdější operační systémy firmy IBM již všechny používaly virtuální paměť. Zkrácené adresování zůstalo součástí základní architektury, a je stále výhodné, když je třeba zavést více modulů do stejného virtuálního adresního prostoru.

Pro srovnání, na prvních systémech se segmentací paměti, např. Burroughs MCP na Burroughs B5000 (1961) a Multics (1964), a systémech se stránkováním paměti, např. IBM TSS/360 (1967)[pozn. 1] nebo systémech používajících bázovací a mezní registr,[pozn. 2] např. GECOS na GE 625 a EXEC na UNIVAC 1107, byl kód také nezávislý na umístění, protože adresy v program byly relativní vůči aktuálnímu segmentu.

Objev dynamického překladu adres (který provádí jednotka správy paměti, MMU) původně omezoval potřebu kódu nezávislého na umístění, protože každý proces mohl mít vlastní nezávislý adresní prostor. Spuštění více úloh současně, které používají stejný kód, způsobuje plýtvání fyzickou pamětí. Pokud dvě úlohy spouští zcela identické programy, dynamický překlad adres poskytuje řešení tím, že umožňuje systému jednoduše namapovat konkrétní adresu dvou různých úloh na stejné byty reálné paměti, která obsahuje jedinou kopii programu.

Různé programy mohou sdílet společný kód. Například program pro výpočet výplat a program pro zpracování faktur mohou obsahovat stejný třídicí podprogram. Sdílený modul (sdílená knihovna je forma sdíleného modulu) bude načten pouze jednou, a namapován do dvou adresních prostorů.

Technické detaily

Při volání procedur ze sdílené knihovny se typicky používají pomocí malých stubů uložených ve spojovací tabulce procedur, který volají vlastní funkci. To umožňuje především, aby sdílená knihovna mohla dědit některá volání funkcí z dříve načtené knihovny.

Reference na data z kódu nezávislého na umístění se obvykle provádějí nepřímo pomocí Globálních tabulek posunutí (anglicky Global Offset Tables, GOTs), které obsahují adresy všech globálních proměnných, k nimž se přistupuje. Každá překladová jednotka nebo cílový modul má jednu GOT, která je umístěna na pevném posunutí od kód (toto posunutí není známo, dokud knihovna není slinkována). Když linker spojuje moduly a vytváří sdílenou knihovnu, slučuje tabulky GOT a nastavuje cílové offsety v kódu, takže není potřeba nastavovat offsety, když se sdílená knihovna později zavádí.

Pozičně nezávislé funkce přistupují ke globálním datům určením absolutní adresy tabulky GOT podle aktuální hodnoty programového čítače. Ta se často zjišťuje voláním fiktivní funkce, při kterém se uloží návratová adresa na zásobník (na architektuře X86), do určitého standardního registru (na architekturách SPARC a MIPS) nebo do speciálního registru (na architektuře IBM POWER/PowerPC/Power ISA), odkud může být pak zkopírována do předdefinovaného standardního registru nebo přímo uložena do standardního registru (architektury PA-RISC, Alfa, ESA/390 a z/Architecture). Některé architektury procesorů, např. Motorola 68000, Motorola 6809, WDC 65C816, Knuthův MMIX, ARM a X86-64 umožňují odkazovat se na data posunutím vůči čítači instrukcí. Cílem je, aby kód nezávislý na umístění byl menší a vyžadoval méně registrů, a tedy aby byl efektivnější.

Windows DLL knihovny

Podrobnější informace naleznete v článku DLL.

Dynamicky linkované knihovny (DLL) v Microsoft Windows používají instrukci CALL s operačním kódem E8 (blízké volání s relativní adresou, posunutí je relativní vůči následující instrukci). Adresy v těchto instrukcích nemusí být při zavádění DLL měněny.

Některé globální proměnné (například pole řetězcových literálů, tabulky virtuálních funkcí) obsahují adresy objektů v datové sekci nebo v kódové sekci dynamické knihovny; adresy uložené v globální proměnné proto musí být aktualizovány, aby obsahovaly adresu, na kterou byla DLL zavedena. Dynamický zavaděč vypočítá adresu, na kterou ukazuje globální proměnná a výsledek uloží do této globální proměnné; tuto úpravu spouští copy-on-write paměťové stránky, která obsahující takovou globální proměnnou. Stránky s kódem a stránky s globálními proměnnými that neobsahuje ukazatele kódovat nebo globální data zůstává sdílené mezi procesy. Tato operace musí být provedena v jakémkoli OS, který může načítat dynamické knihovna na libovolnou adresu.

Ve Windows Vista a novějších verzích Windows se relokace DLL knihoven a spustitelných programů provádí správcem paměti jádra, který umožňuje sdílení relokovaných programů více procesy. Obrazy jsou vždy relokované z upřednostňované bázové adresy, čímž se dosahuje Address space layout randomization (ASLR).[7]

Verze Windows před Windows Vista vyžadují, aby systémové knihovny DLL byly během linkování předlinkovány na nekonfliktní pevné adresy, aby nedocházelo k běhové relokaci obrazů. Běhové relokace v těchto starších verzích Windows byly prováděny DLL zavaděčem v kontextu každého procesu, a výsledné relokované části každého obrazu již nemohly být sdílené mezi procesy.

Zpracovávání DLL knihoven ve Windows se odlišuje od dřívější procedury v OS/2, z něhož bylo přebráno. OS/2 používá třetí možnost a snaží se načítat DLL, které nejsou nezávislé na umístění, do vyhrazené „sdílené oblasti“ v paměti, a mapuje je, když jsou zavedeny. Všichni uživatelé DLL mohou používat tutéž kopii v paměti.

Multics

V OS Multics má konceptuálně[pozn. 3] každá procedura kódový segment a spojovací segment. Kódový segment obsahuje pouze kód a spojovací sekci slouží jako šablona pro nový spojovací segment. Registr ukazatele 4 (PR4) ukazuje na spojovací segment procedury. Volání procedury napřed uloží PR4 na zásobník a pak do něj načte ukazatel na spojovací segment volané procedury. Volání procedur používá dvojici nepřímých ukazatelů[8] s příznakem, který při prvním volání způsobí přerušení, v jehož obsluze dynamický spojovací mechanismus přidá novou proceduru a její spojovací segment do tabulky známých segmentů (anglicky Known Segment Table, KST), vytvoří nový spojovací segment, zavede čísla segmentů do spojovací sekce volající procedury a vynuluje příznak ve dvojici nepřímých ukazatelů.

TSS

V IBM S/360 Time Sharing System (TSS/360 a TSS/370) může mít každá procedura veřejnou sekci CSECT pouze pro čtení a zapisovatelnou soukromou sekci prototypovou sekci (PSECT). Volající zavede V-konstantu funkce do obecného registru číslo 15 (GR15) a zkopíruje R-konstantu PSECT funkce do 19. slova úložné oblasti, na kterou ukazuje GR13.[9]

Dynamický zavaděč[10] nenačítá stránky programu ani neresolvuje adresní konstanty dokud nedojde k prvnímu výpadku stránky.

Spustitelné programy nezávislé na umístění

Související informace naleznete také v článku Address space layout randomization.

Spustitelné programy nezávislé na umístění (PIE) jsou spustitelné binární programy tvořená kompletně z kódu nezávislého na umístění. Zatímco některé systémy spouštějí pouze PIC spustitelné programy, existují i jiné důvody, proč jsou používány. PIE programy se používají v některých bezpečnostně zaměřených distribucích Linuxu, které umožňují, aby PaX nebo Exec Shield používal Address space layout randomization která znemožňuje útočníkům zjistit, kde je proveditelný kód během bezpečnostních útoků zneužívajících exploity, které vyžadují znalost posunutí proveditelného kód v souboru, např. při return-to-libc útocích.

MacOS a IOS firmy Apple plně podporují PIE spustitelné programy od verze 10.7 resp. 4.3; pokud spustitelný program není nezávislý na umístění vkládán do Apple App Store, ale program není zamítnut.[11][12]

OpenBSD používá PIE implicitně na většině architektur od OpenBSD 5.3 vydaného 1. května 2013.[13] Podpora pro PIE ve staticky linkovaných programech, např. spustitelných programech v adresářích /bin a /sbin, byl přidán ke konci roku 2014.[14] OpenSUSE přidalo PIE jako implicitní v únoru 2015. V distribuci Fedora se implicitně vytvářejí programy jako PIE od verze 23.[15] Ubuntu 17.10 má PIE povoleno implicitně na všech architekturách.[16] Nové profily Gentoo Linuxu nyní podporují PIE implicitně.[17] Od července 2017 povoluje Debian PIE implicitně.[18]

Android povolil podporu PIE v verzi Jelly Bean[19] a odstranil podporu non-PIE linkeru v Lollipop.[20]

Odkazy

Poznámky

  1. TSS/360 podporoval sdílené PIC, ale ne všechny systémy, které používaly stránkování, sdílené PIC podporovaly.
  2. Pro každou úlohu se zaváděla vlastní kopie kódu.
  3. Některé technické odchylky kvůli zlepšení výkonnosti jsou nad rámec textu tohoto článku.

Reference

V tomto článku byl použit překlad textu z článku Position-independent code na anglické Wikipedii.

  1. a b c d e iRMX 86 Application Loader Reference Manual. [s.l.]: Intel Dostupné online. Kapitola Types of Object Code, s. ((1-2, 1-3)). […] Absolutní kód, a absolutní cílový modul je kód, který byl zpracován programem LOC86, a který lze spustit pouze při určitém umístění v paměti. Zavaděč takový absolutní cílový modul zavádí pouze na zvolenou adresu. Kód nezávislý na umístění (obvykle označovaný PIC) se odlišuje od absolutního kód v tom, že PIC lze zavést na jakoukoli adresu v paměti. Výhodou PIC je, že není třeba rezervovat určitý blok paměti. Když zavaděč zavádí PIC, obdrží od OS iRMX 86 paměťové segmenty z fondu úlohy, která vyžaduje zavedení programu a do těchto segmentů PIC zavede. Omezení, které se týká PIC je, že při použití modelu segmentace COMPACT v PL/M-86 […], může mít program pouze jeden kódový segment a jeden datový segment, místo letting báze adresy těchto segments, a proto se segmenty samy, se mění dynamicky. To znamená, že PIC programy musí být kratší než 64KiB. PIC kód lze vyrobit pomocí volby BIND programu LINK86. Relokace (obvykle označovaný jako LTL kód) je třetí forma cílového kódu. LTL kód se podobá PIC v tom, že LTL kód lze zavést kamkoli do paměti. Když se však zavádí LTL kód, zavaděč mění bázovou část ukazatelů tak, aby ukazatele byly nezávislé na počátečním obsahu registrů mikroprocesoru. Díky této úpravě bázových adres může být LTL kód používán úlohami, které mají více než jeden kódový segment nebo více než jeden datový segment. To znamená, že LTL programy mohou být větší než 64KiB. Fortran a [[Pascal (programovací jazyk)|]] automaticky produkují LTL kód, dokonce i pro krátké programy. LTL kód lze vyrobit pomocí volby BIND programu LINK86. […]. 
  2. Position Independent Executables (PIE) [online]. Dostupné online. 
  3. LEVINE, John R. Linkers and Loaders. 1. vyd. San Francisco, USA: Morgan Kaufmann, 2000. (The Morgan Kaufmann Series in Software Engineering and Programming). Dostupné v archivu pořízeném z originálu dne 2012-12-05. ISBN 1-55860-496-0. OCLC 42413382 ISBN 978-1-55860-496-4. Kapitola Chapter 8: Loading and overlays, s. 170–171.  Code: [1][2][nedostupný zdroj] Errata: [3]
  4. GABERT, Alexander. Position Independent Code internals [online]. January 2004 [cit. 2009-12-03]. […] přímý non-PIC-vědom adresování je vždy levnější (read: rychlejší) než PIC adresování. […]. Dostupné online. 
  5. 701 Announced [online]. IBM, 1952-04-29. Dostupné online. 
  6. Reference Manual UNIVAC III Data Processing System. [s.l.]: Sperry Rand Corporation, 1962. Dostupné online. UT-2488. 
  7. Advances in Memory Management for Windows [online]. View.officeapps.live.com [cit. 2017-06-23]. Dostupné online. 
  8. DPS/LEVEL 68 & DPS 8M MULTICS PROCESSOR MANUAL. Rev. 1. vyd. [s.l.]: Honeywell, 1982. Dostupné online. AL39. S. 6–21.  Archivováno 25. 5. 2019 na Wayback Machine.
  9. IBM Time Sharing System Concepts and Facilities. Seventh. vyd. [s.l.]: [s.n.], April 1978. Dostupné online. GC28-2003-6. S. 61. 
  10. IBM System/360 Time Sharing System Dynamic Loader. Fourth. vyd. [s.l.]: [s.n.], September 1971. Dostupné online. GY28-2031-3. 
  11. iphone - Non-PIE Binary - The executable 'project name' is not a Position Independent Executable. - Stack Overflow [online]. Dostupné online. 
  12. iOS Developer Library [online]. Dostupné online. 
  13. OpenBSD 5.3 Release [online]. 2013-05-01 [cit. 2020-05-09]. Dostupné online. 
  14. Heads Up: Snapshot Upgrades for Static PIE [online]. 2014-12-24 [cit. 2014-12-24]. Dostupné online. 
  15. Changes/Harden All Packages - FedoraProject [online]. fedoraproject.org. Dostupné online. 
  16. Ubuntu Foundations Team - Weekly Newsletter, 2017-06-15 [online]. 2017-06-15 [cit. 2017-06-17]. Dostupné online. 
  17. New 17.0 profiles in the Gentoo repository [online]. 2017-11-30 [cit. 2017-12-10]. Dostupné online. 
  18. LIANG, Mudong. When did Debian decide to enabled PIE by default? [online]. debian.org, 2017-08-08 [cit. 2021-07-06]. Dostupné online. 
  19. Security Enhancements in Android 1.5 through 4.1  -  Android Open Source Project [online]. Android Open Source Project [cit. 2023-01-05]. Dostupné v archivu pořízeném dne 2022-06-26. 
  20. Security Enhancements in Android 5.0  -  Android Open Source Project [online]. Android Open Source Project [cit. 2023-01-05]. Dostupné v archivu pořízeném dne 2017-02-27. 

Související články

Externí odkazy