diff --git a/content/articles/Attempting-an-aarch64-port.fr.md b/content/articles/Attempting-an-aarch64-port.fr.md new file mode 100644 index 0000000..8b06208 --- /dev/null +++ b/content/articles/Attempting-an-aarch64-port.fr.md @@ -0,0 +1,319 @@ ++++ +title = "Tenter un portage pour AArch64" +date = 2021-05-23 +[taxonomies] +categories = ["Software"] +tags = ["Arm","aarch64","QEMU","Emulation","Virtualisation"] ++++ + + +Je me suis dit qu'essayer de porter une application pour l'architecture AArch64 (Armv8/Arm 64 bits) pourrait être intéressant, voilà ce que j'en ai appris. + + + +Avant-propos : Je suis actuellement employé par Arm mais ces recherches et expérimentations ont +été réalisées sur mon temps libre, sans incitation ni lien avec mon employeur. + +Il faut parfois trouver de quoi s'occuper, et ce week-end s'annonçait ainsi. Cependant en explorant la liste +des exécutables et paquets disponibles d'un logiciel, j'ai remarqué un absent : l'architecture Arm. +Ne programmant pas beaucoup ces derniers temps et n'ayant jamais vraiment de fait de cross-compilation +(la compilation pour un système ayant une architecture différente), je me suis dit que c'était l'occasion +d'expérimenter ! + + +# La cible +## Présentation + +Le logiciel en question est [Cockatrice](https://cockatrice.github.io/). Il permet de jouer des +parties de Magic The Gathering® de façon très flexible, en ligne comme en local, gérer des decks +et probablement moult autres choses. C'est un projet Open Source à l'aspect certes un peu sommaire +mais qui reste un excellent moyen de jouer au jeu sur l'ordinateur. + +Je ne l'ai pas beaucoup utilisé moi-même mais mon groupe d'ami l'utilise extensivement depuis les confinements. + + +## Pourquoi Cockatrice ? + +Un des premiers point décisif est que c'est un projet Open Source : il aurait été très compliqué de porter +un logiciel propriétaire, en tous cas de façon propre et fiable. + +Le deuxième point décisif est la simplicité apparente du projet : seulement deux dépendances, elles-aussi +Open Source, un langage et un système de compilation que je connais bien : C++ et CMake. + +Pour d'autres points qui ont validé le choix est qu'il était déjà multiplateforme (Windows,Mac,Linux;32,64 bits) +et que mes amis l'utilisant j'avais accès à des ~~cobayes~~ testeurs. + + +Après un `git clone` et une compilation de test sur ma machine x86_64, il était temps de s'y mettre. + + +# Cross-compilation + +## Mise en place + +Tout d'abord il me fallait la suite de compilation complète pour AArch64. Après une recherche rapide +parmi les paquets Arch Linux, j'ai simplement installé les paquets `aarch64-linux-gnu-{gcc,binutils}`. + +Un rapide test a confirmé que je pouvais compiler du C++ pour AArch64 : + +``` +a.out: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 3.7.0 +``` + +Les deux dépendances principales de Cockatrice sont [Protocol Buffers](https://developers.google.com/protocol-buffers/) +(protobuf) et [Qt5](https://www.qt.io/). Pour être sûr qu'elles pourraient être compilées, j'ai vérifié qu'il existait +déjà des paquets Arm C'était le cas, je les ai donc téléchargé pour les cross-compiler elles-aussi, +en ayant besoin et ne trouvant pas d'archive toute faite. + + +## Protocol Buffers + +En suivant les [instructions de compilation](https://github.com/protocolbuffers/protobuf/tree/master/src) +pour protobuf il s'est avéré que le projet ne dépendait pas d'autres bibliothèques et avait un système +de compilation relativement classique (proposant même un CMake), j'ai donc décidé de commencer par là. + + +### CMake + +Étant plus familier avec CMake j'ai décidé de regarder de ce côté, malgré le README de cette partie +notant explicitement qu'il était présent pour Windows... + +> This directory contains CMake files that can be used to build protobuf with MSVC on Windows. You can build the project from Command Prompt and using an Visual Studio IDE. + +Mon intuition était que j'aurais principalement à changer les variables indiquant à CMake quel compilateur +utiliser pour qu'il choisisse celui avec le préfixe `aarch64-linux-gnu-` plutôt que le natif. La +[documentation CMake](https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html#cross-compiling) +a confirmé ce sentiment en détaillant d'autres opérations nécessaires pour que la compilation se passe +bien, comme ne sélectionner que les bibliothèques correspondant à la cible. La documentation présente +un exemple de fichier de toolchain, mais plutôt limité. Au cas où, j'ai utilisé le fichier proposé par +[ce blog](https://kubasejdak.com/how-to-cross-compile-for-embedded-with-cmake-like-a-champ#ready-to-use-toolchain-file) +en remplaçant les `${BAREMETAL_ARM_TOOLCHAIN_PATH}arm-none-eabi-gcc${CMAKE_EXECUTABLE_SUFFIX}` +par simplement `aarch64-linux-gnu-gcc` (pour l'exemple de GCC) et en commentant toutes les options de +compilation, en les gardant au cas où j'en aurais besoin. (Le blog explique de façon claire les raisons +des différentes options et l'alternative au fichier de toolchain) + +À ma grande surprise, CMake était bien content de préparer la compilation avec mon cross-compilateur ! + +Mais le compilateur, lui, n'était pas de cet avis. Comme dit précédemment, le CMake de Protocol Buffer +est principalement destiné à compiler sous Windows et manquait probablement des configurations nécessaires +à Linux : la compilation ne fonctionnait pas. Après quelques recherches infructueuses, j'ai enfin suivi la +méthode décrite dans le [README](https://github.com/protocolbuffers/protobuf/blob/master/cmake/README.md) +avec autotools. + + +### Autotools + +Contrairement à CMake, je n'ai jamais écrit de écrit de projet avec autotools ni configuré au delà de +ce qui est décrit dans des instructions d'installation, je n'avais donc pas de point de départ. + +Après un peu de recherche un commentaire sur Stack Overflow m'a pointé vers [cette réponse](https://stackoverflow.com/a/21992287) +détaillant les différentes options et leur sens. Cependant le point de vue de la réponse est un peu perturbant, +et j'ai donc pensé qu'il me fallait utiliser l'option `--target` : ma machine devant être l'hôte et +l'architecture Arm la cible. Cependant, dans les logs du script de configuration j'ai noté qu'il allait +utiliser le compilateur natif. [Une autre réponse](https://stackoverflow.com/a/41755131) plus bas m'a +pointé dans la bonne direction pour comprendre et le faire fonctionner. + +En effet, il semblerait qu'autotools se place du point de vue d'un compilateur. L'option `--build` +permettant de spécifier la machine sur laquelle la compilation *du projet* se fait, `--host` la machine +sur laquelle le compilateur produit sur la machine `--build` sera utilisé et `--target` la machine +sur laquelle les produits du compilateur seront exécutés. +Dans un cas général, `--target` n'a pas l'air d'être très utile. + +Quelques exemples pour la clarté : + - `build=host=target=x86` : sur une machine x86, compiler une toolchain qui va produire de l'x86 depuis une machine x86 elle aussi; + - `build=host=x86, target=arm` : sur une machine x86, compiler une toolchain qui va produire de l'Arm depuis une machine x86. C'est comme ça que l'on produit le cross-compilateur `aarch64-linux-gnu-gcc` par exemple; + - `build=x86, host=target=arm` : sur une machine x86, compiler une toolchain qui va produire de l'Arm depuis une machine Arm. Par exemple, clang pour AArch64 compilé depuis une machine x86. + +Ici nous sommes dans le troisième cas et non pas le deuxième vu que l'on veut que Protocol Buffers +tourne sous aarch64. J'ai donc spécifié `--host=aarch64-linux-gnu` ainsi qu'un préfixe d'installation +avec `--prefix` afin qu'il ne soit pas installé ailleurs dans mon système, remplaçant potentiellement +la version déjà installée. + +Un `make` et quelques minutes plus tard... Succès ! + +Une dépendance de compilée avec succès, un bon présage pour la cross-compilation et la moitié du travail +de fait, passons donc à Qt ! + + +## Qt + +La documentation de Qt était plus... Tortueuse à naviguer. Dans un premier temps la page sur +la [compilation pour Linux](https://doc.qt.io/qt-5/linux-building.html) a été utile, cependant celle +du wiki sur [compiler depuis le dépôt Git](https://wiki.qt.io/Building_Qt_5_from_Git) s'est avéré la +plus pertinente, listant les dépendances et options utiles. + +Il semblerait que Qt utilise aussi autotools mais, en regardant les options disponibles, un peu modifié. +Typiquement pour la cross-compilation, les options précédentes `--host` et compagnie n'étaient pas +disponibles En cherchant sur Stack Overflow et [la documentation Qt](https://doc.qt.io/qt-5/configure-options.html), +j'ai trouvé que l'option `-xplatform` (le `x` étant probablement pour "cross") était celle dont j'avais +besoin. Mais, en mettant le préfixe de mon compilateur... + +``` +ERROR: Invalid target platform 'aarch64-linux-gnu'. +``` + +Donc en plus d'avoir des options différentes, elles ont un sens différent ! En creusant un peu plus, +j'ai trouvé que Qt a une liste précise des plateformes supportées et des toolchains utilisées et demande +exactement ces plateformes. Elles se trouvent sous `qtbase/mkspecs` et consistent principalement de deux +fichiers : un pour définir les outils utilisés (`qmake.conf`), un autre pour des +définitions/configurations (`qplatformdefs.h`). + +Je pense que pour des cas pas trop éloignés il serait possible de copier une configuration existante et +l'adapter, cependant je savais que Qt supportait aarch64 donc j'ai préféré chercher de ce côté. + +Initialement j'avais récupéré la branche 5.5, le minimum demandé par Cockatrice, et n'avais aucune +plateforme aarch64 disponible Ayant réussi à compiler avec Qt 5.15 disponible sur ma machine, j'ai +avancé jusqu'à la dernière LTS Open Source de Qt, la 5.12, et y ai trouvé une plateforme adéquate. + +Avec `-xplatform linux-aarch64-gnu-g++` j'ai pu configurer correctement Qt ! Mais la compilation a bloqué +immédiatement vu que des dépendances pour aarch64 n'étaient pas disponible La page précédente du wiki +ainsi que celle des [bibliothèques requises pour Linux](https://doc.qt.io/qt-5/linux-requirements.html) +donnent les bibliothèques que je devrais installer dans le préfixe aarch64 pour que Qt puisse les trouver +et compiler. Mais... Qt est un projet assez énorme, comptant de nombreuses dépendances. Bien trop nombreuses +pour que je les télécharges et installe à la main; nécessaire vu que Arch Linux ne propose que des paquets +x86_64/x86. + +Cross-compiler Qt ne semblait pas être la meilleure idée, j'ai décidé d'abandonner la cross-compilation +et de porter Cockatrice autrement. + + +# Compilation native + +Il me restait donc comme option compiler directement depuis Arm pour Arm. La solution la plus simple +serait de le faire sur du matériel Arm 64 bits, comme une Raspberry Pi (3/4). +Par chance, j'en possède une ! L'autre solution serait d'émuler un processeur aarch64 sur mon ordinateur +x86. Ce serait plus lent et plus complexe qu'avec ma Raspberry Pi, nécessitant en plus de faire +fonctionner un environnement graphique le tout en émulant une architecture différente... + + +## Émulation +### QEMU + +C'est donc la solution que j'ai choisi ! Les justifications étant que ce serait plus enrichissant, et que +je n'ai pas prévu d'installer une interface graphique dessus pour le moment. + +La façon la plus simple d'émuler un système aarch64 est d'utiliser [QEMU](https://qemu.org), un +émulateur Open Source générique permettant de créer des machines virtuelles émulant moult processeurs +et architectures. La page sur [l'émulation Arm](https://qemu.readthedocs.io/en/latest/system/target-arm.html) +présente les détails de base sur ce qui est nécessaire, en particulier quelle machine émuler. Le meilleur +choix pour une machine générique dont j'ai besoin est la machine "[virt](https://qemu.readthedocs.io/en/latest/system/arm/virt.html)". + +La page du wiki Arch Linux sur [QEMU](https://wiki.archlinux.org/title/QEMU#Creating_new_virtualized_system) +s'est prouvée très utile pour préparer la machine virtuelle et comprendre le fonctionnement de base. + + +### Machine virtuelle et compilation + +J'ai choisi d'utiliser [Arch Linux Arm](https://archlinuxarm.org/), afin de garder la même logique pour +les paquets que sur ma propre machine et pour avoir une image de taille réduite. (Alors que je vais +installer un gestionnaire de bureau...) +Cependant pour une installation générique la page d'insutrctions est plutôt limitée. Mon premier essai n'a +donc absolument pas réussi. Par chance, j'ai trouvé une [liste d'instruction](https://gist.github.com/thalamus/561d028ff5b66310fac1224f3d023c12) +originellement produite pour les nouveaux Mac M1 réalisant *exactement* ce que je voulais ! +(À l'émulation graphique près) Le suivre s'est avéré sans peine, et j'ai pu observer le boot se passer +avec succès ! + +![boot sans console série](/images/Attempting-an-aarch64-port/nographics_boot.png) + +La procédure standard s'appliquait donc : installer les outils de compilation, les différentes dépendances +(dont Qt, pas besoin de le compiler ici) et *enfin* lancer la compilation de Cockatrice ! + +Et d'attendre. + +Un certain temps. + +En effet comme évoqué plus haut, émuler une architecture différente ralenti pas mal les choses. Par dessus +cela, je n'ai alloué que deux cœurs de mon ordinateur à QEMU afin de pouvoir continuer à l'utiliser +en parallèle. + +Au bout de deux heures, le méfait était accompli : Cockatrice était compilé ! Mais sans interface graphique, +impossible de tester le fonctionnement. + + +### Interface graphique + +En soit, installer et lancer une interface graphique n'est pas le plus compliqué. Le plus souvent, +il suffit d'installer le paquet adapté pour un gestionnaire de bureau et tout se fait automatiquement. +Non, ici le problème était autre : comment émuler une interface graphique avec QEMU ? On notera que dans +la commande proposée par les instructions, on trouve l'option `-no-graphics`. Un bon premier pas : +l'enlever ! + +![boot console graphique](/images/Attempting-an-aarch64-port/Graphical_login.png) + +Ok, on a donc une fenêtre QEMU et plus juste un terminal, fantastique. Maintenant, il fallait la transformer +en un écran pour Linux. Pour cela rien de plus simple d'après la documentation, rajouter une carte graphique +virtuelle ! La documentation de QEMU recommande `-device virtio-gpu-pci`, avec lequel j'ai pu en effet +arriver à un TTY normal, comme si j'avais connecté un écran à une machine. Parfait ! + +Moins parfait : le clavier ne répond pas. Dans les options on trouve `-device virtio-keyboard-device`, +visiblement on utiliserait un clavier virtuel, ce qui peut expliquer que notre clavier réel n'ai pas +beaucoup d'effet. + +Après quelques recherches, le meilleur moyen semblait de passer les périphériques USB directement à la +machine virtuelle. Pour une machine Arm, il faut préciser `-usb -device usb-ehci` pour activer l'USB. +Ensuite, il y a plusieurs possibilités pour autoriser des périphériques particuliers sur la machine +virtuelle. La première que j'ai essayé est avec le numéro de bus et de périphérique, mais j'ai fini par +utiliser les identifiant de vendeur et de produit directement, comme montré sur la [page USB](https://qemu.readthedocs.io/en/latest/system/usb.html) +de la documentation de QEMU. Par exemple pour mon clavier : +`-device usb-host,vendorid=0x413c,productid=0x2010`. +Je suis resté sur ce point pendant bien trop longtemps, sans clavier fonctionnel, à tester différentes +cartes graphiques virtuelles et autres manipulations. Au final, il me manquait le rajout du périphérique +usb lui-même : `-device usb-kbd`. Il semblerait que la première option ne fasse qu'exposer le clavier à +la machine virtuelle, alors que la deuxième permette au système d'exploitation virtuel de le considérer +correctement, le "brancher". +Pour la souris, la meilleure option semble être `-device usb-tablet` : elle permet de prendre la souris +en compte comme un pointeur `x,y` et non pas comme une souris normale, rapportant des déplacements. Vu +que notre machine virtuelle est bien plus lente que nos déplacements de souris, il est facile de les +accumuler sans faire exprès et se retrouver avec des déplacements ératiques. En "mode tablette", le curseur +virtuel se positionnera simplement au même endroit que le curseur réel. + +Ne restait plus alors qu'à installer et configurer un gestionnaire de bureau pour avoir une interface +graphique. J'ai choisi KDE, sachant que de nos jours il est plutôt léger tout en étant un des plus +commun. Je ne me faisais donc pas de soucis sur l'installation et la configuration de base. + +Pour qu'il soit lancé correctement, j'ai du modifier le `/etc/X11/xinit/xinitrc` pour lancer KDE au +démarrage de X11, et modifier `/etc/profile` afin que `xinit` soit appelé automatiquement. Le tout, +encore une fois à l'aide du splendide [Wiki Arch Linux](https://wiki.archlinux.org/title/Xinit). + +![KDE QEMU desktop](/images/Attempting-an-aarch64-port/KDE.png) + +Une fois cela fait et finalement en possession d'un bureau émulé pour Arm, je pouvais enfin tester +si la compilation de Cockatrice avait fonctionné... + +# Conclusion +## Résultat + +![Cockatrice sous Arm émulé](/images/Attempting-an-aarch64-port/Cockatrice_Arm.png) + +Et elle avait fonctionné ! Le lancement s'est effectué comme après la compilation pour x86, a suivi les +même étapes, téléchargé et configuré les mêmes éléments. Le chargement de deck fonctionnait, j'ai même +pu rapidement tester que la connexion à un serveur ainsi que la partie "jeu" en elle-même étaient +fonctionnelles avec l'aide d'un ami habitué au logiciel. La compilation a donc été un succès ! + +## Réalisation + +Après ce succès, je suis retourner explorer le dépôt Github de Cockatrice et franchement je ne sais plus +pourquoi. Peut-être même pour retrouver des liens à mettre dans ce poste. Toujours est-il que, caché sous +quelques niveaux de navigation, j'ai découvert une page expliquant... [Comment compiler sur une +Raspberry Pi](https://github.com/Cockatrice/Cockatrice/wiki/Compiling-Cockatrice-(Linux)#raspberry-pi) ! +Ah ! Et bien, oui, en effet : les chances que je rencontre un problème étaient maigres... + +## Rétrospective et continuation + +Malgré cette "boutade", l'expérience entière a été très enrichissante et intéressante Quelques moments de +frustration, mais au final très satisfait d'avoir réussi le challenge initial que de compiler une +application non-triviale pour Arm depuis ma machine x86, même si elle le supportait déjà, sans paquet +officiel. J'ai maintenant une meilleure connaissance des options disponibles ainsi que de l'utilisation +de QEMU, ce qui peut être utile dans de nombreuses situations ! + +Je sais aussi que si je devais le refaire, j'allouerai plus de cœurs à la compilation dans QEMU et +laisserai faire en allant m'occuper ailleurs, afin de minimiser la durée. + +Il y a cependant deux choses que j'aimerais encore faire : + - Essayer d'intégrer la compilation AArch64 à l'intégration continue (CI) de Cockatrice + - Réaliser un vrai portage demandant du travail. C'est une expérience que j'envisage enrichissante et +utile d'un point de vue pratique de la programmation. + +Mais tout ceci est encore à venir ! + + diff --git a/static/images/Attempting-an-aarch64-port/Cockatrice_Arm.png b/static/images/Attempting-an-aarch64-port/Cockatrice_Arm.png new file mode 100644 index 0000000..a5da840 Binary files /dev/null and b/static/images/Attempting-an-aarch64-port/Cockatrice_Arm.png differ diff --git a/static/images/Attempting-an-aarch64-port/Graphical_login.png b/static/images/Attempting-an-aarch64-port/Graphical_login.png new file mode 100644 index 0000000..6293010 Binary files /dev/null and b/static/images/Attempting-an-aarch64-port/Graphical_login.png differ diff --git a/static/images/Attempting-an-aarch64-port/KDE.png b/static/images/Attempting-an-aarch64-port/KDE.png new file mode 100644 index 0000000..f1b983b Binary files /dev/null and b/static/images/Attempting-an-aarch64-port/KDE.png differ diff --git a/static/images/Attempting-an-aarch64-port/nographics_boot.png b/static/images/Attempting-an-aarch64-port/nographics_boot.png new file mode 100644 index 0000000..2f5a745 Binary files /dev/null and b/static/images/Attempting-an-aarch64-port/nographics_boot.png differ