Assembleur ARM sur Android 1/3: Mise en place
par Benoit, 2014-03-19

Pour qui désire apprendre la programmation en langage d’assemblage, la plate-forme ARM est une option particulièrement recommandable: cette architecture RISC est relativement simple, la plate-forme est facile d’accès du fait de son vif succès commercial et l’outillage nécessaire à la mise en oeuvre des programmes est mature et bien documenté. Cette série d’articles montre comment on peut utiliser divers éléments de la plate-forme Android pour s’exercer à diverses approches de programmation en langage d’assemblage ARM, facilement et sans aucun coût (sinon un peu de téléchargement), à partir d’une machine Linux.

Dans cette première partie de trois, j’illustre comment mettre en place le SDK et le NDK Android sur l’hôte Linux qui soutient le développement ARM. Je présente aussi l’usage de l’émulateur Android et de l’outil adb pour communiquer avec cet émulateur.

Introduction

Durant mes études sous-graduées en informatique, l’un des cours qui m’ont donné le plus de difficulté a été celui de la programmation système. Alors que j’apprends intuitivement et aisément la programmation dans les langages de haut niveau, la programmation de la machine m’a toujours un peu rebuté, malgré la simplicité apparente du système manipulé. C’est largement pourquoi je considère que, même à l’époque des applications web et de la revanche des langages fonctionnels, il est crucial que les nouveaux développeurs apprennent la programmation en langage d’assemblage. Cette approche procure une perspective différente sur l’exécution d’un programme et permet une meilleure compréhension du travail des compilateurs, des modèles de calcul, du phénomène des crashes, ainsi que des vulnérabilités et des exploits.

Depuis trois ans, j’étudie les failles du système d’exploitation Android, particulièrement de son navigateur Internet et de ses daemons internes, qui roule sur des processeurs ARM. Ayant moi-même appris la programmation de bas niveau sur plate-forme RISC (architecture Sparc), il m’apparaît naturel de recommander la plate-forme ARM pour ce genre d’apprentissage. Cependant, en jetant un bref coup d’oeil sur Internet, la plupart des tutoriels de programmation ARM viennent de la communauté des systèmes embarqués. Une bonne partie de ces tutoriels concerne la mise en oeuvre de matériel relativement coûteux et difficile d’accès, ainsi que de l’implantation de scripts d’édition de liens et de toutes sortes de choses qui digressent du sujet qui m’intéresse: la programmation ARM. J’aimerais quant à moi

  • une simple plate-forme logicielle,
  • gratuite si possible,
  • grâce à laquelle je pourrais assembler et lier des programmes ARM
  • roulant dans l’espace usager de systèmes d’exploitation relativement bien connus,
  • qui pourraient même faire usage des services de ces systèmes.

La réponse est simple: Android. Le SDK (Software Development Kit) Android procure un émulateur très compétent qui roule un système Android (donc, Linux) complet sur plate-forme ARM. En outre, le NDK (Native Development Kit) Android, un supplément au SDK, donne accès à un assembleur ARM et un éditeur de liens qui permettent la conversion d’un code source ARM en un programme exécutable basé sur les services Linux (voire même les routines de glibc). Tout cela est facile à installer sur un hôte Linux conventionnel (ordinateur basé sur plate-forme Intel x86 32 ou 64 bits, physique ou virtuelle) et ne coûte qu’un peu plus de 1 GB de téléchargement.

Cet article est le premier d’une série de trois. Je présente ici la mise en place d’un système de développement ARM/Android sur un hôte x86/Linux. Dans le deuxième article, j’aborderai l’usage des outils de développement du NDK et je présenterai deux modèles de programmation alternatifs contre cette plate-forme. Dans le troisième article, je présenterai un projet d’application client-serveur, dans le but d’illustrer l’usage des fonctionnalités de déboguage à distance de GDB.

Préparation du système Linux

J’ai tout mis en place sur un système Linux basé sur la distribution Ubuntu. J’utilise spécifiquement la distribution Linux Mint 16, basée sur Ubuntu 13.10. Bien que je n’aie pas testé la chose, je suis convaincu que ces instructions vaudront également pour toute distribution basée sur Ubuntu version 12.04 ou ultérieure. Mon système Linux roule sur une machine virtuelle VirtualBox 4.3.8.

À partir de la distribution de base, pour ceux comme moi qui roulent un système Linux 64 bits, il faut installer le paquet standard ia32_libs. Ce paquet permet l’exécution de programmes compilés pour processeurs 32 bits sur une plate-forme 64 bits. Cela est fait simplement depuis la ligne de commande:

