Tehnični članak

Povezovanje objektov OMF v COFF za FPC Win32 v PDFlibPas

PDFlibPas se pod Free Pascal gradi za 32-bitni Windows, a težaven del nikoli ni bil Pascal. To so bile objektne datoteke: objekti AES in OpenJPEG, ki jih Delphi gradnja poveže, so OMF, notranji povezovalnik Free Pascal pa zahteva COFF, pretvorba med obema pa ustvari imena sekcij in simbole definicij sekcij, zaradi katerih se povezovalnik sesuje z notranjimi napakami namesto z diagnostiko

Vsak, ki je že povezoval C objekte v Pascal knjižnico, pozna to ozemlje. Win64 je v primerjavi s tem precej ugoden: en sam format objektov, ena klicna konvencija in brez dekoracije imen. Win32 pa ohrani vsako plast zgodovine, ki jo je platforma nabrala, in knjižnica, ki statično povezuje C kodo tretjih oseb, se z vsemi sreča naenkrat

Iz imenika prevajalnika cilja ne razberete

Začnite pri vstopni točki gradnje, ker vas napaka tu stane ure, preden se sploh kaj objektnih datotek zgodi. Ime namestitvenega imenika Free Pascal pove, kjer stanuje glavni prevajalnik, ne pa kaj ta izdeluje. 32-bitni gostiteljski prevajalnik lahko prikliče prečni prevajalnik (cross-compiler), ki stoji ob njem, in izda 64-bitno kodo, če podate pravilna stikala cilja, zato je sklepanje o cilju iz poti ugibanje, ki srečno deluje, dokler nekdo ne preuredi svojega toolchaina

Zanesljiva pot je vprašati prevajalnik. Dejanski ciljni procesor in operacijski sistem povprašajte skozi lastna informativna stikala prevajalnika, sprejmite pa oba pogosta razporeda namestitve — ravni binarni imenik in tistega ugnezdenega po verzijah —, ker različni namestitveni programi in upravljalniki toolchainov ustvarjajo različne oblike. Gradbeni skript, ki trdo kodira kateri koli od teh razporedov, deluje natanko na enem računalniku

Zakaj pretvorjena objektna datoteka pokvari notranji povezovalnik?

Ker pretvorba ohrani OMF konvencijo poimenovanja sekcij in izmisli simbole definicij sekcij, ki se ne ujemajo s tem, kar pričakuje COFF povezovalnik. Pretvorba OMF objektov v COFF je nujna, a ne zadostna: nastale datoteke nosijo klasična imena sekcij _TEXT, _DATA in _BSS ter iz njih izpeljana imena simbolov definicij sekcij, njihovo požiranje v notranji povezovalnik Free Pascal pa ne proizvede sporočila o poimenovanju sekcij, temveč notranje napake prevajalnika

Notranja napaka je najslabši način odpovedi pri težavi z gradnjo, ker o vnosu ne pove nič. Rešitev je normalizacijski prehod čez COFF datoteko po pretvorbi: imena sekcij prepišite v pričakovano obliko in pripadajoče simbole definicij sekcij prepišite tako, da se ujemajo, indeks simbolov, bajte kode in relokacije pa pustite pri miru. Zadnja omejitev je vsa težava. Prepis, ki preštevilči simbole ali premakne odmike, proizvede objekt, ki se poveže in potem sesuje

Za enega od obeh naborov objektov obstaja še predhodni korak. Objekti OpenJPEG, zgrajeni s klasičnim 32-bitnim C++ prevajalnikom, so odvisni od zasebnih Delphi rutin za 64-bitna cela števila, ki jih Free Pascal ne ponuja, zato jih nobena količina pretvorbe formata ne naredi uporabnimi. Te najprej znova zgradite s prevajalnikom na osnovi Clang, ki teh odvisnosti ne izdaja, in jih šele nato pretvorite

