Logo Spiria

Créer une chaîne de compilation Mingw-w64 liée à une bibliothèque Visual Studio Tools

15 décembre 2015.
Compiler des modules natifs en Python, à partir de paquetages sources en ayant recours à une chaîne de compilation compatible Un*x, peut parfois se révéler complexe. Les chaînes de compilation Cygwin et Mingw-w64 se lient avec msvcrt.dll. Pour sa part, l’interpréteur Python, programmé avec Visual Studio 2008, se lie avec une bibliothèque d’exécution C msvcr90.dll plus récente. Il s’agit là d’une source d’instabilités si des ressources allouées à un runtime (que ce soit un fichier ou de la mémoire) sont utilisées par l’autre runtime. Cet article vise à expliquer comment modifier un compilateur open source pour programmer un module Python qui se lie à une bibliothèque runtime C. 

Compiler des modules natifs en Python, à partir de paquetages sources en ayant recours à une chaîne de compilation compatible Un*x, peut parfois se révéler complexe. Les chaînes de compilation Cygwin et Mingw-w64 se lient avec msvcrt.dll. Pour sa part, l’interpréteur Python, programmé avec Visual Studio 2008, se lie avec une bibliothèque d’exécution C msvcr90.dll plus récente. Il s’agit là d’une source d’instabilités si des ressources allouées à un runtime (que ce soit un fichier ou de la mémoire) sont utilisées par l’autre runtime. Cet article vise à expliquer comment modifier un compilateur open source pour programmer un module Python qui se lie à une bibliothèque runtime C. 

Avant de commencer, précisons que la création de modules incluant un fichier de projet Microsoft Visual C fonctionnel peut être accomplie en utilisant un interpréteur Python conçu sur mesure, programmé avec Visual Studio 2010 ou Visual Studio 2012. Un excellent didacticiel étape par étape, se retrouve sur le site web de p-nand-q. Les modules que nous visons ici peuvent uniquement être créés sur des systèmes compatibles Un*x et compilés sur des environnements basés sur Windows, comme ceux que fournissent Cygwin et MSys2.

Cet article vous expliquera comment modifier une chaîne de compilation Mingw-w64 pour vérifier que les produits de votre build se lient à la même bibliothèque Runtime. Dans cet exemple, nous viserons Visual Studio 2012, mais si vous vous intéressez plutôt à l’interpréteur Python standard, il vous suffit de substituer  « 90 » à chacun des « 110 ».

Établir l’environnement du build

Notre environnement de programmation sera MSys2. Récupérez l’outil d’installation 64-bit à partir du site web et installez-le sur votre machine. Puis, ouvrez un shell Msys2 depuis votre menu démarrage Windows. La plateforme MSys2 devra être mise à jour, via la commande suivante :

$ pacman -Sy
$ pacman --needed -S bash pacman pacman-mirrors msys2-runtime

Ouvrez un nouveau shell Msys2 et installez toutes les dépendances nécessaires à la création d’une chaîne de commande Mingw-w64 :

$ pacman -S git make texinfo pkgfile diffutils tar flex coreutils cvs subversion wget patch man zip p7zip automake autoconf libtool bison gettext-devel sshpass

N’installez par de compilateurs qui sont déjà en paquetage, surtout ceux destinés à mingw-w64. Ils entraveront la programmation et le script que nous utiliserons ne le permet pas.

Assembler la chaîne de compilation

Nous utiliserons les scripts Mingw-w64 fournis par niXman sur GitHub, en choisissant la version la plus récente offerte.

$ cd ~
$ git clone https://github.com/niXman/mingw-builds.git
$ cd mingw-builds/
$ git checkout develop

Démarrons le build et assurons-nous qu’il complète ses opérations sans que nous ayons à intervenir :

$ cd ~/mingw-builds/
$ ./build --mode=gcc-5.1.0 --exceptions=seh --jobs=10 --rt-version=trunk --threads=win32 --arch=x86_64 --enable-languages=c,c++,fortran --bootstrap

Commençons maintenant à modifier le build à lier avec msvcr110.dll :

$ ./toolchains/mingw64/bin/gcc.exe -dumpspecs > toolchains/mingw64/lib/gcc/x86_64-w64-mingw32/4.9.3/specs

Ce tout nouveau specfile contrôle maintenant la manière dont le compilateur sera créé. Pour atteindre ce résultat, le chemin d’accès doit être celui du compilateur téléchargé par le script (en l’occurrence, gcc 4.9.3). Ouvrez les fichiers specs dans un éditeur de texte compatible Un*x qui peut lire les fins de lignes interplateformes et modifiez la section suivante en y ajoutant le texte en bleu : 

