Teknik Makale

PDFlibPas içinde FPC Win32 OMF'den COFF'a Nesne Bağlama

PDFlibPas, 32 bit Windows hedefi için Free Pascal altında derlenir ve zor kısım hiçbir zaman Pascal olmadı. Asıl mesele nesne dosyalarıydı: Delphi derlemesinin bağladığı AES ve OpenJPEG nesneleri OMF biçimindedir, Free Pascal'ın dahili bağlayıcısı COFF ister ve ikisi arasındaki dönüşüm, bağlayıcıya düzgün tanı mesajı yerine internal error verdiren bölüm adları ile bölüm tanım sembolleri üretir

Bir Pascal kütüphanesine hiç C nesnesi bağlamış herkes bu araziyi tanır. Win64 kıyasla medenidir: tek nesne formatı, tek çağırma konvansiyonu, name decoration yok. Win32 ise platformun biriktirdiği tarihin her katmanını korur ve üçüncü taraf C kodunu statik bağlayan bir kütüphane bunların hepsiyle aynı anda karşılaşır

Derleyici dizini size hedefi söylemez

Derleme giriş noktasından başlayın; burayı yanlış kurmak, hiçbir nesne dosyası devreye girmeden saatler kaybettirir. Bir Free Pascal kurulum dizininin adı, ana derleyicinin nerede durduğunu söyler, ne ürettiğini değil. 32 bitlik bir host derleyici, yanındaki cross-compiler'ı çağırıp doğru hedef anahtarlarını verdiğinizde 64 bit kod üretebilir; bu yüzden hedefi yoldan tahmin etmek, biri araç zincirini yeniden düzenleyene kadar tesadüfen işleyen bir kumardır

Güvenilir yol, derleyiciye sormaktır. Gerçek hedef işlemciyi ve işletim sistemini derleyicinin kendi bilgi anahtarları üzerinden sorgulayın; hem düz binary dizini hem sürüm iç içe dizin olan iki yaygın kurulum yerleşimini de kabul edin, çünkü farklı yükleyiciler ve araç zinciri yöneticileri farklı şekiller üretir. Herhangi bir yerleşimi hard-code eden derleme betiği tam olarak tek bir makinede çalışır

Dönüştürülmüş bir nesne dosyası dahili bağlayıcıyı neden kırar?

Çünkü dönüşüm, OMF bölüm adlandırma konvansiyonunu korur ve COFF bağlayıcısının beklediğiyle eşleşmeyen bölüm tanım sembolleri sentezler. OMF nesnelerini COFF'a dönüştürmek gerekli ama yeterli değildir: ortaya çıkan dosyalar klasik _TEXT, _DATA ve _BSS bölüm adlarını ve bunlardan türetilen bölüm tanım sembol adlarını taşır; bunu Free Pascal'ın dahili bağlayıcısına vermek ise bölüm adlandırma hakkında bir mesaj değil, internal compiler error üretir

Internal error, bir derleme sorunu için en kötü hata modudur; girdinin ne yanlış olduğu hakkında hiçbir şey söylemez. Çözüm, COFF dosyası üzerinde dönüşüm sonrası bir normalize geçişi yapmaktır: bölüm adlarını beklenen biçime yeniden yazın, karşılık gelen bölüm tanım sembollerini de eşleşecek şekilde yeniden yazın; sembol indeksine, kod baytlarına ve relocation girişlerine dokunmadan. Son kısıt, zorluğun tamamıdır. Sembolleri yeniden numaralandıran ya da offsetleri kaydıran bir yeniden yazım, bağlanan ve sonra çöken bir nesne üretir

İki nesne setinden biri için bir ön adım daha var. Klasik 32 bit C++ derleyicisiyle derlenen OpenJPEG nesneleri, Delphi'ye özel 64 bit tamsayı yardımcı rutinlerine bağımlıdır ve Free Pascal bunları sağlamaz; dolayısıyla ne kadar format dönüşümü yaparsanız yapın kullanılabilir hale gelmezler. Bu nesneler önce bu bağımlılıkları üretmeyen Clang tabanlı derleyiciyle yeniden derlenir, sonra dönüştürülür

