PDFlibPas bygges under Free Pascal til 32-bit Windows, og den svære del var aldrig Pascal. Det var objektfilerne: AES- og OpenJPEG-objekterne, som Delphi-builden linker, er OMF, Free Pascals interne linker kræver COFF, og konvertering mellem de to producerer sektionsnavne og sektionsdefinitionssymboler, der får linkeren til at fejle med interne fejl i stedet for ordentlige fejlmeldinger
Alle, der har linket C-objekter ind i et Pascal-bibliotek, kender dette territorium. Win64 er forholdsvis civiliseret med ét objektformat, én calling convention og ingen name decoration. Win32 bevarer alle de historielag, platformen har akkumuleret, og et bibliotek, der statisk linker tredjeparts-C-kode, møder dem alle på én gang
Compiler-mappen fortæller dig ikke, hvad target er
Start med build-indgangspunktet, for at tage fejl her koster timer, før nogen objektfil er involveret. Navnet på en Free Pascal-installationsmappe identificerer, hvor selve compileren bor, ikke hvad den producerer. En 32-bit host-compiler kan kalde en cross-compiler, der ligger ved siden af, og generere 64-bit kode, når du giver de rigtige target-switches, så at udlede target'en fra en sti er gætværk, der tilfældigvis virker, indtil nogen omorganiserer deres toolchain
Den pålidelige tilgang er at spørge compileren. Forespørg den faktiske target-processor og det faktiske styresystem gennem compilerens egne informationsswitches, og acceptér begge gængse installationslayouts, den flade binærmappe og den versionsindlejrede, for forskellige installers og toolchain-managere producerer forskellige former. Et build-script, der hardkoder ét af layoutsne, virker på præcis én maskine
Hvorfor bryder en konverteret objektfil den interne linker?
Fordi konverteringen bevarer OMF-konventionen for sektionsnavne og syntetiserer sektionsdefinitionssymboler, der ikke matcher, hvad COFF-linkeren forventer. At konvertere OMF-objekterne til COFF er nødvendigt, men ikke tilstrækkeligt: de resulterende filer bærer de klassiske _TEXT-, _DATA- og _BSS-sektionsnavne plus sektionsdefinitionssymbolnavne afledt af dem, og giver man det til Free Pascals interne linker, får man interne compilerfejl frem for en melding om sektionsnavngivning
En intern fejl er den værste fejlmåde for et build-problem, for den siger intet om, hvad der var galt med inputtet. Rettelsen er en normaliseringsomgang over COFF-filen efter konverteringen: omskriv sektionsnavnene til den forventede form, og omskriv de tilsvarende sektionsdefinitionssymboler, så de matcher, mens symbolindekset, kodebytene og relokeringerne holdes urørte. Sidstnævnte begrænsning er hele sværhedsgraden. En omskrivning, der omnummererer symboler eller flytter offsets, producerer et objekt, der linker og derefter crasher
Der er et forberedende trin for det ene af de to objectsæt. OpenJPEG-objekterne bygget med den klassiske 32-bit C++-compiler afhænger af Delphis private 64-bit integer-hjælperutiner, som Free Pascal ikke leverer, så ingen mængde formatkonvertering gør dem brugbare. De genbygges først med Clang-compileren, som ikke genererer de afhængigheder, og konverteres bagefter
// Objekterne til FPC-target ligger i deres egen mappe. De erstatter ikke
// Delphi-objectsættet, for begge toolchains bygger fra det samme
// source tree, og hver af dem har brug for sine egne link-inputs
//
// Lib\thirdparty\Win32 Delphi-OMF-objekter, uændrede
// Lib\thirdparty\Win32f FPC-COFF-objekter, konverterede og normaliserede
//
// Build-indgangspunkter:
// build-Win32-Lib-FPC.cmd
// build-Win64-Lib-FPC.cmd
Compiler-private hjælpere er ikke portable, og deres konventioner er det heller ikke
Delphi-runtime'en leverer assembly-trampoliner til 64-bit integer-operationer på 32-bit x86, og forudbyggede C-objekter bygget til Delphi kalder dem. Free Pascal har sin egen ordning, så de referencer skal tilfredsstilles anderledes i stedet for at blive viderestillet. Detaljen, der gør viderestilling umulig, er calling convention: timing-hjælperen, som imaging-koden bruger, får sit fire-byte-argument ryddet af callee, mens 64-bit divisionshjælperen rydder seksten byte og returnerer sit resultat i det klassiske registerpar. To hjælpere, to konventioner, og en trampoline skrevet til den ene korrumperer lydløst stakken for den anden
Name decoration tilføjer den anden halvdel af problemet. På Win32 sætter Free Pascal automatisk understreg foran eksterne C-importer, mens public name-deklarationer eksporteres ordret, så importsiden og eksportsiden af samme bro følger forskellige regler. Den C-runtime-bro, som OpenJPEG har brug for, skal derfor eksportere de præcise C-symbolnavne, og de variadiske entry points skal bruge et 32-bit indirekte jump frem for et direkte. Intet af det er eksotisk, når det først er sagt. Det hele fejler som en link-fejl, der nævner et symbol, ingen har skrevet
Hvad fik en Win32-executable til at dø, før main?
En 64-bit DLL på search path, nået fordi Free Pascal-uniten zlib binder dynamisk i stedet for at linke statisk. Symptomet var en øjeblikkelig exit med invalid-image-statuskoden, før nogen Pascal-kode i programmet var kørt, hvilket sender dig hen til at kigge på programmet, du lige har bygget, mens fejlen ligger i loaderen, der resolver en import mod den forkerte arkitektur
Lektionen handler om antagelser snarere end om zlib. En unit opkaldt efter et komprimeringsbibliotek indeholder ikke nødvendigvis et; det kan være en binding, der forventer et shared library ved runtime, og en dynamisk afhængighed, du ikke havde tænkt dig, er en deployment-liability, selv når den tilfældigvis resolver. Skifter man til den rene Pascal-streamimplementering, får begge targets en statisk inkluderet komprimeringssti helt uden eksterne afhængigheder, og det er, hvad et bibliotek, der indlejres i en andens applikation, burde have i første omgang
Samme instinkt gælder den eksterne JBIG2-encoder-backend. På 32-bit-target er den eksterne encoder ikke linket, så forespørgsler falder tilbage på den indbyggede Pascal-encoder, og testen, der verificerer det, er nødt til at tjekke registreringstilstanden for det aktuelle target i stedet for at tage en vellykket encode som bevis på, at den eksterne backend er til stede. En fallback, der virker, er præcis det, der skjuler en manglende afhængighed, hvilket er fejlmønsteret undersøgt i diagnostik af tavse stub-fejl. Arbejdet med 64-bit statisk linking er dækket i statisk linking af jbig2enc under FPC
32-bit aritmetik på en memory stream
Kode, der manipulerer bufferstørrelser med pointer-bredde unsigned-aritmetik, er korrekt på Win64 og ét stort billede fra overflow på Win32. In-memory-streamen, der fodrer JPEG 2000-codec'et, vokser ved fordobling og avancerer ved addition, og på et 32-bit target kan begge operationer wrappe på input, der er store, men helt legitime
Enhver write, skip, seek og initial allokering tjekker derfor, før den regner, og loftet for kapacitet er den maksimale signede pointer-bredde-værdi, valgt til at matche, hvad block move-rutinen og callback-returværdierne kan udtrykke. Kravet om adfærd, når en forespørgsel afvises, er let at tage fejl af: en afvisning må ikke ændre stream-positionen eller dens længde. En delvis mutation efterfulgt af en fejl efterlader streamen i en tilstand, som kalderen ikke kan ræsonnere om, og den næste operation forstærker det
// Tjek før du regner. På Win32 wrapper begge dele på input, som et
// stort JPEG 2000-billede legitimit producerer
if Needed > NativeUInt(High(NativeInt)) - FPosition then
Exit(False); // afvis, lad position og størrelse være
NewCapacity := FCapacity;
while (NewCapacity < FPosition + Needed) do
begin
if NewCapacity > NativeUInt(High(NativeInt)) shr 1 then
Exit(False); // fordobling ville give overflow
NewCapacity := NewCapacity shl 1;
end;
To build-output-fælder, der overlever porteringen
Adskillelse af test- og sample-executables efter target-arkitektur i output-mapper pr. target er åbenlyst rigtigt og bryder øjeblikkeligt alt, hvad der fandt sine testdata ved at tælle mappeleveller opad. Rettelsen er at søge opad efter asset-mappen i stedet for at antage en fast dybde, med én bevidst begrænsning: signing-samplet accepterer et certifikat som fallback kun fra sin egen projektmappe, aldrig fra en vilkårlig forfader, for et enslydende certifikat fundet længere oppe i træet er en sikkerhedsmæssig overraskelse snarere end en bekvemmelighed
Den anden fælde overlever enhver portering og er værd at tage med til ethvert FPC-projekt. Efter en compiler-opgradering er det ikke nok at få compileren til at afvise forældede PPU-filer, for linkeren foretrækker stadig efterladte objektfiler i unit search path, selv når den PPU, den indlæste, kom fra den korrekte mappe, og at tilføje en eksplicit object output-sti tilsidesætter ikke den præference. Det eneste pålidelige svar er en frisk midlertidig unit-mappe pr. build-runde. Mindre end det giver en binary linket fra to compilerversioner, som fejler på måder, der ligner source-bugs
Platform conditionals er den sidste brik, og valget af den rigtige akse betyder mere, end det ser ud. Det rigtige spørgsmål er som regel, om koden er Windows-specifik, snarere end om et bestemt widget-bibliotek er til stede, som metafile-konverteringsarbejdet i EMF-vektorimport og platform conditionals viste: at skifte den guard fra en control-library-betingelse til en platformsbetingelse forvandlede en formodet rewrite til en ændring af ét enkelt direktiv. Free Pascal- og Lazarus-understøttelse af begge Windows-targets følger med PDFlibPas Delphi PDF-biblioteket, bygget fra de samme sources som Delphi- og C++Builder-pakkerne