$ sudo apt-get install ia32-libs

Note: dans les exemples qui suivront, les lignes de texte précédées d’un signe $ correspondent à des commandes à exécuter depuis le terminal. Les lignes non précédées de ce signe listent la sortie attendue de ce programme, lorsque c’est nécessaire. Je comprends que cette situation empêche le lecteur de copier-coller toutes les instructions de terminal sans discrimination. Je crois que c’est parfait ainsi: la recopie des lignes de commande mène à une réflexion (si brève soit-elle) sur leur signification, ce qui a selon moi une valeur pédagogique.

Installation des outils de développement Android

Nous avons besoin d’installer deux paquets d’outils: le SDK Android et le NDK. Commençons par le SDK. On le télécharge ici, en cliquant l’option Download for other platforms. Descendez tout au bas de la page pour sélectionner, dans le tableau SDK Tools Only, le paquet pour Linux.

Une fois téléchargé, on décompresse ce paquet d’outils et on le place quelque part confortable. J’ai quant à moi le réflexe de mettre tous les programmes à la disposition de tous les usagers de mon système, ensemble qui ne contient que moi. Indulgeons donc mon réflexe inutile, en supposant que l’archive Tar compressée ait été disposée dans le sous-répertoire Downloads du répertoire de l’usager. Notez que le numéro de version de votre archive (chez moi 22.6) pourrait être différent.

$ cd ~/Downloads
$ tar xvfz android-sdk_r22.6-linux.tgz
... Liste de fichiers ...
$ sudo mkdir /opt/android
[sudo] password for hamelin: <haha non je ne le dirai pas!>
$ sudo mv android-sdk-linux /opt/android/sdk
$ sudo chmod -R a+rX /opt/android/sdk

Les outils pertinents à nos besoins du SDK se trouvent dans ses sous-répertoires tools et platform-tools. Je modifie donc mon fichier .bashrc pour les trouver accéder plus facilement, en ajoutant les lignes suivantes à ce fichier:

export ANDROID_SDK=/opt/android/sdk
export PATH=$PATH:$ANDROID_SDK/tools:$ANDROID_SDK/platform-tools

Je quitte mon terminal et je m’en démarre un nouveau pour que ces ajustements de configuration prennent effet. Tant qu’à avoir le navigateur ouvert, téléchargeons le NDK pour l’installer de manière similaire. Une fois le téléchargement complété…

$ cd ~/Downloads
$ tar xvfj android-ndk-r9d-linux-x86_64.tar.bz2
... Une tonne de fichiers! ...
$ sudo mv android-ndk-r9d /opt/android/ndk
[sudo] password for hamelin: <j'ai dit non!>
$ sudo chmod -R a+rX /opt/android/ndk

Encore une fois, le numéro de version inscrit ici (r9d) pourrait être différent pour vous.

Téléchargement d’une plate-forme Android

Le SDK ne vient pas avec un contenu entier. Il offre plutôt un outil permettant de ne télécharger que les plate-formes nécessaires au développement qui nous intéresse, afin d’économiser les données téléchargées et stockées. Ces plate-formes correspondent, grosso modo, aux versions de firmware d’Android, qu’on peut vouloir cibler spécifiquement lors du développement d’applications. La gestion des plate-formes installées est faite via l’outil android, inclus dans les répertoires du SDK ajoutés ci-haut à notre PATH. On le démarre simplement:

$ android &

Cet outil lance une interface graphique permettant de choisir les plate-formes à installer, ainsi que diverses autres composantes. La première fois qu’on démarre cet utilitaire, la plate-forme la plus récente est automatiquement sélectionnée et prête au téléchargement.

Gestionnaire des plate-formes Android

Gestionnaire des plate-formes Android lancé par la commande android.

Il suffit de cliquer le bouton suggérant d’installer des paquets pour que les derniers outils nécessaires soient mis en place, soit l’émulateur du système Android et l’outil de liaison entre l’hôte Linux et cet émulateur. Ce téléchargement peut prendre une heure. Allez voir vos amis!

Construire et démarrer un émulateur

Une fois tout téléchargé, on utilise le gestionnaire d’appareils virtuels (AVD) pour se créer une image d’un système Android qui roulera sur l’émulateur. Ce gestionnaire est ouvert soit via l’option Tools / Manage AVDs… du Android SDK Manager, soit en invoquant, depuis le terminal,

$ android avd &

On obtient la fenêtre suivante:

Gestionnaire des appareils virtuels Android

Gestionnaire des appareils virtuels, vide.