PDFlibPas statik C nesnelerini Win32'de Lib\thirdparty\Win32 altındaki Delphi OMF'den Lib\thirdparty\Win32f altındaki FPC ile bağlanabilir COFF'a taşıyan hat: OMF'den COFF'a dönüşüm, sembol indeksine, kod baytlarına ve relocation girişlerine dokunmadan bölüm adlarını ve bölüm tanım sembollerini yeniden yazan normalize geçişi ve Delphi 64 bit yardımcılarını çağıran OpenJPEG nesneleri için Clang yeniden derlemesi
Dönüştürmek gerekli ama yeterli değil: yeniden adlandırılmış ama normalize edilmemiş COFF'u Free Pascal'ın dahili bağlayıcısına verirseniz cevap internal error olur; dönüşüm sonrası geçiş, offsetlere dokunmadan adları ve sembolleri düzeltir
// FPC hedefine ait nesneler kendi dizininde durur. Bu set, Delphi
// nesne setinin yerini almaz; iki araç zinciri de aynı kaynak ağacından
// derlenir ve her birinin kendi bağlama girdilerine ihtiyacı vardır
//
//   Lib\thirdparty\Win32   Delphi OMF nesneleri, olduğu gibi
//   Lib\thirdparty\Win32f  FPC COFF nesneleri, dönüştürülmüş ve normalize edilmiş
//
// Derleme giriş noktaları:
//   build-Win32-Lib-FPC.cmd
//   build-Win64-Lib-FPC.cmd

Derleyiciye özel yardımcılar taşınabilir değildir, konvansiyonları da öyle

Delphi runtime, 32 bit x86 üzerinde 64 bit tamsayı işlemleri için assembly trampoline rutinleri sağlar ve Delphi için derlenen önderlenmiş C nesneleri bunları çağırır. Free Pascal'ın kendi düzeni vardır; bu yüzden söz konusu referanslar yönlendirilmek yerine farklı biçimde karşılanmak zorundadır. Yönlendirmeyi imkansız kılan ayrıntı çağırma konvansiyonudur: görüntüleme kodunun kullandığı zamanlama yardımcısında dört baytlık argümanı callee temizler, 64 bit bölme yardımcısı ise on altı bayt temizler ve sonucunu klasik register çiftinde döndürür. İki yardımcı, iki konvansiyon ve biri için yazılmış bir trampoline, öbürü için stack'i sessizce bozar

Name decoration sorunun ikinci yarısını ekler. Win32'de Free Pascal, harici C importlarının başına otomatik olarak alt tire eklerken public name bildirimlerini aynen dışa aktarır; böylece aynı köprünün import tarafı ile export tarafı farklı kurallara uyar. Bu yüzden OpenJPEG'in ihtiyaç duyduğu C runtime köprüsü, C sembol adlarını harfiyen export etmek zorundadır ve variadic giriş noktaları doğrudan olan değil 32 bitlik dolaylı bir jump ister. Söyleyince hiçbiri egzotik değil. Hepsi, kimsenin yazmadığı bir sembolü adıyla bildiren link hatası olarak patlar

Bir Win32 çalıştırılabilirini main'den önce öldüren neydi?

Arama yolundaki 64 bitlik bir DLL; Free Pascal'ın zlib uniti statik bağlanmak yerine dinamik bağlandığı için devreye girmişti. Belirti, programdaki hiçbir Pascal kodu çalışmadan, invalid-image durum koduyla ani çıkıştı; bu da sizi, hata aslında yükleyicinin bir importu yanlış mimariye karşı çözmesindeyken az önce derlediğiniz programı incelemeye yönlendirir

Ders zlib'den çok varsayımlarla ilgili. Bir sıkıştırma kütüphanesinin adını taşıyan bir unit, o kütüphaneyi içermek zorunda değildir; çalışma zamanında paylaşımlı kütüphane bekleyen bir bağlama olabilir ve istemeden alınmış dinamik bir bağımlılık, çözülse bile dağıtım açısından bir yüktür. Saf Pascal akış uygulamasına geçmek her iki hedefe de hiçbir dış bağımlılığı olmayan, statik dahil edilmiş bir sıkıştırma yolu verir; bir başkasının uygulamasına gömülen bir kütüphanede zaten baştan olması gereken de budur

Aynı içgörü, harici JBIG2 kodlayıcı backend'i için de geçerlidir. 32 bit hedefte harici kodlayıcı bağlanmaz, istekler yerleşik Pascal kodlayıcısına düşer ve bunu doğrulayan test, başarılı bir kodlamayı harici backend'in var olduğunun kanıtı saymak yerine güncel hedefin kayıt durumunu kontrol etmek zorundadır. İşleyen bir fallback, tam da eksik bağımlılığı gizleyen şeydir; bu, sessiz stub hatalarının teşhisi makalesinde incelenen hata örüntüsüdür. 64 bit statik bağlama çalışması ise FPC altında jbig2enc statik bağlama makalesinde ele alınır

