Teknisk artikkel

FPC på Win32: OMF- til COFF-objektkobling i PDFlibPas

PDFlibPas bygges med Free Pascal for 32-bits Windows, og det vanskelige var aldri Pascal. Det var objektfilene: AES- og OpenJPEG-objektene som Delphi-bygget kobler inn, er OMF, den interne linkeren i Free Pascal krever COFF, og konvertering mellom de to produserer seksjonsnavn og seksjonsdefinisjonssymboler som får linkeren til å feile med interne feil i stedet for diagnosemeldinger

Alle som har koblet C-objekter inn i et Pascal-bibliotek, kjenner dette terrenget. Win64 er forholdsvis sivilisert, med ett objektformat, én kallekonvensjon og ingen navnedekorasjon. Win32 bevarer alle historiske lag plattformen har samlet opp, og et bibliotek som statisk kobler tredjeparts C-kode, møter alle sammen på én gang

Kompilatorkatalogen forteller deg ikke hva målet er

Start med byggets inngangspunkt, for å ta feil her koster timer før noen objektfil er involvert. Et katalognavn fra en Free Pascal-installasjon identifiserer hvor hovedkompilatoren bor, ikke hva den produserer. En 32-bits vertskompilator kan kalle en krysskompilator som ligger ved siden av, og generere 64-bits kode når du passerer de riktige målbryterne, så å utlede målet fra en sti er gjetting som tilfeldigvis fungerer frem til noen omorganiserer verktøykjeden sin

Den pålitelige tilnærmingen er å spørre kompilatoren. Spør etter faktisk målprosessor og operativsystem gjennom kompilatorens egne informasjonsbrytere, og godta begge vanlige installasjonsoppsettene: den flate binærkatalogen og den som er nestet under en versjonskatalog, fordi ulike installasjonsprogrammer og verktøykjedeadministratorer produserer ulike former. Et byggeskript som hardkoder ett av oppsettene, fungerer på nøyaktig én maskin

Hvorfor ødelegger en konvertert objektfil den interne linkeren?

Fordi konverteringen bevarer OMF-seksjonsnavnekonvensjonen og syntetiserer seksjonsdefinisjonssymboler som ikke matcher det COFF-linkeren forventer. Å konvertere OMF-objektene til COFF er nødvendig, men ikke tilstrekkelig: de resulterende filene bærer de klassiske seksjonsnavnene _TEXT, _DATA og _BSS, pluss seksjonsdefinisjonssymbolnavn avledet fra dem, og å mate det inn i Free Pascals interne linker gir interne kompilatorfeil i stedet for en melding om seksjonsnavngivning

En intern feil er den verste feilmoden for et byggproblem, fordi den sier ingenting om hva som var galt med inndataene. Fiksen er en normaliseringsrunde over COFF-filen etter konverteringen: omskriv seksjonsnavnene til forventet form og omskriv de tilhørende seksjonsdefinisjonssymbolene så de matcher, mens symbolindeksen, kodebytene og relokeringene står urørt. Den siste begrensningen er hele vanskeligheten. En omskriving som nummererer symboler på nytt eller flytter forskyvninger, gir et objekt som kobler fint og deretter krasjer

Ett av de to objektsettene trenger et forberedende trinn. OpenJPEG-objektene bygget med den klassiske 32-bits C++-kompilatoren avhenger av Delphis private hjelperutiner for 64-bits heltall, noe Free Pascal ikke tilbyr, så ingen mengde formatkonvertering gjør dem brukbare. De bygges derfor først om med den Clang-baserte kompilatoren, som ikke genererer disse avhengighetene, og konverteres etterpå

