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
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.
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:
On clique sur New… pour créer un nouvel AVD et on se fait servir un dialogue de création:
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:
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
où 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.