Műszaki cikk

Free Pascal Win32: a C szimbólumdekoráció a HotPDF-ben

A Free Pascal Win32-n minden cdecl; external import elé automatikusan aláhúzást tesz, miközben a public name szóról szóra, betűről betűre exportálja az Ön által írt sztringet. A HotPDFnek ugyanabban a forrásfában kell mindkét konvenciónak megfelelnie, mert a Delphi build már szállít olyan importdeklarációkat, amelyek az aláhúzást kézzel írják ki. Ennek az aszimmetriának az elrontása olyan linkhibákat ad, amelyek egy senki által nem írt szimbólumot neveznek meg

A Delphi könyvtár Free Pascalra bővítését általában hordozhatósági problémaként szokták leírni, és Win64-en többnyire az is. A Win32 más. A 32 bites x86-os Windows ABI harminc év felhalmozott konvencióját hordozza arról, hogyan írnak le a C szimbólumok, ki takarítja le a vermet, és mely fordító privát segédeket tételezhet fel egy fordítási egység, és ezek mindegyike olyan hely, ahol két Pascal fordító, amely egyetért a nyelvben, még eltérhet az objektumfájlban

Miért oldódik fel ugyanaz a szimbólum Win64-en, és bukik meg Win32-n?

Mert az aláhúzás-előtag egy 32 bites konvenció, amelyet a Free Pascal az importokra alkalmaz, az exportokra nem. Deklaráljon egy function deflate(...): Integer; cdecl; external; függvényt, és az FPC Win32-en _deflate-et keres az objektumfájlban, Win64-en deflate-et. Ez helyes viselkedés, és egyezik azzal, amit egy C fordító kiad. A csapda a híd másik oldalán van: egy public name 'deflate'-tel jelölt rutin pontosan deflate-et exportál mindkét célon, előtag nélkül

Most jöjjön az a történelmi részlet, amely konkréttá teszi. A Delphi build egyes belépési pontokat már az aláhúzással együtt deklarál a névben, mert ezt tartalmazzák a saját objektumfájljai. Adja ugyanezt a deklarációt Win32-en az FPC-nek, és a fordító kötelességtudóan újra ellátja előtaggal, tehát a linker __deflate-et keres, egy olyan szimbólumot, amelyet semmi nem exportál. A kézenfekvő javítás, hogy mindenhol berak egy aláhúzást, épp a már helyesen leírt importokat töri meg

Ami működik, az egy előtagkonstans-pár, nem egyetlen konstans. A HPDFFPCZLib és a HPDFFPCCodecStubs egy előtagot használ a sima C importokra és egy másikat azokra az importokra, amelyek már hordoznak Delphi oldali előtagot, Win64-en pedig mindkét konstans üres, tehát a meglévő linknevek érintetlenek maradnak. Két konstans egy helyett az egész javítás, és csak akkor válik nyilvánvalóvá, ha szétválasztotta az importszabályt az exportszabálytól

Ugyanazok a C szimbólumdeklarációk, amelyeket a Free Pascal és a Delphi old fel Win64-en és Win32-n: a cdecl importok csak a 32 bites célon kapnak aláhúzást, egy Delphi deklaráció, amely már az aláhúzással együtt lett leírva, __deflate-té válik és nem linkel, míg a public name exportok mindkét architektúrán szó szerintiek maradnak
Egy előtagkonstans nem szolgálhatja mindkét szabályt: a sima cdecl importok és a már Delphi aláhúzást hordozó importok másképp díszítnek FPC alatt Win32-n, ezért a HotPDF kettőt tart, és Win64-en mindkettőt üresen hagyja
// Két előtag, nem egy: a sima C importok és a kézzel írt Delphi
// előtagot már hordozó importok másképp díszítenek FPC/Win32 alatt
const
{$IF DEFINED(FPC) and DEFINED(CPU32)}
  CPrefix     = '_';   // az FPC ezt maga hozzáteszi a cdecl externalhez
  DelphiCName = '';    // a forrásban már az aláhúzással együtt leírva
{$ELSE}
  CPrefix     = '';
  DelphiCName = '';
{$IFEND}

