Tehnični članak

Free Pascal Win32: dekoracija C simbolov v HotPDF

Free Pascal na Win32 samodejno doda vodilni podčrtaj vsakemu uvozu cdecl; external, medtem ko public name izvozi niz, ki ste ga zapisali, znak za znak. HotPDF mora zadovoljiti obe konvenciji v istem izvornem drevesu, ker gradnja Delphi že pošilja uvozne deklaracije, ki podčrtaj zapišejo na roko. Napaka v tej asimetriji da napake povezovanja, ki poimenujejo simbol, ki ga ni napisal nihče

Razširjanje Delphi knjižnice na Free Pascal se običajno opisuje kot težava prenosljivosti, na Win64 pa to v glavnem tudi je. Win32 je drugačen. 32-bitni x86 Windows ABI nosi trideset let nabrane konvencije o tem, kako so C simboli zapisani, kdo počisti sklad in katere pomočnike, zasebne za prevajalnik, sme prevajalska enota privzeti — in vsako od teh je mesto, kjer se lahko dva Pascal prevajalnika, ki se o jeziku strinjata, še vedno ne strinjata o objektni datoteki

Zakaj se isti simbol na Win64 razreši, na Win32 pa spodleti?

Ker je podčrtajna predpona 32-bitna konvencija, ki jo Free Pascal uporabi za uvoze, ne pa za izvoze. Deklarirajte function deflate(...): Integer; cdecl; external; in FPC na Win32 išče _deflate v objektni datoteki, na Win64 pa deflate. To je pravilno vedenje in se ujema s tem, kar izda C prevajalnik. Past je na drugi strani mostu: rutina, označena s public name 'deflate', izvozi na obeh ciljih točno deflate, brez dodane predpone

Dodajte še zgodovinsko podrobnost, ki stvar naredi konkretno. Gradnja Delphi že deklarira nekatere od teh vstopnih točk s podčrtajem, zapisanim v ime, ker to vsebujejo njene lastne objektne datoteke. Isti deklaraciji podajte FPC-ju na Win32 in jo bo prevajalnik vestno ponovno opremil s predpono, tako da povezovalnik išče __deflate, simbol, ki ga ne izvozi nič. Intuitivna rešitev — dodati en podčrtaj povsod — pokvari uvoze, ki so bili že pravilno zapisani

Deluje par predponskih konstant namesto ene same. HPDFFPCZLib in HPDFFPCCodecStubs uporabljata eno predpono za navadne uvoze C in drugo za uvoze, ki že nosijo predpono z strani Delphi, na Win64 pa sta obe konstanti prazni, tako da obstoječa povezovalna imena preživijo nedotaknjena. Dve konstanti namesto ene je celotno popravilo, očitno pa je šele, ko ločite pravilo uvoza od pravila izvoza

Iste deklaracije C simbolov, razrešene s strani Free Pascal in Delphi na Win64 in Win32: uvozi cdecl dobijo podčrtaj le na 32-bitnem cilju, deklaracija Delphi, že zapisana s podčrtajem, postane __deflate in ne poveže, izvozi public name pa ostanejo dobesedni na obeh arhitekturah
Ena predponska konstanta ne more služiti obema praviloma: navadni uvozi cdecl in uvozi, ki že nosijo Delphi podčrtaj, se pod FPC na Win32 dekorirajo drugače, zato HotPDF vzdržuje dve in na Win64 pusti obe prazni
// Dva predponi, ne ena: navadni uvozi C in uvozi, ki že nosijo
// ročno napisano Delphi predpono, se pod FPC/Win32 dekorirajo drugače
const
{$IF DEFINED(FPC) and DEFINED(CPU32)}
  CPrefix     = '_';   // FPC to doda sam za cdecl external
  DelphiCName = '';    // podčrtaj je že zapisan v izvorni kodi
{$ELSE}
  CPrefix     = '';
  DelphiCName = '';
{$IFEND}

// Izvozna stran: 'public name' je dobesedna na vsakem cilju
procedure hpdf_codec_free(P: Pointer); cdecl;
  public name 'hpdf_codec_free';

WIN32 pove arhitekturo, ne ABI

To je napaka pogojnega prevajanja z najdaljšim razhroščevalnim repom in vredi jo je izreči na glas: WIN32 in WIN64 opisujeta ciljno arhitekturo in ne povevata nič o tem, kateri pomočniki izvajalnega okolja, zasebni za prevajalnik, obstajajo. Free Pascal definira oba simbola na pripadajočih Windows ciljih, natanko tako kot Delphi. Stražar, zapisan kot {$IFDEF WIN32} okoli kode, ki kliče Delphi pomočnik izvajalnega okolja, se zato pod FPC prevede in spodleti ob povezovanju

Konkretno v to past zaidejo tri družine kode. Delphi trampolini za 64-bitna cela števila, dosegljeni skozi pomočnike System.@_ll, zbirniške podporne rutine MSVC za Win32 in uvozna mesta, ki gredo z njimi, vsi obstajajo za službo vnaprej prevedenim C objektom, ki jih povezuje gradnja Delphi. Free Pascal teh objektov ne povezuje, zato te mehanike ne potrebuje in vsak sklic nanjo mora izginiti. Subtilnost je, da je treba deklaracijo in implementacijo izključiti skupaj. Izključite le eno in prevajalnik sporoči nekaj neuporabnega o identifikatorju, ki ga ne zna ujeti z ničemer

Pravilo, ki pade iz tega, je kratko: stražite na prevajalnik, kadar je vprašanje o ABI ali podpori izvajalnega okolja, stražite na arhitekturo, kadar gre za širino kazalca ali število registrov, in nikoli ne dovolite, da bi eno zastopalo drugo

Straženje deklaracij in implementacij skupaj

