Tehnički članak

Povezivanje OMF i COFF objekata za FPC Win32 u PDFlibPas-u

PDFlibPas se pod Free Pascal-om gradi za 32-bitni Windows, a teški deo nikada nije bio Pascal. Problem su bile objektne datoteke: AES i OpenJPEG objekti koje Delphi build povezuje su OMF, interni linker Free Pascal-a zahteva COFF, a konverzija između ta dva formata proizvodi imena sekcija i simbole definicija sekcija zbog kojih linker pada sa internim greškama umesto sa pravom dijagnostikom

Svako ko je ikada povezivao C objekte u Pascal biblioteku zna o čemu je reč. Win64 je poredivo uljudan: jedan format objekata, jedna calling convention i nula name decoration-a. Win32 čuva svaki sloj istorije koji je platforma nagomilala, a biblioteka koja statički povezuje C kod trećih strana susreće sve te slojeve odjednom

Ime kompajlerskog direktorijuma ne govori ciljnu platformu

Krenite od build ulazne tačke, jer greška tu košta sati pre nego što bilo koja objektna datoteka uopšte uđe u priču. Ime direktorijuma u koji je instaliran Free Pascal kaže gde živi glavni kompajler, ali ne i šta on proizvodi. 32-bitni host kompajler može da pozove cross-kompajler pored sebe i da emituje 64-bitni kod kad mu prosledite prave ciljne switch-ove, pa je zaključivanje ciljne platforme iz putanje pogađanje koje slučajno radi dok neko ne reorganizuje svoj toolchain

Pouzdan pristup je da pitate kompajler. Upitajte stvarni ciljni procesor i operativni sistem kroz kompajlerove informacione switch-eve, i prihvatite oba uobičajena rasporeda instalacije, ravan binarni direktorijum i onaj ugnježđen po verziji, jer različiti instaleri i alati za upravljanje toolchain-om proizvode različite oblike. Build skripta koja hard-koduje jedan od ta dva rasporeda radi na tačno jednoj mašini

Zašto konvertovana objektna datoteka slomi interni linker?

Zato što konverzija čuva OMF konvenciju imenovanja sekcija i sintetiše simbole definicija sekcija koji se ne poklapaju sa onim što COFF linker očekuje. Konvertovanje OMF objekata u COFF je neophodno, ali nije dovoljno: nastale datoteke nose klasična imena sekcija _TEXT, _DATA i _BSS, plus izvedena imena simbola definicija sekcija, i kada to ugradite u interni linker Free Pascal-a dobijate interne greške kompajlera umesto poruke o imenovanju sekcija

Interna greška je najgori režim otkaza za build problem, jer ne kaže ništa o tome šta je sa ulazom bilo pogrešno. Rešenje je normalizacioni prolaz nad COFF datotekom posle konverzije: prepišite imena sekcija u očekivani oblik i prepišite odgovarajuće simbole definicija sekcija da se poklapaju, a da indeks simbola, bajtovi koda i relokacije ostanu netaknuti. Poslednje ograničenje je sva težina posla. Prepis koji prenumerše simbole ili pomera ofsete proizvodi objekat koji se poveže, a onda se sruši

Ostaje i pripremni korak za jedan od ta dva skupa objekata. OpenJPEG objekti građeni klasičnim 32-bitnim C++ kompajlerom zavise od Delphi privatnih 64-bitnih celobrojnih helper rutina, koje Free Pascal ne isporučuje, pa nijedna količina konverzije formata ne čini ih upotrebljivim. Ti objekti se prvo ponovo grade Clang baziranim kompajlerom, koji te zavisnosti ne emituje, a konvertuju se posle

