Zhruba před rokem a půl jsme řešili vzrůstající počet požadavků uživatelů na vylepšení podpory x64 platformy v naší aplikaci Spyware Terminator. Aplikace je z valné části napsána v Delphi (aktuálně 2007) a tak jsme museli řešit i konverzi systémové služby pro 64bit platformu
Vzhledem k tomu, že se jedná o security aplikaci jsme řešili jako primární problém především změny a úpravy v driveru. Windows na 64bit platformě nepovolují hookovaní systémových funkcí a tak je nutné všechny detekce řešit pomocí oficiálních notifikací.
Úpravy driveru se úspěšně povedly a tak se objevil další požadavek a to 64bit verze systémové služby která byla napsaná v Delphi 2007. O pár zkušeností z této konverze bych se rád podělil.
Upozornění : Zkušenosti jsou založeny na FPC verze 2.2.4. Verze 2.4.0 již má některé chyby opraveny. Stejné problémy však mohou postihovat i jiné zdrojové kódy a proto je publikuji.
Vzhledem k nedostupnosti řešení od EMB jsme použili open source projekt Lazarus s kompilátorem FPC. Lazarus jako IDE se ukázal dostatečně stabilní (ovšem do ideálu má ještě daleko) a použitelný pro vývoj.
Vzhledem ke struktuře aplikace jsme se rozhodli konvertovat pouze systémovou službu. Následující text proto neřeší konverzi VCL > LCL nebo podporu databází. Jako velká výhoda se také ukázalo použití vlastního Windows Services frameworku. Je založen na kódu z roku 1997 a úspěšně používán od NT 4. Jeho výhodou je především čistě API řešení bez VCL návaznosti a možnost spouštění služby v debug režimu v IDE. Kód obsahuje cca 40 tisíc řádků vlastního kódu (bez započítání systémových a 3rd party unit)
A ted zmíněných par tipů :
Neduplikujte si práci
FPC obsahuje poměrně dost různých unit a většina z nich je schopná korektně pracovat na všech platformách. Proveďte analýzu svého kódu a použijte FPC unity tam kde je to možné. V našem případě se jednalo např. o počítání MD5 a dalších pár drobností.
SizeOf(Integer)<>SizeOf(Pointer)
Nejzásadnější problém při konverzi z 32 na 64 bitů jsou jednoznačně délky proměnných. Jistě každý zná a používá konverzi Integer(pointer) a opačně. Na 64bit platformě je to ilegální protože pointer je 64bit ale Integer/Longint jsou pouze 32bit. Důsledky použití jsou vcelku jasné. Jako jistý "bonus" je následné chování debuggeru v případě této chyby. Většinou selže, ukáže neznámou vyjímku a není naplněn callstack. Proto se podobná konverze dost často stává příslovečnou jehlou v kupce kódu.
Windows obsahují typ LPTRINT který je v FPC pod jmenem "ptrint". Tento typ má délku 32/64bit podle cílové platformy a je proto bezpečný pro přetypování.
Jak postupovat při konverzi :
1. projít kód pomocí Find a najít potenciálně nebezpečná přetypování
2. sledovat warning kompilátoru "Warning: Conversion between ordinals and pointers is not portable" který tyto problémy identifikuje
Bohužel se vám může stát že toto nestačí. Kompilátor neukáže všechny přetypování a vy je také nenajdete. Zde je bohužel jen jedna rada - pomocí komentářů eliminujte části kódu a následně logujte průběh operací. A také není špatné se modlit :-) V mém případě se jednalo o místo volané threadem, spouštěným z jiného threadu na základě požadavku ze systémového driveru.
Pozor na to ze Handle je take 64bitová a proto je zde stejný problém.
Typy a jejich definice
V případě že používáte některé API funkce se můžete dostat do stavu kdy definice jednoho typu je v několika unitách. To samozřejmě není zásadní problém ale pouze pokud jsou všechny definice korektní.
FPC 2.2.4 obsahoval JCL unity s chybou kde LPTRINT byl definovan jako = longint. Na první pohled se pak kód jevil jako správný ale nefunkční. Až po delším zkoumání jsem zjistil že JCL používá vlastní definice které byly špatně. Současná veze JCL by již měla být opravena ale na podobný problém můžete narazit i v jiných knihovnách
Rekordy, struktury a jejich zarovnání
Těsně před koncem prací, v době kdy vše vypadalo krásně a funkčně jsme narazili na problém s jedním systémovým eventem. Služba jej vytvořila ale nebyla schopna mu změnit mandatory level. V důsledku toho se 32bit aplikace spouštěné uživatelem nebyly schopny na event připojit a synchronizovat se. Vzhledem k tomu že v debug mode se chyba neprojevuje (service v debugu i aplikace jedou na medium levelu) se problém detekoval relativně pozdě a od začátku nedával moc smysl.
Zkoumáním kódu jsme došli až na funkce pro manipulaci s ACE které vraceli chybu 87 - Invalid parameter. Bohužel tato informace je dost nepoužitelná protože neříká kde je problém. Následovala proto kontrola definice funkce, definice parametrů atd… ale vše se jevilo v pořádku. Protože možnosti byly vyčerpány provedl jsem test s Visual Studiem Express 2008 kde identický kód bez problému prošel. Při následném porovnání opět vypadalo vše stejně. Jako poslední nápad jsem zavolal sizeof na record a structuru a zjistil jsem zásadní rozdíl. Při dalším zkoumání jsem pak došel k tomu že FPC a VS zarovnávají položky v recordech/structurach ruzně.
Rešením pak bylo vložení direktivy {$PACKRECORDS C} která přikáže FPC použít GCC zarovnávání které je různé na různých platformách a korektní.
Službu jsme pak již doladili a tak již nějakou dobu podporujeme x64 verze Windows s plnou podporou realtime štítu stejně jako u 32bit. Části které jsou vizuální jsou dále 32bitové a se službou komunikují pomocí named pipes a to úplně bez problému.
Hodně štěstí při vašich konverzích a pevné nervy