V pogojni blok v oddelku vmesnika je enostavno zaiti, ne da bi ga opazili, sporočilo o napaki pa kaže kamorkoli, samo ne na vzrok. Deklaraciji metode v vmesnik razreda dodate in naravno mesto zanjo je ob sorodnih metodah — kar je v redu vse do trenutka, ko se izkaže, da ti sosedje sedejo znotraj obstoječega bloka {$IFDEF}. Pogojne direktive niso zamaknjene, zato je blok, odprt štirideset vrstic višje, med branjem okoliških deklaracij v bistvu neviden

Nato sledi prevod, ki na enem toolchainu uspe in na drugem proizvede plaz. Če je okoliški stražar preverjanje verzije Delphi, ki mu Free Pascal ne zadošča, deklaracija za FPC izgine, brezpogojna implementacija pa ostane, prevajalnik pa sporoči dolg seznam pritožb o identifikatorjih metod, ki jih je pričakoval in jih ni našel. Nobeno od sporočil ne omeni pogojnega bloka, ki ga je povzročil

Dve navadi preprečita celoten razred odpovedi. Pred vstavljanjem v oddelek vmesnika poglejte navzgor po najbližjem odprtem pogoju, namesto da bi zaupali vizualnemu grupiranju. Zeleno testno zbirko Delphi pa jemljite kot dokaz samo o Delphi: gradnja knjižnice Free Pascal je ločena pregrada in edini način, da veste, da gre skozi, je zagnati build-Win32-Lib-FPC.cmd in build-Win64-Lib-FPC.cmd kot del iste spremembe

Kaj se zlomi v 32-bitni aritmetični kodi

Ena jezikovna omejitev se pokaže natanko v kodi, ki se najmanj pripravi spremeniti: 32-bitni Free Pascal ne sprejme UInt64 kot kontrolne spremenljivke zanke for. V enotah eliptičnih krivulj, ki nosita X25519 in X448, so zanke, ki prečkajo polja okončin (limb arrays), bile napisane s 64-bitnimi števci preprosto zato, ker je vse ostalo v datoteki 64-bitno

Popravilo mora biti kirurško, ker je v poljski aritmetiki širina spremenljivke del argumenta pravilnosti. Indeksi zank postanejo Integer, saj ima polje okončin peščico elementov in se noben indeks ne približa 32-bitnemu obsegu. Vse, kar sodeluje v aritmetiki — same okončine, prenos prenašanja in maske — ostane UInt64, ker ožanje katerega koli od njih tiho spremeni rezultat po modulu poljskega praštevila

// 32-bitni FPC zavrne spremenljivko zanke UInt64. Oži le indeks;
// okončine, maske in prenosi obdržijo širino, sicer se poljska matematika spremeni
var
  I: Integer;                 // prej 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;

Preverjanje za tako spremembo ne more biti test povratne poti. Šifriranje in dešifriranje z isto pokvarjeno implementacijo se s sabo popolnoma strinjata, zato so tu vektorji znanih odgovorov nepogajljivi: poženite objavljene testne vektorje X25519 in X448 ter primerjajte točne izhodne bajte. To je edino preverjanje, ki loči pravilno implementacijo od samoskladne napačne, in enako velja za simetrične primitivne, obravnavane v mejah kodekov deflate in AES v Free Pascal

Dve prelomni točki gradnje HotPDF Win32 Free Pascal: stražar {$IFDEF WIN32} okoli Delphi pomočnikov izvajalnega okolja, ki se prevede, a spodleti ob povezovanju, če deklaracije in implementacije nista izključeni skupaj, in spremenljivka zanke UInt64 v prečkanjih okončin X25519 in X448, ožana na Integer, medtem ko okončine, prenosi in maske obdržijo svojo širino
Stražite na prevajalnik, kadar gre za ABI ali podporo izvajalnega okolja, in na arhitekturo, kadar gre za širino kazalca, aritmetične spremembe pa dokažite z objavljenimi vektorji znanih odgovorov in ne s testi povratnih poti

Kaj je vredna gradnja Win32 Free Pascal

Praktični dobiček je, da Lazarus aplikacija, ciljana na 32-bitni Windows, dobi isti dokumentni motor kot njen Delphi dvojček, brez ločene binarne pogodbe za vzdrževanje. Najbolj šteje pri namestitvah, o katerih ljudje redko govorijo: industrijski krmilniki, prodajni terminali in dolgoživa poslovna programska oprema, kjer 32-bitno izvajalno okolje ni dediščinska izbira, temveč strojna omejitev

Zgodba Win64 je prišla prva in opisana je v podpori Free Pascal in Lazarus na Win64. Win32 ni ponovitev te iste. Win64 ima eno klicno konvencijo, brez dekoracije imen in brez Delphi zasebnih celoštevilskih pomočnikov, ki bi se jim bilo treba izogniti, zato je skoraj vse v tem članku specifično za 32-bitni cilj. Aritmetične enote, ki so potrebovale spremembo spremenljivke zanke, so iste, opisane v Montgomery aritmetiki nad krivuljami NIST, kjer je disciplina širine razložena bolj poglobljeno

Splošni pouk je, da delo o prenosljivosti med prevajalniki ni v prvi vrsti o jezikovnih zmožnostih. Oba prevajalnika tu sprejemata isti Object Pascal. Razlika je v objektni datoteki: kako so simboli zapisani, katere pomožne rutine izvajalno okolje sme privzeti in kateri vnaprej prevedeni objekti so v povezavi. HotPDF pošilja pakete Free Pascal in Lazarus ob paketih Delphi in C++Builder v HotPDF Delphi PDF component, tako da isto izvorno drevo napaja vsak toolchain, namesto da bi se razcepilo na prevajalnik