+++
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.

<!-- more -->

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 !

<!-- TODO : Pictures, schémas, update theme to differentiate between update and publish, add Qt config options -->
