A könyvtár nem pusztán újrahasznosítható forráskód: fordítási, linkelési és telepítési szerződés is. Statikus könyvtár használatakor a szükséges gépi kód rendszerint bekerül a fogyasztó binárisába. Megosztott könyvtárnál a bináris függőséget rögzít, amelyet futáskor a betöltési környezetnek kell teljesítenie. DevOps-szempontból ez meghatározza, mit kell újraépíteni egy biztonsági frissítés után, és miért indulhat el egy program a buildkonténerben, miközben a futtatóképben már nem.
A Szimbólumok, linkage és relocation részben
megismert feloldatlan szimbólumhivatkozásokra építünk. Most ugyanahhoz
az interfészhez készítünk libcount.a és
libcount.so könyvtárat, majd különválasztjuk a linkeléskori
és futásidejű keresést.
GNU/ELF-eszközlánc: a statikus könyvtár általában
ar formátumú archívum, amely relokálható objektumfájlokat
és szimbólumindexet tartalmaz. A linker alapesetben nem az egész
archívumot másolja be: azokat az objektumtagokat emeli ki, amelyek
szükségesek az addig felismert feloldatlan hivatkozásokhoz. A kiemelés
egysége objektumfájl, nem egyetlen függvény; a későbbi optimalizálás és
szekcióeldobás ettől külön mechanizmus.
A megosztott objektum (shared object) önálló ELF-bináris. Kódja külön fájlban marad, és a fogyasztó rendszerint futásidejű függőséget rögzít rá.
| Állomány | Szerep | Kell a fogyasztó futásához? |
|---|---|---|
count.h |
Fordításkori API-deklaráció | Nem |
libcount.a |
Objektumtagok linkeléskori forrása | Maga az archívum nem |
libcount.so |
A linker által keresett fejlesztői név | Nem feltétlenül ez a név |
libcount.so.1 |
A példában rögzített futásidejű könyvtárnév | Igen, feloldható állományként |
Fontos különbség: egyetlen könyvtár statikus felhasználása nem jelenti a teljes program statikus linkelését. A program más függőségei, például a libc, továbbra is lehetnek megosztott könyvtárak.
A példák minimuma ISO C17. A választott, reprodukáláshoz rögzítendő mintaprofil Linux x86-64, System V ABI, LP64 adatmodell, GCC 14.2.0, GNU binutils 2.43 és glibc 2.40. Második fordítóként Clang 19.1.7 használható, ugyanazzal a célplatformmal és külön rögzített linkerrel. Ezek választott verziók, nem itt ellenőrzött futtatási környezetek; az alábbi eredmények elvárások, nem mért kimenetek.
A három forrásfájl ugyanabba a friss munkakönyvtárba kerüljön.
count.h:
#ifndef COUNT_H
#define COUNT_H
unsigned count_add(unsigned total, unsigned delta);
#endifcount.c:
#include "count.h"
unsigned count_add(unsigned total, unsigned delta)
{
return total + delta;
}main.c:
#include <stdio.h>
#include "count.h"
int main(void)
{
if (printf("%u\n", count_add(2u, 3u)) < 0) {
return 1;
}
return 0;
}ISO C17: a függvény értékeket fogad és ad vissza;
nincs erőforrás-tulajdonlás vagy kölcsönzött hozzáférés. Az
unsigned összeadás az adott típus értéktartományának
megfelelő moduláris összeadás. A függvény nem jelzi a matematikai összeg
tartománytúllépését; ez az API szerződésének része, nem linkelési
kérdés.
Először készítsünk objektumfájlokat és archívumot:
mkdir -p build/lib build/bin
gcc -std=c17 -Wall -Wextra -Wpedantic -O2 -fno-lto \
-fPIC -c count.c -o build/count.pic.o
gcc -std=c17 -Wall -Wextra -Wpedantic -O2 -fno-lto \
-fPIE -c main.c -o build/main.o
ar rcs build/lib/libcount.a build/count.pic.o
gcc -pie build/main.o build/lib/libcount.a \
-o build/bin/count-staticAz ar rcs parancsban az r tagot illeszt be
vagy cserél, a c jelzi az archívum létrehozásának
szándékát, az s pedig szimbólumindexet készít.
A statikus archívumba itt szándékosan PIC objektum kerül, hogy ugyanazt az objektumot használhassuk a megosztott változathoz is. A PIC kód nem teszi dinamikussá a linkelést.
A megosztott változat:
gcc -shared build/count.pic.o \
-Wl,-soname,libcount.so.1 \
-o build/lib/libcount.so.1.0
ln -sfn libcount.so.1.0 build/lib/libcount.so.1
ln -sfn libcount.so.1 build/lib/libcount.so
gcc -pie build/main.o -Lbuild/lib -lcount \
-Wl,--enable-new-dtags,-rpath,'$ORIGIN/../lib' \
-o build/bin/count-sharedA -Lbuild/lib linkeléskori keresési könyvtárat ad meg. A
-lcount a megadott GNU/ELF-profilban normál, dinamikus
linkelési módban a megosztott változatot részesíti előnyben, ha az adott
keresési helyen az archívum is jelen van. A statikus példában ezért
írtuk ki közvetlenül a .a fájl nevét.
Sikeres betöltés és kimeneti írás esetén mindkét program várt
kimenete 5, majd újsor.
A hagyományos GNU linkerfeldolgozásban az archívum helye jelentéssel bír. Az alábbi sorrend szándékosan hibás:
# Szándékosan hibás linksorrend.
gcc -pie build/lib/libcount.a build/main.o \
-o build/bin/count-wrongIndoklás, lépésenként: tekintsük csak a
count_add hivatkozását.
count.pic.o tagot.main.o létrehozza a
hivatkozást.A várt következmény feloldatlan count_add szimbólum
miatti linkelési hiba. A javítás: előbb a fogyasztó objektum, utána a
függőség.
Ugyanez vonatkozik archívumok közötti függőségre. Ha
libfront.a igényli libback.a függvényeit,
tipikusan -lfront -lback a megfelelő sorrend. Ciklikus
archívumfüggőségnél a GNU linker --start-group és
--end-group kapcsolói ismételt keresést kérhetnek, de a
ciklus gyakran modulhatár-problémát jelez.
Megosztott könyvtáraknál se feltételezzünk korlátlan
sorrendfüggetlenséget: például a --as-needed beállítás
befolyásolja, mely könyvtárakhoz marad meg futásidejű függőségi
bejegyzés.
GCC/ELF-eszközlánc: a három kapcsolócsoport különböző feladatot végez.
| Kapcsoló | Fázis | Rendeltetés |
|---|---|---|
-fPIC |
Fordítás | Megosztott objektumhoz alkalmas helyfüggetlen kód |
-shared |
Linkelés | Megosztott objektum előállítása |
-fPIE, majd -pie |
Fordítás, majd linkelés | Helyfüggetlen végrehajtható program előállítása |
A position-independent code nem azt jelenti, hogy semmilyen relokáció nem szükséges. Azt jelenti, hogy a generált kód a célplatform megfelelő címzési mechanizmusaival támogatja az eltérő betöltési címet.
A PIE (position-independent executable) továbbra is program,
nem általánosan használható könyvtár. A példában a
count-static is PIE: a saját count kódját
statikusan tartalmazza, miközben helyfüggetlen végrehajtható
állomány.
ELF-ben a shared object és a PIE is lehet ET_DYN típusú.
Ez az egy fejlécmező tehát önmagában nem elegendő a szerep eldöntéséhez.
A betöltési mechanizmusokat az ELF-betöltés, PLT/GOT
és dinamikus szimbólumok rész bontja ki.
A SONAME a megosztott könyvtárba írt logikai függőségi
név. A példában:
libcount.so.1.0;DT_SONAME értéke
libcount.so.1;DT_NEEDED bejegyzése ezért
libcount.so.1.A libcount.so fejlesztői szimbolikus link segíti a
linkelést, de a futásidejű keresés nem erre a névre támaszkodik. A
SONAME nem automatikus kompatibilitásvizsgálat: az azonos
nevet viselő, hibásan inkompatibilis könyvtár is megtalálható és
betölthető.
Linux/glibc: ha a függőség neve nem tartalmaz perjelet, normál, nem biztonsági végrehajtási módban a keresés fő sorrendje leegyszerűsítve:
DT_RPATH bejegyzések, a megfelelő
DT_RUNPATH hiányában.LD_LIBRARY_PATH.DT_RUNPATH
bejegyzése./etc/ld.so.cache.A részletes algoritmust befolyásolja a függőségi lánc, a glibc verziója, a hardverképesség-alapú könyvtárválasztás és egyes linkkapcsolók. Biztonsági végrehajtási módban, például bizonyos privilegizált programoknál, környezeti változók figyelmen kívül maradhatnak. Más libc betöltőjére ezt a sorrendet nem szabad automatikusan átvinni.
A régi RPATH és az újabb RUNPATH fontos
eltérése, hogy a RUNPATH csak az adott objektum közvetlen
függőségeinek keresésére vonatkozik. Ha a libcount egy
további privát könyvtárat igényel, annak megtalálását nem garantálja a
főprogram RUNPATH értéke. Az RPATH a függőségi
láncon tovább is érvényesülhet, és az LD_LIBRARY_PATH-hoz
viszonyított elsőbbsége is eltér.
A példában a --enable-new-dtags kifejezetten az új
bejegyzést kéri. A $ORIGIN a bejegyzést tartalmazó bináris
könyvtárát jelöli, ezért a build/bin alatti program a
szomszédos build/lib könyvtárban kereshet. Az egyszeres
idézőjel megakadályozza, hogy a shell idő előtt helyettesítse a
dollárjeles kifejezést.
Ez áthelyezhető csomagelrendezést támogat, de csak megfelelő jogosultságokkal biztonságos: nem megbízható felhasználó által írható keresési könyvtár kódbetöltési kockázat.
A külön laborban, a programok elindítása nélkül vizsgálhatók a metaadatok:
ar t build/lib/libcount.a
readelf -hW build/bin/count-shared
readelf -dW build/bin/count-shared
readelf -dW build/lib/libcount.so.1.0
readelf -dW build/bin/count-staticAz ellenőrzési feltételek:
count.pic.o tagot;SONAME értéke
libcount.so.1;NEEDED bejegyzés és
megfelelő RUNPATH;libcount-függőség, de a
libc dinamikus függősége megmarad.Az ldd az aktuális környezetben feloldott függőségekhez
hasznos, de ismeretlen, nem megbízható binárison ne
használd: megvalósítástól és a bináris interpreterétől függően
kódvégrehajtást is előidézhet. A readelf adatként értelmezi
az állományt; ez előnyösebb, bár sérült bemenetekhez naprakész eszköz és
elkülönített vizsgálókörnyezet indokolt.
A readelf -d sem teljes futásidejű bizonyíték: közvetlen
metaadatokat mutat, nem a teljes feloldott függőségi fát, és nem látja
előre az alkalmazás által később dinamikusan betöltött modulokat.
Az API a forrásból látható interfész és viselkedési szerződés. Az ABI a már lefordított részek együttműködésének bináris szerződése: többek között szimbólumnevek, paraméterátadás, visszatérés és adatreprezentáció.
| Változtatás | Kompatibilitási következmény |
|---|---|
| Belső algoritmus cseréje változatlan szerződéssel | Lehet API- és ABI-kompatibilis |
| Új függvény hozzáadása | A régi fogyasztóknak általában kompatibilis |
| Meglévő szimbólum eltávolítása | Régi fogyasztók működését megszakíthatja |
unsigned helyett unsigned long
paraméter |
A mintaprofilban eltérő típusszélesség; ABI-vizsgálat és rendszerint új ABI-verzió szükséges |
Inkompatibilis ABI-váltáskor szokás új SONAME, például
libcount.so.2 használata. Egy .so.1 nevű
szimbolikus link átirányítása egy inkompatibilis .so.2
állományra nem javítás.
Statikus felhasználásnál a könyvtár frissítése után a fogyasztót újra kell linkelni. Kompatibilis megosztott könyvtárfrissítésnél ez elkerülhető lehet, de a már futó folyamat nem cseréli le automatikusan a betöltött kódját: a telepítési tervnek újraindítással is számolnia kell.
A -L nem futásidejű keresési útvonal; a sikeres linkelés
ezért nem bizonyítja a telepítés helyességét. A .a
használata nem helyettesíti a teljes statikus linkelés külön döntését. A
.so fájlnév pedig önmagában nem jelent megfelelő
SONAME-ot vagy kompatibilis ABI-t.
A javítás közös mintája: a buildparancs mellett mindig vizsgáld a tényleges ELF-metaadatokat, majd külön ellenőrizd a futtatási környezetet.
count-static
egyszerre PIE és részben dinamikusan linkelt program? Melyik állomány
nem kell a telepítéséhez?unsigned count_zero(void) függvényt, amely nullát ad
vissza. Készíts új megosztott könyvtárat változatlan
SONAME-mal. Indokold a kompatibilitás irányát.libcount.so.1 fájlt. A csomag csak
libcount.so és libcount.so.1.0 állományokat
tartalmaz. Mit ellenőrzöl és javítasz?A PIE a program címzési és betöltési tulajdonsága. A statikus
libcount-felhasználás azt jelenti, hogy annak szükséges
objektumtagja bekerült a programba. Ettől a libc még dinamikus függőség
maradhat. A libcount.a nem kell futáskor.
Ellenőrzés: a count-static dinamikus szakaszában nincs
libcount-hoz tartozó NEEDED.
A header védelmén belüli deklaráció:
unsigned count_zero(void);A count.c kiegészítése, az eredeti C17-profilban:
unsigned count_zero(void)
{
return 0u;
}Az előző fordítási és megosztott linkelési parancsok ismétlésével elkészíthető az új változat. Ellenőrizd a dinamikus szimbólumtáblát:
readelf --dyn-syms -W build/lib/libcount.so.1.0A régi fogyasztó továbbra is megtalálja a változatlan
count_add függvényt. Az új count_zero
függvényt használó fogyasztó viszont nem működik a régi könyvtárral. A
kompatibilitás tehát nem szimmetrikus; a csomagfüggőségnek szükség
esetén minimumverziót is ki kell fejeznie.
Először a fogyasztó NEEDED és RUNPATH
bejegyzéseit vizsgáljuk. Ezután ellenőrizzük, hogy a tényleges keresési
könyvtárban létezik-e a kért libcount.so.1 név.
A mintacsomagból hiányzik a futásidejű szimbolikus link:
ln -sfn libcount.so.1.0 build/lib/libcount.so.1Ez csak akkor megfelelő javítás, ha a célállomány valóban az elvárt
ABI-t valósítja meg. Az ellenőrzési feltétel a teljes, elérhető névlánc
és a helyes keresési útvonal, nem pusztán a .so
kiterjesztésű fájl jelenléte.
A statikus archívum linkeléskori objektumforrás; a megosztott
könyvtár futásidejű függőség. A linksorrend, a SONAME, a
DT_NEEDED és a loader keresési útvonalai külön
szerződéselemek. Megbízható kiadásnál ezeket együtt kell ellenőrizni az
API/ABI-kompatibilitással és a telepített állományokkal.