PDFlibPas kääntyy Free Pascalilla 32-bittiselle Windowsille, ja vaikea osa ei koskaan ollut Pascali vaan objektitiedostot: Delphi-käännöksen linkittämät AES- ja OpenJPEG-objektit ovat OMF-muodossa, Free Pascalin sisäinen linkkeri taas edellyttää COFF-muotoa, ja muunnos näiden välillä tuottaa osionimiä ja osion määrittelysymboleja, jotka saavat linkkerin kaatumaan sisäisiin virheisiin oikean diagnostiikan sijaan
Jokainen, joka on linkittänyt C-objekteja Pascal-kirjastoon, tuntee tämän maaston. Win64 on suhteellisen sivistynyt: yksi objektiformaatti, yksi kutsukonventio eikä nimenkoristelua lainkaan. Win32 säästää jokaisen kerroksen sitä historiaa, jota alusta on ehtinyt kerryttää, ja kolmannen osapuolen C-koodia staattisesti linkittävä kirjasto kohtaa ne kaikki yhtä aikaa
Kääntäjän hakemisto ei kerro kohdealustaa
Aloita käännöksen sisääntulopisteestä, koska tässä tehty virhe haaskaa tunteja ennen kuin mikään objektitiedosto on edes mukana. Free Pascal -asennushakemiston nimi kertoo, missä pääkääntäjä asuu, ei sitä, mitä se tuottaa. 32-bittinen isäntäkääntäjä voi kutsua vieressään istuvaa ristikkäiskääntäjää ja tuottaa 64-bittistä koodia, kun annat oikeat kohdekytkimet, joten kohteen päätteleminen polusta on arvaamista, joka sattuu toimimaan siihen asti, kunnes joku järjestää työkaluketjunsa uusiksi
Luotettava tapa on kysyä kääntäjältä itseltään. Kysy oikea kohdeprosessori ja käyttöjärjestelmä kääntäjän omilla tietokytkimillä ja hyväksy molemmat tavalliset asennusasettelut, litteä binäärihakemisto ja versioihin sisäkkäin aseteltu, koska eri asennusohjelmat ja työkaluketjujen hallintatyökalut tuottavat eri muotoja. Asettelun kovakoodaava buildiskripti toimii täsmälleen yhdellä koneella
Miksi muunnettu objektitiedosto rikkee sisäisen linkkerin?
Koska muunnos säilyttää OMF:n osionimikäytännön ja syntetisoi osion määrittelysymboleja, jotka eivät vastaa sitä, mitä COFF-linkkeri odottaa. OMF-objektien muuntaminen COFF-muotoon on tarpeen mutta ei riitä: syntyneet tiedostot kantavat klassisia _TEXT-, _DATA- ja _BSS-osionimiä sekä niistä johdettuja osion määrittelysymbolien nimiä, ja sen syöttäminen Free Pascalin sisäiselle linkkerille tuottaa kääntäjän sisäisiä virheitä eikä viestiä osionimistä
Sisäinen virhe on pahin vikaantumismuoto käännösongelmalle, koska se ei kerro mitään siitä, mikä syötteessä oli vialla. Korjaus on muunnoksen jälkeinen normalisointikierros COFF-tiedoston yli: kirjoita osionimet odotettuun muotoon ja kirjoita vastaavat osion määrittelysymbolit täsmäämään jättäen symboli-indeksin, kooditavut ja relokaatiot koskemattomiksi. Tuo viimeinen rajoite on koko vaikeus. Uudelleenkirjoitus, joka numerointi symbolit uudelleen tai siirtää siirtymiä, tuottaa objektin, joka linkittää ja sitten kaatuu
Toisella kahdesta objektijoukosta on lisäksi esivaihe. Klassisen 32-bittisen C++-kääntäjän rakentamat OpenJPEG-objektit riippuvat Delphin yksityisistä 64-bittisistä kokolukurutiineista, joita Free Pascal ei tarjoa, joten mikään määrä formaattimuunnoksia ei tee niistä käytettäviä. Ne rakennetaan ensin uudelleen Clangiin perustuvalla kääntäjällä, joka ei emittoi kyseisiä riippuvuuksia, ja muunnetaan vasta sen jälkeen
// FPC-kohteen objektit asuvat omassa hakemistossaan eivätkä korvaa
// Delphin objektijoukkoa: molemmat työkaluketjut rakentuvat samasta
// lähdekoodipuusta ja kummalla on omat linkityssyötteensä
//
// Lib\thirdparty\Win32 Delphin OMF-objektit, muuttumattomia
// Lib\thirdparty\Win32f FPC:n COFF-objektit, muunnetut ja normalisoidut
//
// Käännösten sisääntulopisteet:
// build-Win32-Lib-FPC.cmd
// build-Win64-Lib-FPC.cmd
Kääntäjän yksityiset apurutiinit eivät ole siirrettäviä, eivätkä niiden konventiotkaan
Delphin ajonaikainen ympäristö tarjoaa Assembly-trampoliinit 64-bittisiin kokolukuoperaatioihin 32-bittisellä x86:lla, ja Delphille rakennetut esikäännetyt C-objektit kutsuvat niihin. Free Pascalilla on oma järjestelynsä, joten nuo viittaukset on tyydytettävä eri tavalla, ei uudelleenohjattava. Yksityiskohta, joka tekee uudelleenohjauksesta mahdotonta, on kutsukonventio: kuvankäsittelykoodin käyttämän ajoitusapurin nelitavuinen argumentti siivotaan kutsuttavan puolelta, kun taas 64-bittisen jakolaskun apuri siivoaa kuusitoista tavua ja palauttaa tuloksensa klassisessa rekisteriparissa. Kaksi apuria, kaksi konventiota, ja toiselle kirjoitettu trampoliini korruptoi hiljaisesti pinon toiselle
Nimenkoristelu lisää ongelman toisen puoliskon. Win32:lla Free Pascal etuliittää ulkoiset C-tuonnit alaviivalla automaattisesti ja vie public name-määrittelyt sellaisenaan, joten saman sillan tuontipuoli ja vientipuoli seuraavat eri sääntöjä. Se C-ajonaikainen silta, jonka OpenJPEG tarvitsee, on siksi vietävä tarkoilla C-symbolinimillä, ja variadiset sisääntulopisteet tarvitsevat 32-bittisen epäsuoran hypyn suoran sijaan. Mikään tästä ei ole eksoottista kun sen kerran sanoo ääneen. Kaikki epäonnistuu linkitysvirheenä, joka nimeää symbolin, jota kukaan ei kirjoittanut
Mikä sai Win32-ohjelman kuolemaan ennen mainia?
64-bittinen DLL hakupolulla, johon jouduttiin, koska Free Pascalin zlib-yksikkö sitoutuu dynaamisesti staattisen linkittämisen sijaan. Oire oli välitön poistuminen virheellisen imagen tilakoodilla ennen kuin mikään ohjelman Pascal-koodi ehti ajaa, mikä lähettää sinut tuijottamaan juuri rakentamaasi ohjelmaa, vaikka vika on lataajassa, joka ratkaisee tuonnin väärälle arkkitehtuurille
Oppitunti koskee oletuksia, ei zlibiä. Pakkauskirjaston mukaan nimetty yksikkö ei välttämättä sisällä sellaista; se voi olla sidonta, joka odottaa jaettua kirjastoa ajonaikana, ja tahaton dynaaminen riippuvuus on jakeluriski silloinkin, kun se sattuu ratkeamaan. Siirtyminen puhtaaseen Pascal-virtatoteutukseen antaa molemmille kohteille staattisesti mukana tulevan pakkauspolun ilman mitään ulkoista riippuvuutta, eli juuri sen, mitä toisen sovellukseen upotettavan kirjaston pitäisi alun pitäen olla
Sama vaisto pätee ulkoiseen JBIG2-enkooderin taustajärjestelmään. 32-bittisellä kohteella ulkoinen enkooderi ei ole linkitettynä, joten pyynnöt palaavat sisäänrakennettuun Pascal-enkooderiin, ja tämän varmistavan testin on tarkistettava nykyisen kohteen rekisteröintitila sen sijaan, että se pitäisi onnistunutta pakkausta todisteena ulkoisen taustan läsnäolosta. Toimiva varapolku on juuri se asia, joka kätkee puuttuvan riippuvuuden, ja kyseessä on vikakaava, jota tarkastellaan artikkelissa hiljaisten stub-vikojen diagnostiikka. 64-bittisen staattisen linkityksen työ on käsitelty artikkelissa jbig2encin staattinen linkitys FPC:n alla
Muistivirran 32-bittinen aritmetiikka
Puskurikokoja osoitinleveällä etumerkittömällä aritmetiikalla käsittelevä koodi on oikein Win64:lla ja yhden suuren kuvan päässä ylivuodosta Win32:lla. JPEG 2000 -koodekkia ruokkiva muistissa oleva virta kasvaa kaksinkertaistamalla ja etenee lisäämällä, ja 32-bittisellä kohteellä molemmat operaatiot voivat kiertyä syötteillä, jotka ovat suuria mutta täysin laillisia
Jokainen kirjoitus, ohitus, seek ja alkuvaraus tarkistaa siis ennen kuin laskee, ja kapasiteettikatto on suurin etumerkkinen osoitinleveä arvo, valittu vastaamaan sitä, mitä lohkosiirtorutiini ja callbackien paluuarvot pystyvät ilmaisemaan. Vaatimus käytöksestä silloin, kun pyyntö hylätään, on helppo saada väärin: hylkääminen ei saa muuttaa virran sijaintia eikä sen pituutta. Osittainen muutos, jota seuraa virhe, jättää virran tilaan, josta kutsuja ei pysty päättelemään mitään, ja seuraava operaatio pahentaa sitä
// Tarkista ennen kuin lasket. Win32:lla molemmat kiertyvät syötteillä,
// joita suuri JPEG 2000 -kuva tuottaa täysin laillisesti
if Needed > NativeUInt(High(NativeInt)) - FPosition then
Exit(False); // hylkää, jätä sijainti ja koko rauhaan
NewCapacity := FCapacity;
while (NewCapacity < FPosition + Needed) do
begin
if NewCapacity > NativeUInt(High(NativeInt)) shr 1 then
Exit(False); // kaksinkertaistus ylivuotaisi
NewCapacity := NewCapacity shl 1;
end;
Kaksi käännöstulosten ansaa, jotka elävät porttauksen jälkeen
Testi- ja esimerkkiohjelmien erottelu kohdearkkitehtuurin mukaan omiin tulostushakemistoihinsa on ilmeisen oikein ja rikkoo välittömästi kaiken, joka paikallisti testidatansa laskemalla hakemistotasoa ylöspäin. Korjaus on etsiä resurssihakemistoa ylöspäin kiinteän syvyyden olettamisen sijaan, yhdellä tahallisella rajoitteella: allekirjoitusesimerkki hyväksyy varmenteen varapolun vain omasta projektihakemistostaan, ei koskaan mielivaltaisesta esivanhemmasta, koska puusta ylempää löytyvä samanniminen varmenne on turvallisuusyllätys eikä mukavuus
Toinen ansa selviytyy jokaisesta porttauksesta ja kannattaa viedä mukanaan mihin tahansa FPC-projektiin. Kääntäjän päivityksen jälkeen ei riitä, että kääntäjä hylkää vanhentuneet PPU-tiedostot, koska linkkeri suosii yhä yksikköhakupolussa lojuneita objektitiedostoja silloinkin, kun sen lataama PPU tuli oikeasta hakemistosta, eikä eksplisiittisen objektitulostuspolun lisääminen kumoa tätä mieltymystä. Ainoa luotettava vastaus on tuore väliaikainen yksikköhakemisto jokaista käännöskierrosta varten. Mistä tahansa vähemmästä syntyy binääri, joka on linkitetty kahdesta kääntäjäversiosta ja joka epäonnistuu tavoilla, jotka näyttävät lähdekoodin bugeilta
Alustaehdollistukset ovat viimeinen palanen, ja oikean akselin valitseminen merkitsee enemmän kuin näyttää. Oikea kysymys on yleensä se, onko koodi Windows-kohtainen, ei se, onko jokin tietty widget-kirjasto läsnä, kuten metafailien muunnostyö artikkelissa EMF-vektorituonti ja alustaehdollistukset osoitti: kyseisen vahdin vaihtaminen kontrollikirjastoehdosta alustaehdoksi muutti oletetun uudelleenkirjoituksen yhden direktiivin muutokseksi. Free Pascal- ja Lazarus-tuki molemmille Windows-kohteille toimituu PDFlibPas Delphi PDF -kirjaston mukana, rakennettuna samoista lähteistä kuin Delphi- ja C++Builder-paketit