Pipeline som bærer PDFlibPas statiske C-objekter fra Delphi OMF i Lib\thirdparty\Win32 til FPC-koblbart COFF i Lib\thirdparty\Win32f på Win32: OMF-til-COFF-konvertering, en normaliseringsrunde som omskriver seksjonsnavn og seksjonsdefinisjonssymboler mens symbolindeks, kodebyte og relokeringer står urørt, og Clang-gjenoppbyggingen for OpenJPEG-objekter som kaller Delphis 64-bits hjelpere
Konvertering er nødvendig, men ikke tilstrekkelig: mate den omdøpte men ikke normaliserte COFF-en inn i Free Pascals interne linker gir interne feil tilbake, så en runde etter konverteringen fikser navn og symboler uten å røre forskyvninger
// Objektene for FPC-målet ligger i sin egen katalog. De erstatter ikke
// Delphi-objektsettet, fordi begge verktøykjedene bygger fra det samme
// kildetreet og hver trenger sine egne koblingsinndata
//
//   Lib\thirdparty\Win32   Delphi OMF-objekter, uendret
//   Lib\thirdparty\Win32f  FPC COFF-objekter, konvertert og normalisert
//
// Bygg-inngangspunkter:
//   build-Win32-Lib-FPC.cmd
//   build-Win64-Lib-FPC.cmd

Kompilatorprivate hjelpere er ikke portable, og det er heller ikke konvensjonene deres

Delphi-kjøretiden har trampoliner i assembler for 64-bits heltallsoperasjoner på 32-bits x86, og forhåndskompilerte C-objekter bygget for Delphi kaller inn i dem. Free Pascal har sin egen ordning, så disse referansene må tilfredsstilles annerledes i stedet for å videresendes. Detaljen som gjør videresending umulig, er kallekonvensjonen: tidsmålingshjelperen som bildekoden bruker, får sitt firebyte-argument ryddet opp av den kalte rutinen, mens 64-bits divisjonshjelperen rydder seksten byte og returnerer resultatet i det klassiske registerparet. To hjelpere, to konvensjoner, og en trampoline skrevet for den ene korrumperer stabelen i stillhet for den andre

Navnedekorasjon legger til den andre halvdelen av problemet. På Win32 setter Free Pascal understrek foran eksterne C-importer automatisk, mens public name-deklarasjoner eksporteres ordrett, så importsiden og eksportsiden av samme bro følger ulike regler. C-kjøretidsbroen som OpenJPEG trenger, må derfor eksportere de nøyaktige C-symbolnavnene, og de variadiske inngangspunktene trenger et indirekte 32-bits hopp i stedet for et direkte. Ingenting av dette er eksotisk når det først er sagt. Alt av det feiler som en koblingsfeil som nevner et symbol ingen har skrevet

Hva fikk en Win32-kjørbar fil til å dø før main?

En 64-bits DLL på søkestien, nådd fordi Free Pascal-enheten zlib binder dynamisk i stedet for å koble statisk. Symptomet var umiddelbar avslutning med statuskoden for ugyldig image, før noen Pascal-kode i programmet hadde kjørt, noe som sender deg til å se på programmet du nettopp bygde, mens feilen ligger i at loaderen løser en import mot feil arkitektur

Lærdommen handler om antakelser, ikke om zlib. En enhet oppkalt etter et kompresjonsbibliotek inneholder ikke nødvendigvis ett; det kan være en binding som forventer et delt bibliotek ved kjøretid, og en dynamisk avhengighet du ikke hadde tenkt deg, er et utrullingsproblem selv når den tilfeldigvis løser seg. Å bytte til den rene Pascal-implementasjonen av strømmen gir begge mål en statisk inkludert kompresjonsvei helt uten eksterne avhengigheter, som er det et bibliotek som bygges inn i noen andres applikasjon burde hatt fra først av

Det samme instinktet gjelder for den eksterne JBIG2-encoder-backenden. På 32-bits-målet er den eksterne encoderen ikke koblet inn, så forespørsler faller tilbake til den innebygde Pascal-encoderen, og testen som verifiserer dette må sjekke registreringstilstanden til gjeldende mål i stedet for å behandle en vellykket koding som bevis på at ekstern backend er til stede. En fallback som fungerer, er nøyaktig det som skjuler en manglende avhengighet, som er feilmønsteret undersøkt i diagnose av stille stub-feiler. Arbeidet med 64-bits statisk kobling er dekket i statisk kobling av jbig2enc under FPC

