Les dessous d'Android

Magazine
Marque
GNU/Linux Magazine
Numéro
112
|
Mois de parution
janvier 2009
|


Résumé
Dernier-né de chez Google, Android est une nouvelle plateforme libre pour téléphones mobiles, destinée avant tout à être programmée en Java. Il s'agit pourtant d'un système basé sur Linux, qui peut donc être utilisé comme tel. Cet article vous propose de visiter les coulisses d'Android pour comprendre comment la plateforme fonctionne, et, surtout, pour apprendre à la modifier et à la transformer en un véritable système embarqué Linux, avec un shell, des utilitaires et des programmes en C.

Body

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 Setup a target ->Create a new target. Choisissez un nom, comme android, puis sélectionnez le compilateur arm-linux. Sélectionnez cputransp dans le menu suivant, puis qemu-arm-0.8.2-sb2. À la question Do you wish to extract a rootstrap on the target?, répondez No. Répondez par contre Yes à la question Do you wish to install files to the target?. 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 Busybox Settings -> Build Options, choisissez l'option build a static binary. 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 :

001

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


Par le même auteur

Enquête dans les méandres du système… ou l'aventure humaine de la chasse aux bugs

Magazine
Marque
GNU/Linux Magazine
Numéro
216
|
Mois de parution
juin 2018
|
Résumé
Chronique d'une investigation, en compagnie d'un détective des temps modernes, sans gabardine ni revolver, armé d'un simple clavier. Explorez avec lui les coulisses du système GNU/Linux, celles que l'on oublie volontiers lorsque tout marche bien.Lui c'est moi. C'est vous. C'est tous ceux qui ont si souvent passé de longues heures seuls devant la lumière blafarde de leur écran, sur la piste évanescente d'un bug coriace et sournois.Cet article leur est dédié.

Les dessous d'Android

Magazine
Marque
GNU/Linux Magazine
Numéro
112
|
Mois de parution
janvier 2009
|
Résumé
Dernier-né de chez Google, Android est une nouvelle plateforme libre pour téléphones mobiles, destinée avant tout à être programmée en Java. Il s'agit pourtant d'un système basé sur Linux, qui peut donc être utilisé comme tel. Cet article vous propose de visiter les coulisses d'Android pour comprendre comment la plateforme fonctionne, et, surtout, pour apprendre à la modifier et à la transformer en un véritable système embarqué Linux, avec un shell, des utilitaires et des programmes en C.