1. Introduction et prise en main
1.1 Android, qui est-ce ?
Google définit Android comme une plateforme pour systèmes mobiles, allant du système d'exploitation aux applications. Android contient donc différentes couches :
- un noyau Linux, patché par Google notamment, pour la gestion de l'énergie et les IPC ;
- un ensemble de bibliothèques, dont une bibliothèque C (bionic), SQLite, WebKit, des bibliothèques de rendu graphique, etc. ;
- une machine virtuelle Java, la Dalvik Virtual Machine ;
- des applications système, telles que des gestionnaires de fenêtres, de paquets ou du système de téléphonie ;
- des applications utilisateur : calendrier, navigateur web, etc.
Android est livré avec plusieurs outils à destination des développeurs. Tout d'abord un émulateur, basé sur QEMU, qui simule une architecture (system-on-chip) avec un processeur ARM appelée « Goldfish ». L'émulateur permet de tester et de déboguer les programmes sur l'architecture cible (ARM). Ensuite, des plugins Eclipse et une documentation en ligne bien fournie permettent la création d'applications Java. Android est en effet conçu pour être exclusivement programmé dans ce langage, au point qu'on peut lire dans la FAQ :
- Est-il possible d'écrire du code pour Android en C/C++ ?
- Pour le moment, il est seulement possible d'utiliser le langage Java avec Android.
Nous allons tenter de donner tort à cette réponse.
1.2 Pourquoi Android ?
Outre l'attrait bien normal que suscite auprès des « hackers » une nouvelle plateforme libre dans le domaine de l'embarqué, qui plus est venant de Google, Android présente en soi plusieurs avantages. Tout d'abord, il s'agit d'une plateforme logicielle. Grâce à l'émulateur, sa prise en main ne nécessite pas d'acheter et de configurer du matériel. C'est donc l'outil idéal pour une initiation à l'informatique embarquée, puisque le coût initial est nul, et qu'Android est simple d'utilisation. Ensuite, Android a vocation à être utilisé et porté sur de nombreux téléphones. Il ne s'agit donc pas d'une plateforme « jouet ».
1.3 Installation du SDK
Le SDK contient l'émulateur et quelques outils de debug. L'installation du SDK consiste à télécharger le fichier zip sur le site Android, et à l'extraire dans un répertoire que nous appellerons android_dir. Il faut ensuite mettre à jour la variable $PATH pour pointer vers android_dir/tools.
1.4 Démarrer Android
Une fois l'installation terminée, on peut lancer l'émulateur avec la commande emulator. Un téléphone devrait s'afficher à l'écran, présentant son écran d'accueil. Rien ne valant la bonne vieille ligne de commande, nous n'allons pas nous contenter de cette interface graphique ! Dans une console, lancez la commande adb shell pour obtenir un shell sur le téléphone.
Au bout de quelques minutes « d'exploration », les habitués de GNU/Linux se rendront probablement compte que ce système est un peu déroutant. En effet, Android, bien que basé sur le noyau Linux, ne possède pas les outils auxquels nous sommes habitués. Le shell, par exemple, est extrêmement sommaire, et beaucoup d'utilitaires « classiques » sont aux abonnés absents (cp, grep, find...). De plus, la structure du système de fichier est pour le moins étrange : un dossier /system/bin au lieu de /bin, pas de dossier /home, /etc est un lien symbolique vers /system/etc...
Sur certains systèmes, l'émulateur se plaint au lancement de ne pas pouvoir créer le fichier /home/user/.android/SDK-1.0. Il faut dans ce cas le créer à la main.
1.5 Le système de fichier
Le système de fichier d'Android est mappé sur trois périphériques, qui sont en fait des images disque. L'image ramdisk.img est montée à la racine au format rootfs. Le répertoire /system correspond sur l'image system.img, et contient principalement les binaires et les fichiers de configuration. Enfin, le répertoire /data correspond à l'image ~/.android/SDK-1.0/userdata-qemu.img. Seul ce répertoire est accessible en écriture. De plus, les modifications de cette image sont persistantes entre les reboots : c'est donc là que l'utilisateur peut stocker ses données personnelles. Pour ces deux dernières images, le système de fichier est YAFFS2, un système adapté aux mémoires flash.
2. Installer un shell
Pour pouvoir utiliser Android correctement en tant que système embarqué Linux, il va nous falloir installer un shell et tout un ensemble d'utilitaires. La façon Unix de procéder étant souvent de compiler les programmes à partir des sources, cela veut donc dire qu'il va nous falloir compiler des programmes en C pour Android.
2.1 Compilation croisée
Android étant destiné à tourner sur un processeur ARM, nous voici confronté à une problématique bien connue des développeurs dans le monde de l'embarqué : la compilation croisée (cross-compilation). On parle de compilation croisée lorsque le système sur lequel tourne le compilateur (dans notre cas, probablement un processeur Intel ou AMD) n'est pas le même que celui pour lequel sont produits les binaires (il s'agit ici d'un processeur ARM).
Pour créer des binaires ARM, nous allons devoir utiliser une chaîne de compilation croisée (cross-compilation toolchain). Cet outil propose un compilateur fonctionnant en mode « croisé », ainsi qu'un ensemble d'outils (linker, assembler) et des bibliothèques compilées pour l'architecture cible (notamment une bibliothèque C). La chaîne de compilation croisée permet également d'isoler son environnement de compilation par rapport au système hôte, pour être sûr, par exemple, que le compilateur va bien utiliser la bibliothèque C « croisée », et non celle du système.
2.2 Installation de Scratchbox
La chaîne de compilation croisée Scratchbox répond à nos besoins. Elle s'installe sous forme de paquet Debian ou en téléchargeant sur ce site les archives suivantes :
- core-1.0.10
- devkit-cputransp-1.0.7
- libs-1.0.10
- toolchain-arm-linux-2007q1-21-1-07
On décompresse chacune des archives en tant que root :
tar zxf archive.tar.gz -C /
Il faut ensuite configurer Scratchbox et ajouter un utilisateur (le vôtre, par exemple):
sudo /scratchbox/run_me_first.sh
sudo /scratchbox/sbin/sbox_adduser utilisateur
Vérifiez que l'utilisateur ajouté appartient bien désormais au groupe sbox, avec la commande id (redémarrez votre interface graphique au besoin). Cet utilisateur peut maintenant lancer l'environnement de compilation en tapant /scratchbox/login. Une fois dans l'environnement, il faut configurer la cible de la compilation croisée. Tapez sb-menu, puis choisissez . Choisissez un nom, comme android, puis sélectionnez le compilateur . Sélectionnez dans le menu suivant, puis . À la question , répondez . Répondez par contre à la question . Sélectionnez la bibliothèque C, /etc, devkits et fakeroot [remplacer la description par une/des image(s) ?].
Si vous quittez l'environnement Scratchox, désactivez-le avec sudo /scratchbox/sbin/sbox_umount_all. Pour le réactiver : sudo /scratchbox/sbin/sbox_ctl start.
2.3 Compilation d'un fichier C
Pour essayer notre chaîne de compilation, nous allons écrire le classique "Hello,world", le compiler, et l'essayer sous Android. À l'intérieur de l'environnement Scratchbox, créez le fichier suivant avec votre éditeur favori.
#include
main() {
printf("Hello, Android\n");
}
gcc -static hello.c -o hello
Placez ensuite le binaire dans le répertoire /data de l'émulateur. Lancez l'émulateur, puis tapez adb push hello /data/. Enfin, tapez adb shell /data/hello pour tester le programme.
N'oubliez pas l'option -static lors de la compilation. En effet, la libc d'Android (bionic) est incompatible avec la libc GNU. GCC ne sait donc pas, par défaut, générer du code qui se liera dynamiquement avec elle.
2.4 Busybox
Souvent mentionné dans ces pages dès qu'il s'agit de systèmes embarqués, Busybox est un véritable couteau suisse. Afin de factoriser le code au maximum, donc de gagner une place précieuse, Busybox est conçu comme un exécutable polymorphe intégrant un shell (ash), les utilitaires Unix classiques (cat, cp, ls...), et une multitude de programmes, allant du serveur web à une version miniature de Vi. Suivant le nom avec lequel le binaire est appelé (argv[0]), Busybox se comportera comme l'un ou l'autre de ces programmes (nommés « applets »). Installer Busybox sur Android nous apportera non seulement un shell, mais tous les outils qui nous manquaient.
Pour l'installer, téléchargez les sources depuis le site Busybox. Placez l'archive dans un répertoire accessible, comme /tmp, pour pouvoir l'utiliser avec Scratchbox. En effet, pour s'isoler du système hôte, l'environnement Scratchbox est chrooté, et n'a donc pas accès à votre répertoire $HOME. Après avoir extrait l'archive, tapez make menuconfig (en dehors de l'environnement Scratchbox). Dans le menu , choisissez l'option . Pour le reste, choisissez les applets qui vous plaisent parmi la longue liste proposée. Enfin, placez-vous dans l'environnement Scratchbox et tapez make pour créer le binaire busybox.
3. Modifier l'image système
Afin d'installer correctement Busybox sur l'émulateur Android, il va nous falloir modifier l'image système pour y placer nos fichiers.
3.1 Récupération du Ramdisk
L'image système se trouve à l'emplacement android_dir/tools/lib/images/ramdisk.img. Copiez-la dans un répertoire vide (par exemple ~/ramdisk) pour pouvoir la manipuler sans risquer d'écraser la version originale. Cette image n'est autre qu'une archive cpio compressée, qu'on extrait ainsi :
gunzip -S.img ramdisk.img
cpio -i -F ramdisk
cpio -t -F ramdisk > ramdisk_list
La dernière commande permet de garder la liste du contenu de l'image, qui nous sera utile pour la reconstruire et la compléter.
3.2 Installation de Busybox
Nous allons installer Busybox dans le répertoire /bin d'Android, créé pour l'occasion. Commençons par créer un répertoire bin dans ~/ramdisk, puis plaçons-y le binaire busybox. Il s'agit ensuite de créer une multitude de liens symboliques. En effet, en tant qu'exécutable « couteau-suisse », Busybox doit être appelé avec le nom de l'applet que l'on souhaite lancer. Pour ce faire, on va créer dans /bin des liens symboliques portant les noms des applets, et pointant vers Busybox. Busybox sait créer ces liens avec l'option --install, mais nous devons malheureusement installer Busybox avant même de pouvoir le lancer. Il va donc falloir faire le travail à la main.
3.2.1 Liste des applets
La première chose à faire est de récupérer la liste des applets qui ont été compilés dans Busybox. Celle-ci est définie dans le fichier include/applet_tables.h du dossier Busybox. Le script suivant permet de l'extraire :
awk -F\" '/applet_names/,/;/{if($2) printf("ln -s busybox %s\n", $2)}' include/applet_tables.h
3.2.2 Création des liens
Une fois la liste récupérée, il faut créer un lien pour chaque applet. La commande awk vue précédemment crée automatiquement la liste des commandes ln adéquates. Il suffit donc de se placer dans ~/ramdisk/bin et de lancer ces commandes.
3.3 Création d'une image modifiée
Pour créer une nouvelle image ramdisk modifiée, nous allons tout d'abord ajouter nos modifications à la liste des fichiers à inclure dans l'image. Puis, nous allons recréer une archive cpio à partir de cette liste et la compresser.
find bin >> ramdisk_list
cat ramdisk_list | cpio -o -H newc -O mydisk
gzip -S.img mydisk
3.4 Test des modifications
On lance cette fois-ci l'émulateur en précisant l'image à utiliser :
emulator -ramdisk ~/ramdisk/mydisk.img
Puis, on s'y connecte et on met à jour la variable $PATH, pour pouvoir utiliser Busybox :
adb shell
export PATH=/bin:$PATH
4. Modification du boot et ajout du login
Une fois Busybox installé, Android est déjà beaucoup plus « confortable » en ligne de commande. Nous allons améliorer encore notre environnement en étoffant le système de fichiers, et en ajoutant un véritable mécanisme de login. Pour ce faire, il va nous falloir modifier le processus de démarrage d'Android.
4.1 Login
Le fichier /etc/passwd n'existant pas sous Android, il n'y a pas de mécanisme de login au sens traditionnel Unix. Android étant prévu pour être programmé en Java, c'est l'environnement Java qui est chargé de gérer les permissions et les droits avec un mécanisme qui lui est propre – bien que basé en partie sur les permissions Unix. Nous allons donc devoir ajouter les fichiers /etc/passwd et /etc/group à notre image système.
Le problème qui se pose est que, sous Android, /etc est un lien symbolique vers /system/etc. Or /system est monté sur une image qui n'est pas l'image ramdisk dont nous avons l'habitude. Cette image n'est pas une archive cpio, et il est beaucoup moins facile de la modifier. Nous allons donc devoir contourner cette difficulté en créant, nous aussi, des liens symboliques.
4.2 Un nouvel /etc
Choisissons par exemple de placer nos fichiers dans /usr/local/etc. Il faut donc créer ce répertoire dans ~/ramdisk, créer les fichier passwd et group, et ajouter nos modifications à la liste des fichiers de l'image :
mkdir -p usr/local/etc
echo "root::0:0:root:/home:/bin/ash" > usr/local/etc/passwd
echo "root::0:" > usr/local/etc/group
find usr >> ramdisk_list
4.3 Profile
L'avantage de cette procédure standard de login, c'est que l'on va pouvoir disposer d'un « login shell », avec un fichier profile contenant nos préférences. Au démarrage, le shell Ash de Busybox lit les fichiers /etc/profile et le fichier .profile contenu dans le répertoire personnel de l'utilisateur. Nous allons donc créer le fichier usr/local/etc/profile :
export ANDROID_ROOT=/system
export LD_LIBRARY_PATH=/system/lib
export PATH=/bin:/sbin:/system/sbin:/system/bin:/system/xbin
export BOOTCLASSPATH=/system/framework/core.jar:/system/framework/ext.jar:/system/framework/framework.jar:/system/framework/android.policy.jar:/system/framework/services.jar
export ANDROID_BOOTLOGO=1
export ANDROID_ASSETS=/system/app
export EXTERNAL_STORAGE=/sdcard
export ANDROID_DATA=/data
export ANDROID_PROPERTY_WORKSPACE=9,32768
Ces valeurs ne sortent pas du chapeau ! Pour la plupart, il s'agit de variables d'environnements définies dans le shell d'Android. Il faut d'ailleurs vérifier que les valeurs sont bien les mêmes (en tapant par exemple adb shell set). Android a besoin de ces variables pour fonctionner : par exemple, l'accès au réseau ne fonctionne pas si ANDROID_PROPERTY_WORKSPACE n'a pas le bon numéro. Vous pouvez par contre définir d'autres variables et alias à votre guise, bien qu'il soit plutôt recommandé d'utiliser le fichier ~/.profile pour cela. N'oubliez pas enfin d'ajouter ce fichier à la liste des fichiers de l'image ramdisk.
4.4 Fichier de démarrage
Au démarrage, Android lance le programme init, qui lit et exécute les commandes du fichier init.rc. Ce fichier est placé à la racine, donc sur l'image ramdisk.img. Il se trouve donc déjà dans le répertoire ~/ramdisk, et nous allons le modifier. Nous allons tout d'abord créer un lien symbolique pour avoir un répertoire /home pointant vers /data/local. Il est important de placer le répertoire /home dans la partition /data, la seule qui soit persistante et accessible en écriture. Nous allons aussi faire un lien /tmp pointant vers /cache. Enfin, il nous faut créer des liens pour ajouter les fichiers passwd, group et profile à /etc. Pour pouvoir faire ces modifications, il faudra d'abord s'assurer que les partitions sont en mode lecture/écriture (rw) au lieu de lecture seule (ro). En effet, même si le mode écriture ne permet pas de faire des modifications durables que sur la partition /data, il permet quand même de créer des liens symboliques le temps d'une session. Dans la suite, les modifications sont en gras :
# Backward compatibility
symlink /system/etc /etc
symlink /data/local /home
symlink /cache /tmp
mount rootfs rootfs / rw remount
mount yaffs2 mtd@system /system rw remount
chmod 0770 /cache/lost+found
symlink /usr/local/etc/passwd /system/etc/passwd
symlink /usr/local/etc/group /system/etc/group
symlink /usr/local/etc/profile /system/etc/profile
on boot
4.5 login: root
Une fois l'image mydisk.img reconstruite, on peut désormais se connecter à Android (avec adb shell) et taper /bin/login. Un joli prompt nous accueille. Il suffit de taper root et nous voilà sur notre shell.
5. Telnet et réseau
Pour pallier certains problèmes inhérents à la commande adb shell, nous allons mettre en place un serveur telnet. Ce sera aussi l'occasion d'aborder la problématique du réseau sous Android.
5.1 Tab et flèches
Adb shell n'est pas la panacée en tant qu'interface en ligne de commande. Sur certains systèmes, la touche [Tab] n'est pas gérée correctement, ce qui empêche la complétion. Sur d'autres, ce sont les flèches qui ne fonctionnent pas, empêchant l'accès à l'historique et aux fonctionnalités « readline ». Nous allons donc nous passer de cette interface et en utiliser une autre bien connue : telnet.
5.2 Telnetd
Nous avons besoin d'un serveur telnet sur Android. Fort heureusement, Busybox dispose de l'applet telnetd (si vous ne l'avez pas compilé, retournez à la case 2.3). Pour lancer ce serveur au démarrage, nous allons créer un service en écrivant cette ligne à la fin du fichier init.rc :
service telnetd /bin/telnetd
Une fois l'image ramdisk reconstruite, le serveur telnet se lance au démarrage et attend sur le port 23 d'Android
5.3 Connexion
Android n'ayant pas d'IP propre (il s'agit d'un émulateur), il faut, pour s'y connecter, mettre en place une redirection. Cela se fait avec la commande suivante :
adb forward tcp:4444 tcp:23
Toute connexion à localhost sur le port 4444 aboutira désormais sur le port 23 d'Android. On accède donc ainsi à notre shell :
telnet localhost 4444