PDFlibPas se construit sous Free Pascal pour Windows 32 bits, et la partie difficile n'a jamais été le Pascal. C'étaient les fichiers objets : les objets AES et OpenJPEG que l'édition de liens Delphi consomme sont en OMF, l'éditeur de liens interne de Free Pascal exige du COFF, et la conversion entre les deux produit des noms de sections et des symboles de définition de section qui font échouer l'éditeur de liens avec des erreurs internes plutôt qu'avec de vrais diagnostics
Quiconque a déjà édité les liens de fichiers objets C dans une bibliothèque Pascal connaît ce territoire. Win64 est comparativement civilisé, avec un seul format d'objet, une seule convention d'appel et aucune décoration de noms. Win32 préserve toutes les couches d'histoire accumulées par la plateforme, et une bibliothèque qui édite statiquement les liens de code C tiers les rencontre toutes en même temps
Le répertoire du compilateur ne vous dit pas la cible
Commencez par le point d'entrée de construction, car se tromper ici vous fait perdre des heures avant même qu'un fichier objet ne soit impliqué. Le nom d'un répertoire d'installation de Free Pascal indique où vit le compilateur principal, pas ce qu'il produit. Un compilateur hôte 32 bits peut invoquer un compilateur croisé installé à côté de lui et émettre du code 64 bits quand vous passez les bons commutateurs de cible, donc déduire la cible d'un chemin relève de la devinette qui fonctionne par hasard jusqu'à ce que quelqu'un réorganise sa chaîne d'outils
L'approche fiable consiste à demander au compilateur. Interrogez le processeur cible et le système d'exploitation effectifs via les commutateurs d'information du compilateur lui-même, et acceptez les deux dispositions d'installation courantes, le répertoire binaire à plat et celui imbriqué par version, car différents installeurs et gestionnaires de chaîne d'outils produisent des formes différentes. Un script de construction qui code en dur l'une ou l'autre disposition ne fonctionne qu'en exactement une machine
Pourquoi un fichier objet converti casse-t-il l'éditeur de liens interne ?
Parce que la conversion préserve la convention de nommage des sections OMF et synthétise des symboles de définition de section qui ne correspondent pas à ce que l'éditeur de liens COFF attend. Convertir les objets OMF en COFF est nécessaire mais pas suffisant : les fichiers obtenus portent les noms de sections classiques _TEXT, _DATA et _BSS, plus des noms de symboles de définition de section qui en dérivent, et nourrir l'éditeur de liens interne de Free Pascal avec cela produit des erreurs internes du compilateur plutôt qu'un message parlant de nommage de sections
Une erreur interne est le pire mode d'échec pour un problème de construction, car elle ne dit rien de ce qui n'allait pas dans l'entrée. La correction est une passe de normalisation post-conversion sur le fichier COFF : réécrire les noms de sections sous la forme attendue et réécrire les symboles de définition de section correspondants pour qu'ils concordent, en laissant intacts l'index des symboles, les octets de code et les relocalisations. Cette dernière contrainte constitue toute la difficulté. Une réécriture qui renumérote les symboles ou décale les offsets produit un objet qui édite ses liens puis plante
Il y a une étape préliminaire pour l'un des deux jeux d'objets. Les objets OpenJPEG construits par le compilateur C++ 32 bits classique dépendent de routines privées Delphi pour les entiers 64 bits, que Free Pascal ne fournit pas, donc aucune conversion de format ne les rend utilisables. On les reconstruit d'abord avec le compilateur basé sur Clang, qui n'émet pas ces dépendances, puis on les convertit
// Les objets pour la cible FPC vivent dans leur propre répertoire. Ils ne
// remplacent pas le jeu d'objets Delphi, car les deux chaînes d'outils
// construisent depuis le même arbre source et chacune a besoin de ses propres entrées d'édition de liens
//
// Lib\thirdparty\Win32 objets OMF Delphi, inchangés
// Lib\thirdparty\Win32f objets COFF FPC, convertis et normalisés
//
// Points d'entrée de construction :
// build-Win32-Lib-FPC.cmd
// build-Win64-Lib-FPC.cmd
Les helpers privés du compilateur ne sont pas portables, et leurs conventions non plus
Le runtime Delphi fournit des trampolines en assembleur pour les opérations sur entiers 64 bits en x86 32 bits, et les objets C précompilés construits pour Delphi les appellent. Free Pascal a sa propre organisation, donc ces références doivent être satisfaites différemment plutôt que redirigées. Le détail qui rend la redirection impossible est la convention d'appel : le helper de minutage utilisé par le code d'imagerie fait nettoyer son argument de quatre octets par l'appelé, tandis que le helper de division 64 bits nettoie seize octets et rend son résultat dans la paire de registres classique. Deux helpers, deux conventions, et un trampoline écrit pour l'un corrompt silencieusement la pile pour l'autre
La décoration de noms ajoute la seconde moitié du problème. Sur Win32, Free Pascal préfixe automatiquement d'un underscore les imports C externes tout en exportant verbatim les déclarations public name, si bien que le côté import et le côté export du même pont suivent des règles différentes. Le pont vers le runtime C dont OpenJPEG a besoin doit donc exporter les noms de symboles C exacts, et les points d'entrée variadiques ont besoin d'un saut indirect 32 bits plutôt que d'un saut direct. Rien de tout cela n'est exotique une fois énoncé. Tout échoue sous forme d'erreur d'édition de liens nommant un symbole que personne n'a écrit
Qu'est-ce qui faisait mourir un exécutable Win32 avant le main ?
Une DLL 64 bits sur le chemin de recherche, atteinte parce que l'unité zlib de Free Pascal se lie dynamiquement au lieu d'être liée statiquement. Le symptôme était une sortie immédiate avec le code de statut d'image invalide, avant que le moindre code Pascal du programme ne s'exécute, ce qui vous envoie inspecter le programme que vous venez de construire alors que le défaut est dans le chargeur qui résout un import contre la mauvaise architecture
La leçon porte sur les hypothèses plutôt que sur zlib. Une unité nommée d'après une bibliothèque de compression n'en contient pas nécessairement une ; ce peut être une liaison qui attend une bibliothèque partagée à l'exécution, et une dépendance dynamique non voulue est un fardeau de déploiement même quand elle se résout par chance. Passer à l'implémentation de flux en Pascal pur donne aux deux cibles un chemin de compression statiquement inclus sans aucune dépendance externe, ce que devrait avoir dès le départ une bibliothèque embarquée dans l'application de quelqu'un d'autre
Le même instinct s'applique au backend externe d'encodage JBIG2. Sur la cible 32 bits, l'encodeur externe n'est pas lié, donc les requêtes retombent sur l'encodeur Pascal intégré, et le test qui vérifie cela doit contrôler l'état d'enregistrement de la cible courante plutôt que de traiter un encodage réussi comme la preuve de la présence du backend externe. Un repli qui fonctionne est précisément ce qui cache une dépendance manquante, et c'est le schéma d'échec examiné dans le diagnostic des échecs silencieux de stubs. Le travail d'édition de liens statique 64 bits est couvert dans l'édition de liens statique de jbig2enc sous FPC
Arithmétique 32 bits sur un flux mémoire
Du code qui manipule des tailles de tampons avec de l'arithmétique non signée de la largeur d'un pointeur est correct sur Win64 et à une grande image du débordement sur Win32. Le flux en mémoire qui alimente le codec JPEG 2000 croît par doublement et avance par addition, et sur une cible 32 bits les deux opérations peuvent boucler sur des entrées volumineuses mais parfaitement légitimes
Chaque écriture, saut, positionnement et allocation initiale vérifie donc avant de calculer, et le plafond de capacité est la plus grande valeur signée de la largeur du pointeur, choisie pour correspondre à ce que la routine de déplacement de blocs et les valeurs de retour des callbacks peuvent exprimer. L'exigence comportementale quand une requête est refusée est facile à rater : refuser ne doit ni changer la position du flux ni sa longueur. Une mutation partielle suivie d'une erreur laisse le flux dans un état que l'appelant ne peut pas raisonner, et l'opération suivante aggrave le cas
// Vérifiez avant de calculer. Sur Win32 les deux tests bouclent sur des
// entrées qu'une grande image JPEG 2000 produit légitimement
if Needed > NativeUInt(High(NativeInt)) - FPosition then
Exit(False); // refuser, laisser position et taille intactes
NewCapacity := FCapacity;
while (NewCapacity < FPosition + Needed) do
begin
if NewCapacity > NativeUInt(High(NativeInt)) shr 1 then
Exit(False); // le doublement déborderait
NewCapacity := NewCapacity shl 1;
end;
Deux pièges de sortie de construction qui survivent au portage
Séparer les exécutables de test et d'exemple par architecture cible dans des répertoires de sortie par cible est évidemment juste et casse immédiatement tout ce qui localisait ses données de test en comptant les niveaux de répertoires vers le haut. La correction est de chercher vers le haut le répertoire de ressources plutôt que de supposer une profondeur fixe, avec une restriction délibérée : l'exemple de signature n'accepte un certificat de repli que depuis son propre répertoire de projet, jamais depuis un ancêtre arbitraire, car un certificat de même nom trouvé plus haut dans l'arbre est une surprise de sécurité plutôt qu'une commodité
Le second piège survit à tous les portages et mérite d'être emporté dans tout projet FPC. Après une montée de version du compilateur, faire rejeter par le compilateur les fichiers PPU périmés ne suffit pas, car l'éditeur de liens préfère encore les fichiers objets résiduels dans le chemin de recherche des unités même quand le PPU qu'il a chargé venait du bon répertoire, et ajouter un chemin de sortie d'objets explicite ne contourne pas cette préférence. La seule réponse fiable est un répertoire d'unités temporaire frais à chaque tour de construction. Moins que cela produit un binaire édité depuis deux versions du compilateur, qui échoue de façons qui ressemblent à des bugs de source
Les conditionnelles de plateforme sont la dernière pièce, et choisir le bon axe compte plus qu'il n'y paraît. La bonne question est généralement de savoir si le code est spécifique à Windows plutôt que si une bibliothèque de composants particulière est présente, comme l'a montré le travail de conversion de métafichiers dans l'import vectoriel EMF et les conditionnelles de plateforme : basculer cette garde d'une condition sur la bibliothèque de contrôles vers une condition de plateforme a transformé une réécriture supposée en changement d'une directive. Le support de Free Pascal et Lazarus pour les deux cibles Windows est livré avec la bibliothèque PDF Delphi PDFlibPas, construite depuis les mêmes sources que les paquets Delphi et C++Builder