*cpp:
%{posix:-D_POSIX_SOURCE} %{mthreads:-D_MT} %{municode:-DUNICODE} %{!no-pthread:-D_REENTRANT} %{pthread:-U_REENTRANT} -D__MSVCRT_VERSION__=0x1100 -D__USE_MINGW_ACCESS
*cc1plus:
-D__MSVCRT_VERSION__=0x1100 -D__USE_MINGW_ACCESS
*libgcc:
%{mthreads:-lmingwthrd} -lmingw32 %{static|static-libgcc:-lgcc -lgcc_eh} %{!static: %{!static-libgcc: %{!shared: %{!shared-libgcc:-lgcc -lgcc_eh} %{shared-libgcc:-lgcc_s -lgcc} } %{shared:-lgcc_s -lgcc} } } -lmoldname110 -lmingwex -lmsvcr110

Puis, vous devez créer la bibliothèque libmoldname110 qui se lie à msvcr110.dll (puisque n’importe quel symbole lié via libmoldname  ira tirer des symboles de msvcrt.dll) :

$ cd ~/mingw-builds/toolchains/mingw64/x86_64-w64-mingw32/lib/
$ ../../bin/dlltool.exe -d ~/mingw-gcc-5.1.0_Baseline/src/mingw-w64/mingw-w64-crt/lib64/moldname-msvcrt.def -U --dllname msvcr110.dll -l libmoldname110.a -k --as=~/mingw-builds/toolchains/mingw64/bin/as.exe --as-flags=--64 -m i386:x86-64

Après ces quelques modifications, vous êtes maintenant prêts à commencer votre build. Avant tout, s’il vous reste des artefacts de builds passés, vous devriez procéder à un nettoyage :

$ rm -rf ~/mingw-gcc-5.1.0/x86_64-510-win32-seh-rt_v4/ ~/mingw-gcc-5.1.0/prerequisites-build/

Démarrez maintenant votre build : 

$ cd ~/mingw-builds/
$ ./build --mode=gcc-5.1.0 --exceptions=seh --jobs=10 --rt-version=trunk --threads=win32 --arch=x86_64 --enable-languages=c,c++,fortran --bootstrap

Amener votre build à maturité :

Après ces modifications, vous n’obtiendrez probablement pas le résultat escomptés dès le premier essai. Voici une revue des erreurs les plus communes :

1- Les échecs de linker non-identifiés :

collect2.exe: error: ld returned 127 exit status
Makefile:947: recipe for target 'libgcc_s.dll' failed

Le linker a simplement échoué et ne fournit pas de messages d’erreur. Ce problème peut être particulièrement difficile à déboguer puisque Id.exe est sollicité à travers plusieurs couches de scripts shell et d’exécutables. La meilleure solution reste d’attacher de force un débogueur, dès le démarrage de Id.exe, en ayant recours au registry, tel qu’expliqué ici. Grâce au débogueur, vous remarquerez que l’ancienne chaîne de compilation Id.exe (liée avec msvcrt.dll) plante en essayant de lire un descripteur de fichier ouvert par un plugin LTO nouvellement compilé et qui se lie à mscvcr110.dll. La solution est de s’assurer que le lien s’effectue via le nouveau Id.exe en éditant ~\mingw-gcc-5.1.0\x86_64-510-win32-seh-rt_v4\build\gcc-5.1.0\gcc\collect-ld.sh pour y changer les lignes suivantes :

ORIGINAL_LD_FOR_TARGET="F:/msys64/home/jgamache/mingw-builds/toolchains/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/4.9.3/../../../../x86_64-w64-mingw32/bin/ld.exe"
ORIGINAL_LD_BFD_FOR_TARGET="F:/msys64/home/jgamache/mingw-builds/toolchains/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/4.9.3/../../../../x86_64-w64-mingw32/bin/ld.bfd.exe"
ORIGINAL_LD_GOLD_FOR_TARGET="F:/msys64/home/jgamache/mingw-builds/toolchains/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/4.9.3/../../../../x86_64-w64-mingw32/bin/ld.gold.exe"
ORIGINAL_PLUGIN_LD_FOR_TARGET="F:/msys64/home/jgamache/mingw-builds/toolchains/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/4.9.3/../../../../x86_64-w64-mingw32/bin/ld.exe"

Pour