Cevovod, ki prenaša statične C objekte PDFlibPas iz Delphi OMF v Lib\thirdparty\Win32 do FPC-povezljivega COFF v Lib\thirdparty\Win32f na Win32: pretvorba OMF v COFF, normalizacijski prehod, ki prepiše imena sekcij in simbole definicij sekcij, medtem ko indeks simbolov, bajte kode in relokacije pusti pri miru, ter ponovna Clang gradnja za objekte OpenJPEG, ki kličejo Delphi 64-bitne pomočnike
Pretvorba je nujna, a ne zadostna: preimenovani, a nenormalizirani COFF, ki ga požrete v notranji povezovalnik Free Pascal, odgovori z notranjimi napakami, zato prehod po pretvorbi popravi imena in simbole, ne da bi se dotaknil odmikov
// Objekti za cilj FPC živijo v svojem imeniku. Ne nadomeščajo
// nabora Delphi objektov, saj oba toolchaina gradita iz istega
// izvornega drevesa in vsak potrebuje svoje povezovalne vhode
//
//   Lib\thirdparty\Win32   Delphi OMF objekti, nespremenjeni
//   Lib\thirdparty\Win32f  FPC COFF objekti, pretvorjeni in normalizirani
//
// Vstopne točke gradnje:
//   build-Win32-Lib-FPC.cmd
//   build-Win64-Lib-FPC.cmd

Pomočniki, zasebni za prevajalnik, niso prenosljivi, niti njihove konvencije

Delphi runtime zagotavlja zbirniške trampoline za 64-bitne celoštevilske operacije na 32-bitnem x86, in vanje kličejo vnaprej prevedeni C objekti, zgrajeni za Delphi. Free Pascal ima svojo ureditev, zato je treba te reference zadovoljiti drugače in jih ne preusmerjati. Podrobnost, ki preusmerjanje onemogoči, je klicna konvencija: pomočnik za merjenje časa, ki ga uporablja slikovna koda, ima štiribajtni argument počiščen s strani klicanca, medtem ko pomočnik za 64-bitno deljenje počisti šestnajst bajtov in vrne rezultat v klasičnem paru registrov. Dva pomočnika, dve konvenciji, in trampoline, napisan za enega, drugemu tiho pokvari sklad

Dekoracija imen doda drugo polovico težave. Na Win32 Free Pascal samodejno doda podčrtaj pred zunanje C uvoze, medtem ko deklaracije public name izvozi dobesedno, tako da uvozna in izvozna strani istega mostu sledita različnim pravilom. C most, ki ga potrebuje OpenJPEG, zato mora izvažati točno določena imena C simbolov, variadične vstopne točke pa potrebujejo 32-bitni posredni skok namesto neposrednega. Nič od tega ni eksotika, ko je enkrat povedano. Vse to odpove kot napaka povezovanja, ki poimenuje simbol, ki ga ni napisal nihče

Kaj je povzročilo, da je Win32 izvedljiva datoteka umrla pred main?

64-bitna DLL na iskalni poti, do katere se pride, ker se enota zlib Free Pascal veže dinamično namesto da se statično poveže. Simptom je bil takojšen izhod s statusno kodo neveljavne slike, še preden se je zagnala katera koli Pascal koda v programu, kar vas pošlje gledat program, ki ste ga pravkar zgradili, ko je napaka v nalagalniku, ki razrešuje uvoz proti napačni arhitekturi

Pouk je v predpostavkah, ne v zlib. Enota, poimenovana po stiskalniški knjižnici, je ne nujno vsebuje; je lahko vez, ki ob zagonu pričakuje deljeno knjižnico, in dinamična odvisnost, ki je niste nameravali, je breme za namestitev, tudi ko srečno razreši. Preklop na čisto Pascal implementacijo toka da obema ciljema statično vključeno pot stiskanja brez kakršne koli zunanje odvisnosti, kar bi morala imeti knjižnica, vgrajena v tujo aplikacijo, že od vsega začetka

Isti instinkt velja za zunanji zaledni kodirnik JBIG2. Na 32-bitnem cilju zunanji kodirnik ni povezan, zato se zahteve vrnejo k vgrajenemu Pascal kodirniku, test, ki to preverja, pa mora pogledati registracijsko stanje trenutnega cilja, namesto da bi uspešno kodiranje jemal kot dokaz, da je zunanji zaledni del prisoten. Delujoč fallback je natanko tisto, kar skrije manjkajočo odvisnost — to je vzorec odpovedi, obravnavan v diagnosticiranju tihih odpovedi stubov. Delu o statičnem povezovanju na 64 bitih pa je posvečen članek statično povezovanje jbig2enc pod FPC