Diagnostikkflyt for en Win32-kjørbar fil som avslutter før main under Free Pascal: loaderen løser importer mens enhetsinitialisering kjører, zlib-bindingen finner en 64-bits DLL på søkestien, og prosessen dør med ugyldig image-status før noen Pascal-setning, noe som presser PDFlibPas mot en statisk inkludert ren Pascal-kompresjonsvei
Feilen var aldri programmet som nettopp var bygget: en enhet kalt zlib var en kjøretidsbinding løst mot feil arkitektur, og en fallback som fungerer, som den innebygde JBIG2-encoderen, skjuler en manglende avhengighet

32-bits aritmetikk på en minnestrøm

Kode som manipulerer bufferstørrelser med usignert aritmetikk i pekerbredde, er korrekt på Win64 og ett stort bilde unna overflow på Win32. Minnestrømmen som fôrer JPEG 2000-kodeksen vokser ved dobling og avanserer ved addisjon, og på et 32-bits mål kan begge operasjonene wrappe på inndata som er store, men helt legitime

Hver skriving, hopp, søking og innledende allokering sjekker derfor før den regner, og taket for kapasitet er den maksimale signerte verdien i pekerbredde, valgt for å matche det blokkflyttingsrutinen og callback-returverdiene kan uttrykke. Atferdskravet når en forespørsel avslås, er lett å få feil: et avslag må ikke endre strømposisjonen eller lengden. En delvis mutasjon etterfulgt av en feil etterlater strømmen i en tilstand kalleren ikke kan resonnere om, og neste operasjon forsterker det

// Sjekk før du regner. På Win32 wrapper begge disse på inndata et
// stort JPEG 2000-bilde produserer helt legitimt
if Needed > NativeUInt(High(NativeInt)) - FPosition then
  Exit(False);                    // avslå, la posisjon og størrelse være

NewCapacity := FCapacity;
while (NewCapacity < FPosition + Needed) do
begin
  if NewCapacity > NativeUInt(High(NativeInt)) shr 1 then
    Exit(False);                  // dobling ville gitt overflow
  NewCapacity := NewCapacity shl 1;
end;

To feller i byggresultatet som overlever porteringen

Å skille test- og eksempelkjørbare filer etter målarkitektur i per-mål-output-kataloger er åpenbart riktig, og knuser umiddelbart alt som fant testdataene sine ved å telle katalognivåer oppover. Fiksen er å søke oppover etter ressurskatalogen i stedet for å anta en fast dybde, med én bevisst begrensning: signeringseksemplet godtar en sertifikat-fallback bare fra sin egen prosjektkatalog, aldri fra en vilkårlig forgjenger, fordi et sertifikat med samme navn lenger oppe i treet er en sikkerhetsoverraskelse snarere enn en bekvemmelighet

Den andre fellen overlever hver portering og er verdt å ta med til ethvert FPC-prosjekt. Etter en kompilatoroppgradering er det ikke nok å få kompilatoren til å avvise foreldede PPU-filer, fordi linkeren fortsatt foretrekker gjenværende objektfiler i enhetssøkestien selv når PPU-en den lastet kom fra riktig katalog, og å legge til en eksplisitt objekt-output-sti overstyrer ikke den preferansen. Det eneste pålitelige svaret er en fersk midlertidig enhetskatalog per byggerunde. Noe mindre gir en binær koblet fra to kompilatorversjoner, som feiler på måter som ligner kildekodefeil

Plattformbetingelser er den siste brikken, og valget av riktig akse betyr mer enn det ser ut som. Det riktige spørsmålet er vanligvis om koden er Windows-spesifikk, ikke om et bestemt widget-bibliotek finnes, som metafilkonverteringsarbeidet i EMF-vektorimport og plattformbetingelser viste: å bytte den vakten fra en kontrollbibliotek-betingelse til en plattformbetingelse gjorde en angitt omskriving om til en endring av ett direktiv. Free Pascal- og Lazarus-støtte for begge Windows-målene følger med PDFlibPas Delphi PDF-bibliotek, bygget fra de samme kildene som Delphi- og C++Builder-pakkene