On clique sur New… pour créer un nouvel AVD et on se fait servir un dialogue de création:

Création d'un nouvel AVD

Dialogue de création d’un nouvel AVD.

Le lecteur est bienvenu de remplacer les choix illustrés ci-haut, à ceci près de quelques options:

  • CPU/ABI doit être armeabi-v7a. On peut programmer la plate-forme ARM, pas Intel Atom.
  • Je n’ai pas réussi à faire fonctionner l’ajout d’une carte SD simulée. Si vous avez besoin de plus de capacité de stockage dans le système Android émulé, augmentez plutôt Internal Storage.
  • Si l’hôte Linux est une machine virtuelle, il y a fort à parier que vous ne pourrez pas jouir d’accélération graphique matérielle (option Use Host GPU). Sinon, le Linuxien expérimenté pourra tester la robustesse de son pilote graphique propriétaire… Quoi qu’il en soit, pour un peu d’expérimentation avec la programmation ARM, nous n’utiliserons pas tellement les fonctionnalités graphiques du système Android que nous émulons, alors aucune conséquence à laisser cette option désélectionnée.

On clique OK, la création a lieu et on revient au gestionnaire d’AVD:

Gestionnaire des appareils virtuels Android

Gestionnaire des appareils Android, après la création de notre AVD.

On peut démarrer l’émulateur ainsi créé de deux manières. La première est via ce gestionnaire, en cliquant le bouton Start… après avoir sélectionné l’AVD. La deuxième est via la commande

$ emulator -avd MyNexusOne

MyNexusOne est remplacé par le nom donné à votre AVD. On peut subséquemment simplement fermer la fenêtre de l’émulateur pour terminer son exécution.

Le Android Debug Bridge (adb)

L’émulateur Android simule un appareil Android complètement indépendant de l’hôte Linux qui exécute cette simulation. En ce sens, pour interagir avec cet appareil virtuel, il faut une sorte de pont. C’est l’utilité du Android Debug Bridge, encapsulé dans l’utilitaire adb. On peut jeter un coup d’oeil à la fonctionnalité complète de ce programme en exécutant simplement

$ adb

Il y a beaucoup de commandes! Pour se familiariser avec le système, nous en utiliserons principalement cinq.

Interroger la présence de l’émulateur

Si on veut voir si l’émulateur est prêt à communiquer avec notre hôte Linux, on utilise la commande

$ adb devices
List of devices attached
emulator-5554   device

Lorsqu’on démarre l’émulateur, il faut un moment avant que le démarrage du système soit assez avancé pour que la connexion via ADB soit possible. On reçoit alors la sortie suivante:

$ adb devices
List of devices attached
emulator-5554   offline

Afin de s’assurer que l’émulateur soit prêt avant d’interagir davantage avec l’émulateur, on peut utiliser

$ adb wait-for-device

Cette commande ne se termine qu’une fois que l’émulateur est en ligne et prêt à discuter avec nous.

Transférer des fichiers

Sur un émulateur, l’utilisateur qui se connecte via ADB a tous les droits; il est root. Ceci contraste avec le fait que sur un véritable appareil Android utilisé pour le développement, les droits d’accès à l’appareil sont limités à l’usager shell (UID 2000). Cet usager peut notamment se servir du répertoire /data/local/tmp comme répertoire de travail ou de transit de fichiers, tant et si bien que j’ai pris l’habitude de tout faire dans ce répertoire, même sur un émulateur.

On peut donc utiliser les commandes adb pull et adb push pour, respectivement, télécharger et téléverser des fichiers dans des répertoires auxquels on a accès. Par exemple:

adb push mon_programme /data/local/tmp
# Faire quelques calculs...
adb pull /data/local/tmp/mon_resultat .

Si le second paramètre de ces commandes n’est pas un répertoire existant, le fichier est téléversé/téléchargé à la destination ainsi nommée.

Exécuter des programmes

Finalement, on peut exécuter des programmes sur notre émulateur via la commande adb shell. À elle seule, cette commande roule un terminal interactif distant sur l’émulateur. Dans l’exemple qui suit, je rapporte la sortie telle que l’émulateur me la donne.