Free Pascal altında main'den önce çıkan bir Win32 yürütülebilirinin teşhis akışı: yükleyici, unit ilklendirmesi çalışırken importları çözer, zlib bağlaması arama yolunda 64 bitlik bir DLL bulur ve süreç, tek bir Pascal ifadesi çalışmadan invalid-image durumuyla ölür; PDFlibPas'ı statik dahil edilmiş saf Pascal sıkıştırma yoluna yönlendirir
Hata asla az önce derlenen programda değildi: zlib adlı bir unit, yanlış mimariye karşı çözülen bir çalışma zamanı bağlamasıydı; yerleşik JBIG2 kodlayıcı gibi işleyen bir fallback ise eksik bağımlılığı gizler

Bir bellek akışında 32 bit aritmetik

Tampon boyutlarını işaretçi genişliğinde işaretsiz aritmetikle yöneten kod Win64'te doğrudur, Win32'de ise büyük tek bir imge kadar taşmaya yakındır. JPEG 2000 codec'ini besleyen bellek içi akış ikiye katlanarak büyür ve eklemelerle ilerler; 32 bit hedefte her iki işlem de büyük ama tamamen meşru girişlerde taşabilir (wrap)

Bu yüzden her yazma, atlama, seek ve ilk ayırma işlemi hesaplamadan önce kontrol eder ve kapasite tavanı, blok taşıma rutininin ve callback dönüş değerlerinin ifade edebileceğiyle eşleşecek şekilde seçilen, işaretli işaretçi genişliğindeki en büyük değerdir. Bir istek reddedildiğindeki davranış kuralını yanlış kurmak kolaydır: red, akışın konumunu da uzunluğunu da değiştirmemelidir. Hata ile bırakılan kısmi bir değişiklik, akışı çağıranın akıl yürütemeyeceği bir durumda bırakır ve bir sonraki işlem bunu büyütür

// Hesaplamadan önce kontrol et. Win32'de her iki işlem de büyük bir
// JPEG 2000 imgesinin meşru biçimde üreteceği girişlerde taşar
if Needed > NativeUInt(High(NativeInt)) - FPosition then
  Exit(False);                    // reddet, konumu ve boyutu olduğu gibi bırak

NewCapacity := FCapacity;
while (NewCapacity < FPosition + Needed) do
begin
  if NewCapacity > NativeUInt(High(NativeInt)) shr 1 then
    Exit(False);                  // ikiye katlama taşmaya yol açardı
  NewCapacity := NewCapacity shl 1;
end;

Portajdan uzun ömürlü iki derleme çıktısı tuzağı

Test ve örnek yürütülebilirlerini hedef mimariye göre hedef başına çıktı dizinlerine ayırmak gayet doğru bir harekettir ve test verisini yukarı doğru dizin seviyeleri sayarak bulan her şeyi anında kırar. Çözüm, sabit bir derinlik varsaymak yerine varlık dizinini yukarı doğru aramaktır; tek kasıtlı kısıtla: imzalama örneği sertifika yedeğini yalnızca kendi proje dizininden kabul eder, rastgele bir üst dizinden asla; çünkü ağaçta daha yukarıda bulunan aynı adlı sertifika kolaylık değil, güvenlik sürprizidir

İkinci tuzak her portajdan sağ çıkıyor ve herhangi bir FPC projesine taşımaya değer. Derleyici yükseltmesinden sonra derleyicinin bayat PPU dosyalarını reddetmesi yetmez; bağlayıcı, yüklediği PPU doğru dizinden gelmiş olsa bile unit arama yolunda kalmış nesne dosyalarını tercih etmeye devam eder ve açık bir nesne çıktı yolu eklemek bu tercihi geçersiz kılmaz. Tek güvenilir cevap, her derleme turu için taze bir geçici unit dizinidir. Daha azı, iki derleyici sürümünden bağlanmış bir binary üretir ve bu, kaynak kodu hatasına benzeyen şekillerde patlar

Platform koşulluları son parça ve doğru ekseni seçmek göründüğünden daha önemli. Doğru soru genellikle belirli bir widget kütüphanesinin var olup olmadığı değil, kodun Windows'a özel olup olmadığıdır; EMF vektör içe aktarımı ve platform koşulluları çalışındaki metafile dönüştürme işi bunu gösterdi: o guard'ı kontrol kütüphanesi koşulundan platform koşuluna çevirmek, sanılan yeniden yazımı tek bir directive değişikliğine indirdi. Free Pascal ve Lazarus desteği, her iki Windows hedefi için de Delphi ve C++Builder paketleriyle aynı kaynaklardan derlenen PDFlibPas Delphi PDF kütüphanesi ile birlikte gelir