PDFlibPas se pod Free Pascalem sestavuje i pro 32bitové Windows, a to těžké v tom nikdy nebyl Pascal. Byly to objektové soubory: objekty AES a OpenJPEG, které linkuje sestavení Delphi, jsou ve formátu OMF, interní linker Free Pascalu vyžaduje COFF, a převod mezi oběma produkuje názvy sekcí a symboly definic sekcí, kvůli nimž linker padá s internal errors místo pořádné diagnostiky
Kdo už někdy linkoval C objekty do Pascalovské knihovny, ten tenhle terén zná. Win64 je v tomhle ohledu poměrně civilizovaný: jeden formát objektů, jedna volací konvence a žádná dekorace jmen. Win32 si naopak nese každou vrstvu historie, kterou platforma nashromáždila, a knihovna, která staticky linkuje C kód třetích stran, se s ní všemi setká najednou
Z adresáře kompilátoru cíl nepoznáte
Začněte u vstupního bodu sestavení, protože chyba tady zahodí hodiny, ještě než se vůbec provede kterýkoli objektový soubor. Název instalačního adresáře Free Pascalu říká, kde bydlí hlavní kompilátor, ne co vyrábí. 32bitový hostitelský kompilátor může vedle sebe vyvolat cross-kompilátor a při správných přepínačích cíle vyslat 64bitový kód, takže odhadovat cíl z cesty je hádání, které funguje právě do chvíle, kdy někdo přeuspořádá svůj toolchain
Spolehlivá cesta je zeptat se kompilátoru. Skutečný cílový procesor a operační systém zjistíte přes informační přepínače kompilátoru, a počítejte s oběma běžnými layouty instalace, s plochým binárním adresářem i s verzí vnořenou do podadresářů, protože různé instalátory a správci toolchainů vyrábějí různé tvary. Build skript, který natvrdo zapeče jeden konkrétní layout, funguje přesně na jediném stroji
Proč zkonvertovaný objektový soubor rozbije interní linker?
Protože převod zachová konvenci pojmenovávání sekcí z OMF a vysyntetizuje symboly definic sekcí, které neodpovídají tomu, co COFF linker čeká. Konverze OMF objektů na COFF je nutná, ale nikoli dostačující: výsledné soubory nesou klasické názvy sekcí _TEXT, _DATA a _BSS plus z nich odvozené názvy symbolů definic sekcí, a když to nakrmíte internímu linkeru Free Pascalu, dostanete interní chyby kompilátoru místo zprávy o pojmenovávání sekcí
Interní chyba je u buildovacího problému nejhorší možný způsob selhání, protože o tom, co bylo špatně na vstupu, neříká vůbec nic. Řešením je normalizační průchod nad COFF souborem po konverzi: přepsat názvy sekcí do očekávané podoby a přepsat odpovídající symboly definic sekcí tak, aby ladily, přičemž symbolový index, bajty kódu i relokace zůstanou nedotčené. Právě ta poslední podmínka je celá obtíž. Přepis, který přečísluje symboly nebo posouvá offsety, vyprodukuje objekt, který se sice linkuje a pak spadne
Pro jednu z obou sad objektů je tu ještě předběžný krok. Objekty OpenJPEG sestavené klasickým 32bitovým C++ kompilátorem závisí na privátních 64bitových celočíselných helper rutinách Delphi, které Free Pascal neposkytuje, takže žádná konverze formátu je neučiní použitelnými. Ty se proto nejdřív znovu sestaví kompilátorem založeným na Clangu, který tyhle závislosti nevysílá, a teprve potom se zkonvertují
// Objekty pro cíl FPC žijí ve vlastním adresáři. Nenahrazují sadu
// objektů Delphi, protože oba toolchainy sestavují ze stejného
// zdrojového stromu a každý potřebuje vlastní vstupy linkování
//
// Lib\thirdparty\Win32 OMF objekty Delphi, beze změn
// Lib\thirdparty\Win32f COFF objekty pro FPC, zkonvertované a normalizované
//
// Vstupní body sestavení:
// build-Win32-Lib-FPC.cmd
// build-Win64-Lib-FPC.cmd
Privátní helpery kompilátoru nejsou přenositelné, a ani jejich konvence
Delphi runtime dodává assembly trampolíny pro 64bitové celočíselné operace na 32bitovém x86 a předkompilované C objekty stavěné pro Delphi se do nich odkazují. Free Pascal má vlastní uspořádání, takže tyhle reference se musí uspokojit jinak, nikoli přesměrovat. Detail, který přesměrování znemožňuje, je volací konvence: timing helper používaný imagingovým kódem má svůj čtyřbajtový argument uklizený volaným, zatímco helper 64bitového dělení uklízí šestnáct bajtů a výsledek vrací v klasickém páru registrů. Dva helpery, dvě konvence, a trampolína napsaná pro ten jeden tiše rozhází zásobník tomu druhému
Druhou polovinu problému přidává dekorace jmen. Na Win32 Free Pascal automaticky prefixuje externí C importy podtržítkem, zatímco deklarace public name exportuje doslova, takže importní a exportní strana téhož mostu se řídí různými pravidly. C runtime most, který OpenJPEG potřebuje, proto musí exportovat přesné symbolové názvy z C a variadické vstupní body potřebují 32bitový nepřímý skok místo přímého. Jakmile je to takhle vysvětlené, není na ničem z toho nic exotického. Celé to ale selhává jako linkovací chyba uvádějící symbol, který nikdo nenapsal
Co donutilo Win32 spustitelný soubor zemřít před main?
64bitová DLL na search path, na kterou se program dostal proto, že jednotka zlib Free Pascalu se váže dynamicky místo statického linkování. Symptomem bylo okamžité ukončení se statusovým kódem invalid image, ještě před spuštěním jakéhokoli Pascalovského kódu, což člověka pošle zkoumat program, který právě sestavil, zatímco zrada je v loaderu párujícím import se špatnou architekturou
Poučení se týká předpokladů, ne zlib. Jednotka pojmenovaná po kompresní knihovně nemusí nutně nějakou obsahovat; může to být binding čekající za běhu na sdílenou knihovnu a dynamická závislost, kterou jste nezamýšleli, je deployní zátěž i v okamžiku, kdy se náhodou zrovna vyřeší. Přechod na čistě Pascalovskou implementaci streamů dává oběma cílům staticky zahrnutou kompresní cestu bez jediné externí závislosti, a přesně to by měla mít knihovna zabudovaná v cizí aplikaci od začátku
Týž instinkt platí pro externí backend JBIG2 encoderu. Na 32bitovém cíli se externí encoder nelinkuje, takže požadavky spadnou na vestavěný Pascalovský encoder, a test, který tohle ověřuje, musí zkontrolovat registrační stav aktuálního cíle místo toho, aby bral úspěšné kódování jako důkaz přítomnosti externího backendu. Fallback, který funguje, je přesně ta věc, která schovanou chybějící závislost ukrývá, a to je selhávácí vzor rozebraný v článku diagnostika tichých selhání stubů. Práci se statickým linkováním na 64 bitů pokrývá statické linkování jbig2enc pod FPC
32bitová aritmetika na memory streamu
Kód, který manipuluje s velikostmi bufferů pomocí unsigned aritmetiky šířky ukazatele, je na Win64 správně a na Win32 je od přetečení vzdálen jeden velký obrázek. In-memory stream, který krmí kodek JPEG 2000, roste zdvojnásobováním a posunuje se sčítáním, a na 32bitovém cíli mohou obě operace přetéct na vstupech velkých, ale naprosto legitimních
Každý zápis, skip, seek i počáteční alokace se proto nejdřív zkontroluje a teprve pak počítá, a strop kapacity je maximální signed hodnota šířky ukazatele, zvolená tak, aby odpovídala tomu, co dokážou vyjádřit rutina blokového přesunu i návratové hodnoty callbacku. Behaviorální požadavek při odmítnutí se snadno pokazí: odmítnutí nesmí změnit pozici streamu ani jeho délku. Částečná mutace následovaná chybou nechá stream ve stavu, nad nímž volající nedokáže uvažovat, a další operace to ještě znásobí
// Nejdřív zkontroluj, pak počítej. Na Win32 obě věci přetečou na vstupech,
// které velký obrázek JPEG 2000 produkuje zcela legitimně
if Needed > NativeUInt(High(NativeInt)) - FPosition then
Exit(False); // odmítni, pozici a velikost nech být
NewCapacity := FCapacity;
while (NewCapacity < FPosition + Needed) do
begin
if NewCapacity > NativeUInt(High(NativeInt)) shr 1 then
Exit(False); // zdvojnásobení by přeteklo
NewCapacity := NewCapacity shl 1;
end;
Dvě pasti build výstupů, které port přežijí
Oddělit testovací a ukázkové spustitelné soubory podle cílové architektury do výstupních adresářů per cíl je jasně správně a okamžitě rozbije cokoli, co si svoje testovací data hledalo počítáním adresářových úrovní nahoru. Řešením je hledat adresář s assety směrem nahoru místo předpokládat pevnou hloubku, s jedním záměrným omezením: signing sample přijme certifikátový fallback jen ze svého vlastního projektového adresáře, nikdy z libovolného předka, protože stejně pojmenovaný certifikát nalezený výš ve stromu je bezpečnostní překvapení, ne pohodlí
Druhá past přežije každý port a stojí za to ji vzít do jakéhokoli FPC projektu. Po upgradu kompilátoru není dost, že kompilátor odmítne zastaralé PPU soubory, protože linker dál dává přednost zbytkovým objektovým souborům v unit search path, i když PPU, které načetl, pochází ze správného adresáře, a přidání explicitní cesty k objektovému výstupu tu preferenci nepřebije. Jediná spolehlivá odpověď je čerstvý dočasný adresář unit na každé kolo sestavení. Cokoli méně vyprodukuje binárku linkovanou ze dvou verzí kompilátoru, která selhává způsoby vypadajícími jako chyby ve zdrojácích
Platformové podmíněné kompilace jsou poslední dílek a volba správné osy záleží víc, než se zdá. Správná otázka obvykle zní, zda je kód specifický pro Windows, ne zda je přítomna ta či ona widget knihovna, jak ukázala práce na metafile konverzi v článku import EMF vektorů a platformové podmínky: přepnutí té hlídky z podmínky na control knihovnu na podmínku platformy změnilo domnělé přepsání na změnu jediné direktivy. Podporu Free Pascalu a Lazarusu pro oba Windows cíle přináší PDFlibPas Delphi PDF knihovna, sestavovaná ze stejných zdrojáků jako balíčky pro Delphi a C++Builder