// Export oldal: a 'public name' minden célon szó szerinti
procedure hpdf_codec_free(P: Pointer); cdecl;
  public name 'hpdf_codec_free';

A WIN32 az architektúrát mondja meg, nem az ABI-t

Ez a feltételes fordítási hiba a leghosszabb hibakeresési farokkal, és érdemes egyszerűen kimondani: a WIN32 és a WIN64 a célarchitektúrát írja le, és semmit sem mond arról, mely fordító privát futásidejű segédek léteznek. A Free Pascal mindkét szimbólumot definiálja a megfelelő Windows célokon, pontosan úgy, mint a Delphi. Egy {$IFDEF WIN32} őr tehát lefordul FPC alatt, és linkeléskor bukik meg, ha Delphi futásidejű segédet hívó kódot fog közre

Konkrétan három kódcsalád esik ebbe a csapdába. A Delphi 64 bites egész trampoline-ok, amelyeket a System.@_ll segédeken keresztül érnek el, az MSVC Win32 assembly támogató rutinok, és a hozzájuk tartozó importhelyek mind azért léteznek, hogy kiszolgálják azokat az előre lefordított C objektumokat, amelyeket a Delphi build linkel. A Free Pascal ezeket az objektumokat nem linkeli, tehát egyik gépezetre sem szüksége, és minden rájuk való hivatkozásnak el kell tűnnie. A finomság az, hogy a deklarációt és az implementációt együtt kell kizárni. Csak az egyiket kizárva a fordító valami használhatatlant jelent egy olyan azonosítóról, amelyet semmihez sem tud párosítani

A levezetett szabály rövid. Akkor őrizzön a fordítóra, ha a kérdés az ABI vagy a futásidejű támogatás, és akkor az architektúrára, ha a kérdés a pointer-szélesség vagy a regiszterszám, és soha ne engedje, hogy az egyik a másikat helyettesítse

Deklarációk és implementációk őrözése együtt

Egy interfész szakaszbeli feltételes blokkba könnyű belecsúszni észrevétlenül, és a kapott hibaüzenet bárhová mutat, csak az okhoz nem. Ha egy metódusdeklarációt vesz fel egy osztályinterfészbe, a kézenfekvő hely a rokon metódusok mellett van, ami pontosan addig rendben van, amíg e szomszédok éppen egy meglévő {$IFDEF} blokkon belül ülnek. A feltételes direktívákat nem húzzuk be, tehát egy negyven sorral feljebb nyíló blokk lényegében láthatatlan, miközben a környező deklarációkat olvassa

Amit azután kap, egy fordítás, amely az egyik toolchainen sikerül, a másikon lavinát indít. Ha a környező őr egy Delphi verzióellenőrzés, amelyet a Free Pascal nem teljesít, a deklaráció eltűnik az FPC számára, miközben a feltétel nélküli implementáció megmarad, és a fordító egy hosszú panaszlistát jelent azokról a metódusazonosítókról, amelyeket várt, és nem talált. Egyik üzenet sem említi az okozó feltételes blokkot

Két szokás előzi meg az egész hibaosztályt. Interfész szakaszba való beszúrás előtt nézzen felfelé a legközelebbi nyitott feltételért, a vizuális csoportosításba vetett bizalom helyett. És a zöld Delphi tesztcsomagot kezelje csak Delphi-re vonatkozó bizonyítékként: a Free Pascal könyvtárbuild külön kapu, és az egyetlen mód annak tudására, hogy átmegy, ha lefuttatja a build-Win32-Lib-FPC.cmd és build-Win64-Lib-FPC.cmd szkripteket ugyanazon változtatás részeként

Mi törik meg a 32 bites aritmetikai kódban?

Egy nyelvi korlátozás pontosan abban a kódban mutatkozik meg, amely a legkevésbé hajlandó változni: a 32 bites Free Pascal nem fogad el UInt64-et for ciklusvezérlő változóként. Az X25519-et és X448-at hordozó elliptikusgörbe unitokban a limb tömböket bejáró ciklusokat egyszerűen azért írták 64 bites számlálókkal, mert a fájlban minden más 64 bites