ORIGINAL_LD_FOR_TARGET="F:/msys64/home/jgamache/mingw-gcc-5.1.0/x86_64-510-win32-seh-rt_v4/mingw64/x86_64-w64-mingw32/bin/ld.exe"
ORIGINAL_LD_BFD_FOR_TARGET="F:/msys64/home/jgamache/mingw-gcc-5.1.0/x86_64-510-win32-seh-rt_v4/mingw64/x86_64-w64-mingw32/bin/ld.bfd.exe"
ORIGINAL_LD_GOLD_FOR_TARGET="F:/msys64/home/jgamache/mingw-gcc-5.1.0/x86_64-510-win32-seh-rt_v4/mingw64/x86_64-w64-mingw32/bin/ld.gold.exe"
ORIGINAL_PLUGIN_LD_FOR_TARGET="F:/msys64/home/jgamache/mingw-gcc-5.1.0/x86_64-510-win32-seh-rt_v4/mingw64/x86_64-w64-mingw32/bin/ld.exe"

Puis, redémarrez le build. Le script collect-Id.sh sera régénéré à chacune des étapes du build GCC, vous devrez donc répéter cette étape 3 fois.

2 – La bibliothèque libmoldname110 manquante :

Le linker affiche ce message :

ld.exe: cannot find -lmoldname110

Nous devons propager libmoldname110.a à toutes les localisations où libmoldname.a reste trouvable, puis redémarrer le build.

$ cd ~/mingw-gcc-5.1.0
$ find -name libmoldname.a -execdir cp ~/mingw-builds/toolchains/mingw64/x86_64-w64-mingw32/lib/libmoldname110.a . \;

3- Vos nouvelles bibliothèques ne se lient pas au runtime approprié:

Vous devrez probablement vérifier rapidement que vos nouvelles bibliothèques sont correctement liées. Le meilleur outil pour ce travail reste Dependency Walker. Utilisez-le pour vérifier un des premiers produits de votre build (comme ~/mingw-gcc-5.1.0/x86_64-510-win32-seh-rt_v4/build/bzip2-1.0.6/bzip2.exe) et assurez-vous que msvcr110.dll est la seule bibliothèque runtime référencée par cet exécutable, que ce soit directement ou indirectement. 

Quelques améliorations a posteriori :

Ces changements ne sont pas nécessaires pour compléter le build. Ils vont devenir utiles lors de la compilation de modules Python. La première erreur est généralement liée à la redéfinition d'un symbole lié aux nombres entiers. Pour corriger ce problème, ouvrez le fichier ~/mingw-gcc-5.1.0/x86_64-510-win32-seh-rt_v4/mingw64/x86_64-w64-mingw32/include/basetsd.h et ajoutez des clauses empêchant la redéfinition des typedef:

#if !defined(INT8)
  typedef signed char INT8,*PINT8;
#endif  
#if !defined(INT16)
  typedef signed short INT16,*PINT16;
#endif  
#if !defined(INT32)
  typedef signed int INT32,*PINT32;
#endif  
#if !defined(INT64)
  __MINGW_EXTENSION typedef signed __int64 INT64,*PINT64;
#endif  
#if !defined(UINT8)
  typedef unsigned char UINT8,*PUINT8;
#endif  
#if !defined(UINT16)
  typedef unsigned short UINT16,*PUINT16;
#endif  
#if !defined(UINT32)
  typedef unsigned int UINT32,*PUINT32;
#endif  
#if !defined(UINT64)
  __MINGW_EXTENSION typedef unsigned __int64 UINT64,*PUINT64;
#endif  
#if !defined(LONG32)
  typedef signed int LONG32,*PLONG32;
#endif  
#if !defined(ULONG32)
  typedef unsigned int ULONG32,*PULONG32;
#endif  
#if !defined(DWORD32)
  typedef unsigned int DWORD32,*PDWORD32;
#endif

Vous êtes maintenant prêts à utiliser la chaîne de compilation. Vous pouvez maintenant l’ajouter à une archive tar pour l’installer sur n’importe quelle installation Msys2 vierge.

$ cd ~/mingw-gcc-5.1.0/x86_64-510-win32-seh-rt_v4/
$ tar -cJvf ~/x86_64-510-win32-seh-rt_v4_VS2012.tar.xz
$ cd /
$ tar -xf ~/x86_64-510-win32-seh-rt_v4_VS2012.tar.xz

Si vous fermez le shell Msys2 et en ouvrez un nouveau en utilisant C:\msys64\mingw64_shell.bat à la place, vous devriez pouvoir compiler un fichier test C qui se liera au runtime, tel que vous le désirez.