A PDFlibPas 32 bites Windowson Free Pascal alatt is fordul, és a nehéz rész sosem a Pascal volt. Hanem az objektumfájlok: az AES és az OpenJPEG objektumok, amelyeket a Delphi build linkel, OMF formátumúak, a Free Pascal belső linkere COFF-ot kíván, és a kettő közötti átalakítás olyan szekcióneveket és szekciódefiníciós szimbólumokat ad, amelyekre a linker diagnosztika helyett belső hibákkal reagál
Aki valaha linkelt C objektumokat Pascal könyvtárba, az ismeri ezt a terepet. A Win64 viszonylag civilizált: egy objektumformátum, egy hívási konvenció, névfestés nélkül. A Win32 viszont megőrzi a platform által felhalmozott történelem minden rétegét, és egy harmadik féltől származó C kódot statikusan linkelő könyvtár mindegyikkel egyszerre találkozik
A fordító könyvtárneve nem árulja el a célt
Kezdje a build belépési pontjánál, mert ha itt rontja el, az objektumfájlok érintése előtt órákat veszít. A Free Pascal telepítési könyvtárának neve azt mondja meg, hol lakik a fő fordító, nem azt, mit állít elő. Egy 32 bites gazdafordító meghívhatja a mellette ülő cross-compilert, és a megfelelő célkapcsolókkal 64 bites kódot ad ki, tehát a cél kikövetkeztetése az útvonalból találgatás, ami éppen működik, amíg valaki át nem rendezi a toolchaint
A megbízható út az, ha megkérdezi a fordítót. Kérdezze le a tényleges célprocesszort és operációs rendszert a fordító saját információs kapcsolóin keresztül, és fogadja el mindkét szokásos telepítési elrendezést, a lapos bináris könyvtárat és a verzió szerint egymásba ágyazottat, mert a különböző telepítők és toolchain-kezelők különböző formákat hagynak maguk után. A valamelyik elrendezést beégető build szkript pontosan egy gépen működik
Miért töri meg az átalakított objektumfájl a belső linkert?
Mert az átalakítás megőrzi az OMF szekcióelnevezési konvenciót, és olyan szekciódefiníciós szimbólumokat gyárt, amelyek nem egyeznek azzal, amit a COFF linker vár. Az OMF objektumok COFF-ra váltása szükséges, de nem elegendő: a keletkező fájlok a klasszikus _TEXT, _DATA és _BSS szekcióneveket viselik, hozzájuk a belőlük származtatott szekciódefiníciós szimbólumneveket, és ha ezt eteti a Free Pascal belső linkerébe, a szekcióelnevezésre utaló üzenet helyett belső fordítóhibákat kap
A belső hiba a legrosszabb hibamód egy build problémára, mert semmit sem mond arról, mi volt baj a bemenettel. A javítás egy átalakítás utáni normalizáló menet a COFF fájlon: írja át a szekcióneveket a várt formára, és a hozzájuk illő szekciódefiníciós szimbólumokat is, miközben a szimbólumindex, a kódbájtok és a relokációk érintetlenül maradnak. Az utolsó megkötés az egész nehézség. Az olyan átírás, amely újszámozza a szimbólumokat vagy eltolja az offseteket, olyan objektumot ad, amely linkel, aztán elszáll
A két objektumkészlet egyikéhez egy előzetes lépés is kell. A klasszikus 32 bites C++ fordítóval épített OpenJPEG objektumok Delphi privát 64 bites egészsegéd-rutinoktól függenek, amelyeket a Free Pascal nem biztosít, tehát egyetlen formátumátalakítás sem teszi őket használhatóvá. Ezeket először a Clang alapú fordítóval építik újra, amely ezeket a függőségeket nem emitálja, és csak utána alakítják át
// Az FPC célhoz tartozó objektumok saját könyvtárban élnek. Nem
// váltják fel a Delphi objektumkészletet, mert mindkét toolchain
// ugyanabból a forrásfából épít, és mindegyiknek saját linkbemenet kell
//
// Lib\thirdparty\Win32 Delphi OMF objektumok, változatlanul
// Lib\thirdparty\Win32f FPC COFF objektumok, átalakítva és normalizálva
//
// Build belépési pontok:
// build-Win32-Lib-FPC.cmd
// build-Win64-Lib-FPC.cmd
A fordító privát segédjei nem hordozhatók, és a konvenciójuk sem
A Delphi futtatókörnyezete assembly trampoline-okat szolgáltat a 64 bites egészműveletekhez 32 bites x86-on, és a Delphi számára előre lefordított C objektumok ezekbe hívnak bele. A Free Pascalnak megvan a maga megoldása, tehát ezekre a hivatkozásokra másképp kell választ adni, nem átirányítással. Az a részlet, amely az átirányítást lehetetlenné teszi, a hívási konvenció: a képkezelő kód által használt időzítő segéd négybájtos argumentumát a hívott fél takarítja le, míg a 64 bites osztó segéd tizenhat bájtot, és az eredményét a klasszikus regiszterpárban adja vissza. Két segéd, két konvenció, és az egyikre írt trampoline csendben megrongálja a vermet a másiknál
A névfestés adja a probléma második felét. Win32-n a Free Pascal a külső C importok elé automatikusan aláhúzást tesz, miközben a public name deklarációkat szóról szóra exportálja, tehát ugyanannak a hídnak az import oldala és export oldala különböző szabályok szerint él. Ezért annak a C futásidejű hídnak, amelyre az OpenJPEG-nek szüksége van, pontosan a C szimbólumneveket kell exportálnia, a variadikus belépési pontoknak pedig 32 bites közvetett ugrásra van szükségük, nem közvetlenre. Ebből semmi sem egzotikus, ha már kimondták. Mindegyik linkhibaként bukik meg, amely egy senki által nem írt szimbólumot nevez meg
Mitől halt meg egy Win32 futtatható fájl a main előtt?
Egy 64 bites DLL a keresési útvonalon, ahová azért jutott el a program, mert a Free Pascal zlib unitja dinamikusan köt, nem statikusan linkel. A tünet azonnali kilépés volt érvénytelen-kép státuszkóddal, még a programban lévő bármilyen Pascal kód lefutása előtt, ami arra küldi az embert, hogy az imént épített programját bámulja, miközben a hiba abban van, hogy a betöltő egy importot rossz architektúrához old fel
A tanulság a feltevésekről szól, nem a zlibről. Egy tömörítő könyvtárról elnevezett unit nem feltétlenül tartalmaz ilyet; lehet olyan kötés is, amely futásidőben osztott könyvtárat vár, és egy nem szándékolt dinamikus függőség telepítési teher akkor is, ha éppen feloldódik. A tiszta Pascal stream implementációra váltás mindkét célhoz statikusan befordított tömörítési utat ad külső függőség nélkül, és pontosan ez kellene annak a könyvtárnak, amelyet valaki más alkalmazásába ágyaznak be
Ugyanez az ösztön érvényes a külső JBIG2 encoder backendre is. A 32 bites célon a külső encoder nincs belinkelve, tehát a kérések a beépített Pascal encoderhez esnek vissza, és az ezt ellenőrző tesztnek az aktuális cél regisztrációs állapotát kell megnéznie, nem pedig a sikeres kódolást kell bizonyítéknak vennie arra, hogy a külső backend jelen van. Egy működő fallback pontosan az, ami elrejt egy hiányzó függőséget, és ez a hibaminta, amelyet a néma stub hibák diagnosztizálása tárgyal. A 64 bites statikus linkelés munkáját a jbig2enc FPC alatti statikus linkelése fedi le
32 bites aritmetika a memóriastreamen
A pufferméreteket pointer-szélességű, előjel nélküli aritmetikával kezelő kód Win64-en helyes, és Win32-en egyetlen nagy kéttől van átvitelig. A JPEG 2000 kodeket ellátó memóriabeli stream duplázással nő és összeadással halad, és egy 32 bites célon mindkét művelet átcsoroghat olyan bemeneteken, amelyek nagyok, de teljesen legálisak
Minden írás, átugrás, seek és kezdeti foglalás ezért számolás előtt ellenőriz, és a kapacitásplafon a maximális előjeles pointer-szélességű érték, amelyet ahhoz választottak, amit a blokkmozgató rutin és a callback visszatérési értékei ki tudnak fejezni. A viselkedési követelmény arra az esetre, amikor egy kérést elutasítanak, könnyen félremegy: az elutasítás nem változtathatja meg a stream pozícióját sem, sem a hosszát. Egy részleges módosítás, amelyet hiba követ, olyan állapotban hagyja a streamet, amiről a hívó nem tud következtetni, és a következő művelet ráhalmoz
// Ellenőrizzen, mielőtt számol. Win32-n mindkettő átcsorog olyan
// bemeneteken, amelyeket egy nagy JPEG 2000 kép legálisan előállít
if Needed > NativeUInt(High(NativeInt)) - FPosition then
Exit(False); // elutasítás, pozíció és méret érintetlen
NewCapacity := FCapacity;
while (NewCapacity < FPosition + Needed) do
begin
if NewCapacity > NativeUInt(High(NativeInt)) shr 1 then
Exit(False); // a duplázás átvinné a tartományt
NewCapacity := NewCapacity shl 1;
end;
Két buildkimeneti csapda, amelyek túlélik a portolást
A teszt- és minta-executablek szétválasztása célarchitektúránként saját kimeneti könyvtárakba nyilvánvalóan helyes, és azonnal megtörik bármi, ami a tesztadatait felfelé számolt könyvtárszintek alapján találta meg. A javítás az, hogy felfelé keresve találja meg az eszközkönyvtárat, rögzített mélység feltételezése helyett, egy szándékos megszorítással: a signing minta csak a saját projektkönyvtárából fogad el tartalék tanúsítványt, soha nem tetszőleges ősökönyvtárból, mert egy azonos nevű tanúsítvány, amelyet messzebb a fában találunk, biztonsági meglepetés, nem kényelem
A második csapda minden portot túléli, és érdemes magával vinni bármely FPC projektbe. Fordítófrissítés után nem elég, ha a fordító elutasítja az elavult PPU fájlokat, mert a linker továbbra is a maradvány objektumfájlokat részesíti előnyben a unit keresési útvonalon, akkor is, ha a betöltött PPU helyes könyvtárból származott, és egy explicit objektumkimeneti útvonal megadása ezt a preferenciát nem írja felül. Az egyetlen megbízható válasz friss, ideiglenes unit könyvtár build körönként. Bármi kevesebb olyan binárist ad, amelyet két fordítóverzióból linkeltek, és az olyan módokon bukik meg, amelyek forráshibának látszanak
A platformfeltételek az utolsó darab, és a helyes tengely megválasztása fontosabb, mint amennyinek látszik. A helyes kérdés általában az, hogy a kód Windows-specifikus-e, és nem az, hogy egy adott widget könyvtár jelen van-e, ahogy a EMF vektorimport és platformfeltételek cikk metafájl-átalakítási munkája megmutatta: az őrző átállítása vezérlőkönyvtár-feltételről platformfeltételre az állítólagos újraírást egyetlen direktíva megváltoztatására csökkentette. A Free Pascal és a Lazarus támogatása mindkét Windows célhoz a PDFlibPas Delphi PDF könyvtárral együtt jár, amelyet a Delphi és a C++Builder csomagokkal azonos forrásokból építenek