Diagnostični tok za Win32 izvedljivo datoteko, ki pod Free Pascal umre pred main: nalagalnik razrešuje uvoze, medtem ko teče inicializacija enot, vez zlib najde 64-bitno DLL na iskalni poti in proces umre s statusom neveljavne slike, še pred katero koli Pascal izjavo, kar PDFlibPas potisne h statično vključeni čisti Pascal poti stiskanja
Napaka nikoli ni bila program, ki ste ga pravkar zgradili: enota, poimenovana zlib, je bila vez na zagon, razrešena proti napačni arhitekturi, delujoč fallback — na primer vgrajeni kodirnik JBIG2 — pa skrije manjkajočo odvisnost

32-bitna aritmetika na pomnilniškem toku

Koda, ki upravlja z velikostmi medpomnilnika z nepredznačeno aritmetiko širine kazalca, je na Win64 pravilna, na Win32 pa ena velika slika stran od preliva. Tok v pomnilniku, ki napaja kodek JPEG 2000, raste s podvajanjem in napreduje s seštevanjem, na 32-bitnem cilju pa se lahko obe operaciji prevrneta pri vnosih, ki so veliki, a povsem legitimni

Vsako pisanje, preskok, iskanje in začetna dodelitev zato najprej preveri, nato računa, strop zmogljivosti pa je največja predznačena vrednost širine kazalca, izbrana tako, da se ujema s tem, kar izrazita rutina za premik blokov in povratne vrednosti klicev. Vedenje ob zavrnitvi zahteve je lahko napačno: zavrnitev ne sme spremeniti položaja toka niti njegove dolžine. Delna sprememba, ki ji sledi napaka, pusti tok v stanju, o katerem klicatelj ne more sklepati, naslednja operacija pa to še poglobi

// Preveri, preden računaš. Na Win32 se obe prevrneta pri vnosih, ki jih
// velika slika JPEG 2000 povsem legitimno proizvede
if Needed > NativeUInt(High(NativeInt)) - FPosition then
  Exit(False);                    // zavrni, pusti položaj in velikost pri miru

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

Dve pasti izhodov gradnje, ki preživita preselitev

Ločevanje testnih in vzorčnih izvedljivih datotek po ciljni arhitekturi v izhodne imenike na cilj je očitno prav in takoj pokvari vse, kar je svoje testne podatke našlo s štetjem imeniških ravni navzgor. Rešitev je iskanje imenika sredstev navzgor namesto predpostavljene fiksne globine, z eno namerno omejitvijo: vzorec za podpisovanje sprejme rezervni certifikat le iz svojega projektnega imenika, nikoli iz poljubnega prednika, ker je istoimenski certifikat, najden višje v drevesu, varnostno presenečenje in ne priročnost

Druga past preživi vsako preselitev in jo vredi odnesti v vsak FPC projekt. Po nadgradnji prevajalnika ni dovolj, da prevajalnik zavrne zastarele PPU datoteke, ker povezovalnik še vedno raje vzame preostale objektne datoteke v iskalni poti enot, tudi ko je PPU, ki ga je naložil, prišel iz pravega imenika, in dodajanje izrecne poti izhoda objektov te preference ne preglasi. Edini zanesljiv odgovor je svež začasni imenik enot za vsak krog gradnje. Karkoli manj proizvede binarko, povezano iz dveh verzij prevajalnika, ki odpove na načine, ki izgledajo kot hrošči v izvorni kodi

Platformski pogoji sta zadnji kos, izbira prave osi pa je pomembnejša, kot se zdi. Pravo vprašanje je običajno, ali je koda specifična za Windows, ne pa ali je določena knjižnica gradnikov prisotna, kot je pokazalo delo o pretvorbi metafil v uvozu EMF vektorjev in platformskih pogojih: preklop tega stražarja iz pogoja na knjižnico kontrol v pogoj na platformo je domnevno prepisovanje spremenil v spremembo ene direktive. Podpora Free Pascal in Lazarus za oba Windows cilja je del PDFlibPas Delphi PDF library, zgrajene iz istih virov kot paketi Delphi in C++Builder