Pipeline koji nosi PDFlibPas statičke C objekte iz Delphi OMF u Lib\thirdparty\Win32 do FPC-linkable COFF u Lib\thirdparty\Win32f na Win32: OMF u COFF konverzija, normalizacioni prolaz koji prepisuje imena sekcija i simbole definicija sekcija a indeks simbola, bajtove koda i relokacije ostavlja netaknutim, i Clang rebuild OpenJPEG objekata koji zovu Delphi 64-bit helper-e
Konverzija je neophodna ali nije dovoljna: date internom linkeru Free Pascal-a COFF sa preimenovanim a nenormalizovanim sekcijama pa on odgovara internim greškama, pa prolaz posle konverzije popravlja imena i simbole ne dirajući ofsete
// Objekti za FPC metu žive u svom sopstvenom direktorijumu. Ne zamenjuju
// skup Delphi objekata, jer obe alatke grade iz istog stabla izvornog
// koda i svaka treba svoje ulaze za povezivanje
//
//   Lib\thirdparty\Win32   Delphi OMF objekti, nepromenjeni
//   Lib\thirdparty\Win32f  FPC COFF objekti, konvertovani i normalizovani
//
// Build ulazne tačke:
//   build-Win32-Lib-FPC.cmd
//   build-Win64-Lib-FPC.cmd

Kompajlerski privatni helper-i nisu portabilni, a ni njihove konvencije

Delphi runtime isporučuje asemblerske trampoline za 64-bitne celobrojne operacije na 32-bitnom x86, i prekompajlirani C objekti građeni za Delphi zovu njih. Free Pascal ima svoj aranžman, pa te reference moraju da se zadovolje drugačije, a ne da se preusmere. Detalj koji preusmeravanje čini nemogućim je calling convention: timing helper koji imaging kod koristi ima četvorobajtni argument koji čisti callee, dok 64-bitni helper za deljenje čisti šesnaest bajtova i vraća rezultat u klasičnom paru registara. Dva helper-a, dve konvencije, i trampoline pisana za jednu ćutke kvari stek drugoj

Name decoration dodaje drugu polovinu problema. Na Win32, Free Pascal automatski prefiksuje eksterne C importove donjom crtom, dok public name deklaracije izvozi doslovno, pa uvozna i izvozna strana istog mosta prate različita pravila. C runtime most koji OpenJPEG treba zato mora da izvozi tačna C imena simbola, a variadic ulazne tačke trebaju 32-bitni indirektni skok umesto direktnog. Ništa od ovoga nije egzotično kad se jednom izgovori. Sve to pada kao link greška koja navodi simbol koji niko nije napisao

Šta je teralo Win32 izvršnu datoteku da umre pre main?

64-bitna DLL na search path-u, do koje se došlo zato što se Free Pascal zlib jedinica vezuje dinamički umesto da se statički poveže. Simptom je bio trenutni izlaz sa statusnim kodom invalid-image, pre nego što je ijedna linija Pascal koda u programu proradila, što vas šalje da gledate program koji ste upravo napravili dok je kvar u tome što loader razrešuje import prema pogrešnoj arhitekturi

Lekcija je o pretpostavkama, ne o zlib-u. Jedinica imenovana po kompresionoj biblioteci ne mora nužno da je i sadrži; može biti binding koji očekuje shared biblioteku u vreme izvršavanja, a dinamička zavisnost koju niste nameravali je teret pri deploy-u čak i kad slučajno uspe da se razreši. Prelazak na čisto Pascal implementaciju toka daje obe mete statički uključenu kompresionu putanju bez ijedne eksterne zavisnosti, a to je ono što biblioteka ugrađena u tuđu aplikaciju treba da ima od samog početka

Istog instinkta drži i eksterni JBIG2 encoder backend. Na 32-bitnoj meti eksterni encoder se ne povezuje, pa zahtevi padaju na ugrađeni Pascal encoder, i test koji to proverava mora da gleda registraciono stanje trenutne mete umesto da uspešno enkodiranje uzme kao dokaz da je eksterni backend prisutan. Fallback koji radi je upravo ono što krije nedostajuću zavisnost, i to je obrazac otkaza rastvoren u dijagnostikovanju tihih stub otkaza. Rad na 64-bitnom statičkom povezivanju pokriven je u statičkom povezivanju jbig2enc pod FPC-om

