PDFlibPas sa zostavuje pod Free Pascal pre 32-bitové Windows a tvrdý oriešok nikdy nebol v Pascale. Boli to objektové súbory: objekty AES a OpenJPEG, ktoré Delphi build linkuje, sú vo formáte OMF, interný linker Free Pascal vyžaduje COFF a konverzia medzi nimi vytvára názvy sekcií a symboly definícií sekcií, ktoré navedú linker k interným chybám namiesto zrozumiteľnej diagnostiky
Ktokoľvek, kto už niekedy linkoval C objekty do Pascal knižnice, pozná tento terén. Win64 je v porovnaní kultivovaný: jeden formát objektov, jedna konvencia volania a žiadna dekorácia mien. Win32 si zachováva každú vrstvu histórie, ktorú platforma nazbierala, a knižnica, ktorá staticky linkuje C kód tretích strán, narazí na všetky z nich súčasne
Adresár prekladača vám nepovie, aký je cieľ
Začnite vstupným bodom zostavenia, pretože chyba tu zahodí hodiny skôr, než sa vôbec dostanete k objektovým súborom. Názov inštalačného adresára Free Pascal identifikuje, kde býva hlavný prekladač, nie to, čo vyrába. 32-bitový hostiteľský prekladač môže zavolať cross-compiler sediaci vedľa neho a vyprodukovať 64-bitový kód, keď mu zadáte správne prepínače cieľa, takže odhadovať cieľ z cesty je hádanie, ktoré funguje len dovtedy, kým niekto presťahuje svoj toolchain
Spoľahlivý prístup je spýtať sa priamo prekladač. Zisťujte skutočný cieľový procesor a operačný systém cez informačné prepínače prekladača a akceptujte obe bežné usporiadania inštalácie: plochý binárny adresár aj ten vnorený podľa verzií, pretože rôzni inštalátory a správcovia toolchainov produkujú rôzne tvary. Build skript, ktorý má niektoré usporiadanie natvrdo zapísané, funguje presne na jednom stroji
Prečo konvertovaný objektový súbor rozbije interný linker?
Pretože konverzia zachováva konvenciu pomenovávania sekcií OMF a syntetizuje symboly definícií sekcií, ktoré nezodpovedajú tomu, čo linker COFF očakáva. Konvertovať objekty OMF na COFF je nevyhnutné, ale nepostačujúce: výsledné súbory nesú klasické názvy sekcií _TEXT, _DATA a _BSS plus z nich odvodené názvy symbolov definícií sekcií, a keď to nasadíte do interného linkeru Free Pascal, dostanete interné chyby prekladača namiesto správy o pomenovávaní sekcií
Interná chyba je najhorší režim zlyhania pri build probléme, lebo nič nepovie o tom, čo bolo zlé na vstupe. Riešením je normalizačný prechod nad COFF súborom po konverzii: prepísať názvy sekcií do očakávanej podoby a prepísať zodpovedajúce symboly definícií sekcií tak, aby sedeli, pritom nechať nedotknutý symbolový index, kódové bajty aj relokácie. Práve to posledné obmedzenie je celá ťažkosť. Prepis, ktorý prečísluje symboly alebo posúva ofsety, vyrobí objekt, ktorý sa zlinkuje a potom spadne
Pre jednu z dvoch sád objektov je potrebný ešte prípravný krok. Objekty OpenJPEG zostavené klasickým 32-bitovým C++ prekladačom závisia na súkromných pomocných rutinách Delphi pre 64-bitové celé čísla, ktoré Free Pascal neposkytuje, takže žiadna konverzia formátu z nich neurobí použiteľný kód. Tie sa najprv znovu zostavia Clang-based prekladačom, ktorý tieto závislosti negeneruje, a až potom sa skonvertujú
// Objekty pre cieľ FPC bývajú vo vlastnom adresári. Nenahrádzajú
// sadu objektov Delphi, pretože obe toolchainy stavajú z rovnakého
// zdrojového stromu a každý potrebuje vlastné vstupy na linkovanie
//
// Lib\thirdparty\Win32 objekty Delphi OMF, nezmenené
// Lib\thirdparty\Win32f objekty FPC COFF, skonvertované a normalizované
//
// Vstupné body zostavenia:
// build-Win32-Lib-FPC.cmd
// build-Win64-Lib-FPC.cmd
Privátne helpery prekladača nie sú prenositeľné a ich konvencie tiež nie
Behové prostredie Delphi dodáva trampolíny v assembleri pre 64-bitové celočíselné operácie na 32-bitovom x86 a predkompilované C objekty určené pre Delphi do nich volajú. Free Pascal má vlastné usporiadanie, takže tieto referencie treba naplniť iným spôsobom, nie ich presmerovať. Detail, ktorý presmerovanie znemožňuje, je konvencia volania: časovací helper používaný imaging kódom má štvorbajtový argument čistený volaným kódom, kým helper 64-bitového delenia čistí šestnásť bajtov a výsledok vracia v klasickej dvojici registrov. Dva helpery, dve konvencie a trampolína napísaná pre jeden vám ticho poškodí zásobník pri druhom
Druhú polovicu problému pridáva dekorácia mien. Na Win32 dáva Free Pascal externým C importom automaticky predponu podčiarknika, kým deklarácie public name exportuje doslova, takže importná aj exportná strana toho istého mosta sa riadia odlišnými pravidlami. C runtime most, ktorý OpenJPEG potrebuje, preto musí exportovať presné názvy C symbolov a variadické vstupné body vyžadujú 32-bitový nepriamy skok namiesto priameho. Keď je to raz povedané, nič z toho nie je exotika. Napriek tomu to celé zlyhá ako linková chyba, ktorá menuje symbol, ktorý nenapísal nikto
Čo spôsobilo, že Win32 spustiteľný súbor zomrel pred main?
64-bitová DLL na search path, ku ktorej sa program dostal preto, že unita zlib Free Pascal sa viaže dynamicky, nie staticky. Príznakom bolo okamžité ukončenie so statusovým kódom neplatného obrazu ešte pred tým, než bežal akýkoľvek Pascal kód v programe, čo vás pošle skúmať program, ktorý ste práve zostavili, hoci chyba je v loaderi, ktorý radi import proti nesprávnej architektúre
Ponaučenie sa týka predpokladov, nie zlib. Unita pomenovaná po kompresnej knižnici ich nemusí nevyhnutne obsahovať; môže to byť binding, ktorý očakáva zdieľanú knižnicu za behu, a dynamická závislosť, ktorú ste nezamýšľali, je pri nasadení záväzok aj vtedy, keď sa náhodou vyrieši. Prechod na čistú Pascal implementáciu streamu dáva obom cieľom staticky zahrnutú kompresnú cestu bez akejkoľvek externej závislosti, čo by mala mať knižnica vkladaná do cudzej aplikácie od začiatku
Ten istý inštinkt platí pre externý backend JBIG2 enkodéra. Na 32-bitovom cieli nie je externý enkodér linkovaný, takže požiadavky padajú na vstavaný Pascal enkodér a test, ktorý to overuje, musí skúmať registračný stav aktuálneho cieľa, namiesto aby úspešné kódovanie považoval za dôkaz prítomnosti externého backendu. Fallback, ktorý funguje, je presne tou vecou, ktorá skrýva chýbajúcu závislosť, a to je vzor zlyhania rozobraný v článku diagnostika tichých zlyhaní stubov. Statické linkovanie pre 64 bitov je rozobrané v článku statické linkovanie jbig2enc pod FPC
32-bitová aritmetika na memory streame
Kód, ktorý manipuluje s veľkosťami bufferov pomocou aritmetiky bez znamienka v šírke pointera, je na Win64 správny a na Win32 je od pretečenia vzdialený jeden veľký obrázok. In-memory stream, ktorý živí kodek JPEG 2000, rastie zdvojovaním a posúva sa sčítaním a na 32-bitovom cieli môžu obe operácie pretiecť pri vstupoch, ktoré sú veľké, ale úplne legitímne
Každý zápis, preskočenie, seek aj počiatočná alokácia preto skontroluje skôr, než počíta, a strop kapacity je maximálna znamienková hodnota v šírke pointera, zvolená tak, aby sedela s tým, čo dokáže vyjadriť rutina blokového presunu aj návratové hodnoty callbackov. Behaviorálna požiadavka pri zamietnutej požiadavke sa dá ľahko pokaziť: zamietnutie nesmie zmeniť pozíciu v streame ani jeho dĺžku. Čiastočná mutácia nasledovaná chybou nechá stream v stave, o ktorom volajúci nemôže nič predpokladať, a ďalšia operácia to ešte zhorsí
// Najprv skontroluj, potom počítaj. Na Win32 obe tieto operácie pretečú
// pri vstupoch, ktoré veľký obrázok JPEG 2000 legitímne vyrobí
if Needed > NativeUInt(High(NativeInt)) - FPosition then
Exit(False); // odmietni a nechaj pozíciu aj veľkosť tak
NewCapacity := FCapacity;
while (NewCapacity < FPosition + Needed) do
begin
if NewCapacity > NativeUInt(High(NativeInt)) shr 1 then
Exit(False); // zdvojovanie by preteklo
NewCapacity := NewCapacity shl 1;
end;
Dve pasce build výstupu, ktoré port prežijú
Rozdelenie testovacích aj ukážkových spustiteľných súborov podľa cieľovej architektúry do výstupných adresárov pre jednotlivé ciele je zjavne správne a okamžite rozbije všetko, čo si hľadalo testovacie dáta počítaním úrovní adresárov smerom nahor. Oprava je hľadať adresár s assetmi smerom nahor, namiesto aby ste predpokladali pevnú hĺbku, s jediným zámerným obmedzením: signing vzorka akceptuje certifikátový fallback iba zo svojho projektového adresára, nikdy z ľubovoľného predka, pretože certifikát s rovnakým menom nájdený vyššie v strome je bezpečnostné prekvapenie, nie pohodlie
Druhá pasca prežije každý port a stojí za to preniesť ju do každého FPC projektu. Po upgradu prekladača nestačí, aby prekladač odmietol zastarané PPU súbory, pretože linker aj tak uprednostní zvyškové objektové súbory v unit search path aj vtedy, keď PPU, ktoré načítal, prišlo zo správneho adresára, a pridanie explicitnej cesty na výstup objektov túto preferenciu neprekoná. Jediná spoľahlivá odpoveď je čerstvý dočasný unit adresár pre každé kolo zostavenia. Čokoľvek menej vyrobí binárku zlinkovanú z dvoch verzií prekladača, ktorá zlyhá spôsobmi pôsobiacimi ako chyby v zdrojovom kóde
Podmienený preklad podľa platformy je posledný kus a voľba správnej osi záleží viac, než sa javí. Správna otázka zvyčajne znie, či je kód špecifický pre Windows, nie či je prítomná konkrétna widget knižnica, ako ukázala práca na konverzii metafilov v článku import EMF vektorov a podmienený preklad podľa platformy: prepnutie tej ochrany z podmienky na control knižnicu na podmienku platformy zmenilo údajný prepis na zmenu jednej direktívy. Podpora Free Pascal a Lazarus pre oba Windows ciele prichádza s PDFlibPas Delphi PDF knižnicou, zostavenou z tých istých zdrojov ako balíky Delphi a C++Builder