$ adb shell
root@generic:/ # id
uid=0(root) gid=0(root) context=u:r:shell:s0
root@generic:/ # pwd
/
root@generic:/ # ls -la
drwxr-xr-x root     root              2014-03-18 17:03 acct
drwxrwx--- system   cache             2014-03-18 17:03 cache
dr-x------ root     root              2014-03-18 17:03 config
lrwxrwxrwx root     root              2014-03-18 17:03 d -> /sys/kernel/debug
drwxrwx--x system   system            2014-03-14 17:05 data
-rw-r--r-- root     root          116 1969-12-31 19:00 default.prop
drwxr-xr-x root     root              2014-03-18 17:03 dev
lrwxrwxrwx root     root              2014-03-18 17:03 etc -> /system/etc
-rw-r--r-- root     root         8870 1969-12-31 19:00 file_contexts
-rw-r----- root     root          953 1969-12-31 19:00 fstab.goldfish
-rwxr-x--- root     root       175260 1969-12-31 19:00 init
-rwxr-x--- root     root          919 1969-12-31 19:00 init.environ.rc
-rwxr-x--- root     root         2979 1969-12-31 19:00 init.goldfish.rc
-rwxr-x--- root     root        19848 1969-12-31 19:00 init.rc
-rwxr-x--- root     root         1795 1969-12-31 19:00 init.trace.rc
-rwxr-x--- root     root         3915 1969-12-31 19:00 init.usb.rc
drwxrwxr-x root     system            2014-03-18 17:03 mnt
dr-xr-xr-x root     root              1969-12-31 19:00 proc
-rw-r--r-- root     root         2161 1969-12-31 19:00 property_contexts
drwx------ root     root              2013-07-09 20:46 root
drwxr-x--- root     root              1969-12-31 19:00 sbin
lrwxrwxrwx root     root              2014-03-18 17:03 sdcard -> /storage/sdcard
-rw-r--r-- root     root          656 1969-12-31 19:00 seapp_contexts
-rw-r--r-- root     root        74768 1969-12-31 19:00 sepolicy
drwxr-x--x root     sdcard_r          2014-03-18 17:03 storage
dr-xr-xr-x root     root              2014-03-18 17:03 sys
drwxr-xr-x root     root              1969-12-31 19:00 system
-rw-r--r-- root     root          272 1969-12-31 19:00 ueventd.goldfish.rc
-rw-r--r-- root     root         4024 1969-12-31 19:00 ueventd.rc
lrwxrwxrwx root     root              2014-03-18 17:03 vendor -> /system/vendor

On peut aussi utiliser adb shell pour lancer un unique programme et en récupérer la sortie. Par exemple:

$ adb shell ls -la /data
-rw------- root     root            2 2014-03-14 17:02 .layout_version
drwxrwxr-x system   system            2014-03-18 17:05 anr
drwxrwx--x system   system            2013-12-05 14:10 app
drwx------ root     root              2014-03-14 17:02 app-asec
drwxrwx--x system   system            2014-03-14 17:03 app-lib
drwxrwx--x system   system            2014-03-14 17:02 app-private
drwx------ system   system            2014-03-14 22:15 backup
lrwxrwxrwx root     root              2014-03-14 17:02 bugreports -> /data/data/com.android.shell/files/bugreports
drwxrwx--x system   system            2014-03-14 17:06 dalvik-cache
drwxrwx--x system   system            2014-03-14 17:03 data
drwxr-x--- root     log               2014-03-14 17:02 dontpanic
drwxrwx--- drm      drm               2014-03-14 17:02 drm
drwxr-x--x root     root              2014-03-14 17:02 local
drwxrwx--- root     root              1969-12-31 19:00 lost+found
drwxrwx--- media_rw media_rw          2014-03-14 17:02 media
drwxrwx--- mediadrm mediadrm          2014-03-14 17:02 mediadrm
drwxrwx--t system   misc              2014-03-14 17:02 misc
drwxrwx--x system   system            2013-12-05 14:07 nativebenchmark
drwxrwx--x system   system            2013-12-05 14:07 nativetest
drwx------ root     root              2014-03-18 17:04 property
drwxrwx--x system   system            2014-03-14 17:02 resource-cache
drwx--x--x system   system            2014-03-14 17:02 security
drwxr-x--- root     shell             2014-03-14 17:02 ssh
drwxrwxr-x system   system            2014-03-19 09:03 system
drwx--x--x system   system            2014-03-14 17:02 user

Pour récupérer la sortie standard dans un fichier sur l’hôte Linux, il faut chaîniser (excusez l’invention du mot) la commande à exécuter sur l’émulateur:

$ adb shell "ls -la /data" > ls_data

Le résultat ci-haut se trouve alors stocké dans le fichier ls_data dans le répertoire courant.

À suivre

Dans le prochain article de cette série, je présenterai deux modèles de programmation en langage d’assemblage ARM utilisables sur ce système, démontrant la mise en oeuvre de chacun de ces modèles à l’aide des outils du NDK.

Commentaires