Dijagnostički tok za Win32 izvršnu datoteku koja umire pre main pod Free Pascal-om: loader razrešuje importove dok unit inicijalizacija radi, zlib binding pronalazi 64-bitnu DLL na search path-u, i proces umire sa invalid-image statusom pre bilo koje Pascal naredbe, terajući PDFlibPas ka statički uključenoj čisto Pascal kompresionoj putanji
Kvar nikada nije bio program koji ste upravo napravili: jedinica imenovana zlib bila je runtime binding razrešen prema pogrešnoj arhitekturi, a fallback koji radi, poput ugrađenog JBIG2 encoder-a, krije nedostajuću zavisnost

32-bitna aritmetika nad memory stream-om

Kod koji veličine bafera manipuliše unsigned aritmetikom u širini pointera tačan je na Win64 i jednu veliku sliku udaljen od prekoračenja na Win32. Memory stream koji hrani JPEG 2000 codec raste udvostručavanjem i napreduje sabiranjem, i na 32-bitnoj meti obe operacije mogu da se omotaju na ulazima koji su veliki ali potpuno legitimni

Svako pisanje, preskakanje, seek i početna alokacija zato proverava pre nego što izračuna, a plafon kapaciteta je maksimalna signed vrednost u širini pointera, izabrana da se poklopi sa onim što block move rutina i povratne vrednosti callback-a mogu da izraze. Zahtev o ponašanju kad se zahtev odbije lako je pokvariti: odbijanje ne sme da menja poziciju toka ni njegovu dužinu. Delimična mutacija pa greška ostavlja tok u stanju o kojem pozivalac ne može ništa da zaključi, i sledeća operacija to samo uvećava

// Proveri pre nego što izračunaš. Na Win32 obe ovde omotaju na ulazima
// koje jedna velika JPEG 2000 slika legitimno proizvodi
if Needed > NativeUInt(High(NativeInt)) - FPosition then
  Exit(False);                    // odbij, ostavi poziciju i veličinu na miru

NewCapacity := FCapacity;
while (NewCapacity < FPosition + Needed) do
begin
  if NewCapacity > NativeUInt(High(NativeInt)) shr 1 then
    Exit(False);                  // udvostručavanje bi prekorčilo
  NewCapacity := NewCapacity shl 1;
end;

Dve zamke build izlaza koje nadžive sam port

Razdvajanje test i sample izvršnih datoteka po ciljnoj arhitekturi u izlazne direktorijume po meti je očigledno ispravno i odmah lomi sve što je svoje test podatke lociralo brojanjem nivoa direktorijuma naviše. Popravka je da se asset direktorijum traži naviše umesto da se pretpostavi fiksna dubina, uz jedno namerno ograničenje: signing sample prihvata certificate fallback samo iz sopstvenog projekt direktorijuma, nikada iz proizvoljnog pretka, jer sertifikat istog imena pronađen visoko u stablu je bezbednosno iznenađenje, a ne pogodnost

Druga zamka nadživi svaki port i vredi je poneti u svaki FPC projekat. Posle nadogradnje kompajlera, to da kompajler odbaci zastarele PPU datoteke nije dovoljno, jer linker i dalje daje prednost zaostalim objektnim datotekama u unit search path-u čak i kad je PPU koji je učitao došao iz ispravnog direktorijuma, a dodavanje eksplicitne putanje za objektni izlaz tu preferenciju ne poništava. Jedini pouzdan odgovor je svež privremeni unit direktorijum po svakom build krugu. Sve manje od toga proizvodi binarnu datoteku povezanu iz dve verzije kompajlera, koja otkazuje na načine koji liče na bagove u izvornom kodu

Platformski uslovna kompajliranja su poslednji deo slagalice, i izbor prave ose znači više nego što izgleda. Pravo pitanje je obično da li je kod Windows-specifičan, a ne da li je prisutna neka konkretna widget biblioteka, kao što je pokazao rad na metafile konverziji u EMF vektorskom importu i platformskim uslovima: promena tog guard-a iz uslova o control biblioteci u platformski uslov pretvorila je navodni rewrite u izmenu jedne direktive. Podrška Free Pascal-a i Lazarus-a za obe Windows mete stiže uz PDFlibPas Delphi PDF biblioteku, građenu iz istih izvora kao Delphi i C++Builder paketi