A javításnak sebészinek kell lennie, mert a field aritmetikában egy változó szélessége a helyességi érvelés része. A ciklusindexek Integer-ek lesznek, mert egy limb tömbnek maroknyi eleme van, és egyetlen index sem közelíti meg a 32 bites tartományt. Minden, ami az aritmetikában részt vesz, maguk a limbok, az átvitelterjedés és a maszkok, UInt64 marad, mert bármelyik szűkítése csendben megváltoztatja az eredményt a test prímje szerint

// A 32 bites FPC elutasít egy UInt64 ciklusváltozót. Csak az indexet szűkítse;
// a limbok, maszkok és átvitelek megtartják szélességüket, különben a field matematika megváltozik
var
  I: Integer;                 // korábban UInt64
  Carry, Mask: UInt64;
begin
  Carry := 0;
  for I := 0 to High(Limbs) do
  begin
    Limbs[I] := Limbs[I] + Carry;
    Carry := Limbs[I] shr 51;
    Limbs[I] := Limbs[I] and Mask;
  end;
end;

Egy ilyen változtatás ellenőrzése nem lehet oda-vissza teszt. A titkosítás és visszafejtés ugyanazzal a hibás implementációval tökéletesen egyetért önmagával, ezért a known-answer vektorok itt alku tárgyát sem képezhetik: futtassa a publikált X25519 és X448 tesztvektorokat, és hasonlítsa össze a pontos kimeneti bájtokat. Ez az egyetlen ellenőrzés, amely megkülönböztet egy helyes implementációt egy önkonzisztensen rossztól, és ugyanúgy érvényes a Free Pascal deflate és AES kodekhatárokról szóló cikkben tárgyalt szimmetrikus primitívekre is

A HotPDF Win32 Free Pascal build két töréspontja: egy {$IFDEF WIN32} őr a Delphi futásidejű segédek körül, amely lefordul, de linkeléskor bukik, kivéve ha a deklarációt és az implementációt együtt zárják ki, valamint az X25519-es és X448-as limb bejárások UInt64 ciklusváltozója, amely Integerre szűkül, miközben a limbok, átvitelek és maszkok megtartják szélességüket
Őrizzön a fordítóra, ha a kérdés ABI vagy futásidejű támogatás, és az architektúrára, ha pointer-szélesség, majd a számtani változtatásokat publikált known-answer vektorokkal bizonyítsa, oda-vissza tesztek helyett

Mennyit ér egy Win32 Free Pascal build?

A gyakorlati hozam az, hogy egy 32 bites Windowst célzó Lazarus alkalmazás ugyanazt a dokumentummotort kapja, mint a Delphi társa, külön karbantartandó bináris szerződés nélkül. Ez azokra a telepítésekre számít a leginkább, amelyekről az emberek ritkán beszélnek: ipari vezérlők, pénztárterminálok és hosszú életű üzleti szoftverek, ahol a 32 bites futtatókörnyezet nem legacy választás, hanem hardverkorlát

A Win64 történet korábban jött, és a Free Pascal és Lazarus támogatás Win64-en írja le. A Win32 nem ennek újrafuttatása. A Win64-en egy hívási konvenció van, nincs névfestés, és nincsenek megkerülendő Delphi privát egészsegédek sem, tehát e cikk szinte minden eleme a 32 bites célon specifikus. A ciklusváltozó-változtatást igénylő aritmetikai unitok ugyanazok, amelyeket a Montgomery aritmetika a NIST görbéken ír le, ahol a szélességi fegyelem részletesebben ki van fejteni

Az általános tanulság az, hogy a keresztfordítói hordozhatósági munka elsősorban nem nyelvi funkciókról szól. Mindkét fordító ugyanazt az Object Pascalt fogadja el itt. Ami eltér, az az objektumfájl: hogyan írják le a szimbólumokat, mely segédrutinoktól tételezi fel, hogy a futtatókörnyezet adja őket, és mely előre lefordított objektumok vannak a linkben. A HotPDF a Free Pascal és Lazarus csomagokat a Delphi és C++Builder csomagokkal együtt szállítja a HotPDF Delphi PDF komponensben, tehát ugyanaz a forrásfa táplál minden toolchaint, ahelyett hogy fordítónként forkolna