47
e

année) Julien Névo - download.forge.objectweb.orgdownload.forge.objectweb.org/asm/asmdex-report.pdf · Première partie Introduction Mon traailv au cours de ce stage a consisté

  • Upload
    vanque

  • View
    216

  • Download
    2

Embed Size (px)

Citation preview

2010-2011 Rapport de stage

Orange LabsMAPS/DVC2 avenue Pierre Marzin22307 Lannion

Master Informatique (2e année) Julien Névo

Sous la responsabilité de :

Pierre Crégut, Orange Labs

Olivier Barais, Istic

Adaptation de la bibliothèque ASMpour Dalvik

Stage e�ectué du 7 mars au 6 septembre 2011 à Orange Labs, Lannion.Soutenance à Rennes le 29 août 2011.

[email protected]

[email protected]

[email protected]

ISTIC - UFR Informatique et ÉlectroniqueUniversité de Rennes 1, Campus de Beaulieu

263, avenue Général LeclercCS 74205, 35042 Rennes CEDEX, France

02 23 23 39 00

Résumé

Mon stage en entreprise s'est déroulé du 7 mars au 6 septembre 2011 à Orange Labs à Lannion. Mon travail aconsisté à adapter au bytecode Android la bibliothèque ASM, dont le but est de manipuler du bytecode Java.Cette nouvelle bibliothèque est nommée AsmDex. Bien que la plate-forme Android soit également basée surJava, le bytecode de la machine virtuelle qu'elle utilise a été entièrement repensé. Malgré cette di�érence, il mefallait rester au plus près des interfaces proposées par ASM, a�n d'une part que les développeurs connaissantdéjà ASM puissent trouver facilement leurs marques, mais surtout a�n que les programmes utilisant ASMaient le moins de modi�cations possible à subir pour utiliser AsmDex.

Les deux axes principaux de la bibliothèque ont été adaptés : le premier permet la lecture et productiondu bytecode de manière linéaire, tandis que l'autre le fait à la manière d'un arbre. ASM dispose de plusieursoutils annexes, nous étudierons celui qui a été adapté dans la nouvelle bibliothèque.

Abstract

My internship started on 7 March and ended on 6 September of 2011 at Orange Labs in Lannion. My workconsisted in adapting the library called ASM to the Android bytecode. ASM allows to manipulate the Javabytecode. The new library is called AsmDex. Although the Android platform is also based on Java, thebytecode its virtual machine uses has been fully reorganised. However, in spite of this di�erence, I had tostay as close as possible to the original interface so that developpers experienced with ASM could easilyswitch to AsmDex, but mostly for the programs using ASM not to need modi�cations when working withAsmDex.

The two main axis of the library have been adapted : the �rst allows the reading and the production ofbytecode in a linear way, whereas the other allows to do it as a random, tree-based access. ASM providesseveral tools and we'll study the one I adapted to the new library.

Remerciements

Je tiens à remercier M. Yves-Marie Quémener pour m'avoir accepté au sein de son équipe. Je remercieégalement M. Pierre Crégut pour son aide technique, pour avoir pris le temps de répondre pertinemment àtoutes mes questions, mais aussi pour la relecture et les corrections de ce rapport.

En�n, je remercie l'équipe DVC pour son accueil.

TABLE DES MATIÈRES

Table des matières

I Introduction 1

II Présentation de l'entreprise 2

1 L'entreprise 21.1 Le groupe France Télécom . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2

2 Orange Labs 22.1 Activité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22.2 Organisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32.3 Le laboratoire Devices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3

III Présentation de ASM 4

1 Présentation générale 4

2 La Core API 52.1 Génération d'une classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62.2 Génération de code à partir d'un �chier compilé existant . . . . . . . . . . . . . . . . . . . . . 62.3 Exemple d'utilisation de �ltre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

3 La Tree API 83.1 Génération d'une classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103.2 Génération de code à partir d'un �chier compilé . . . . . . . . . . . . . . . . . . . . . . . . . . 113.3 Exemple de transformation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

4 Les outils annexes 12

IV Présentation de Dalvik 13

1 Présentation de la machine virtuelle Dalvik 131.1 Contexte de Dalvik : Android . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131.2 Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131.3 Les instructions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131.4 Comparaison entre le bytecode de Dalvik et de Java . . . . . . . . . . . . . . . . . . . . . . . . 14

2 Le format dex 152.1 Présentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152.2 Compilation d'un �chier .dex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152.3 Les di�érents éléments à encoder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162.4 Le Constant Pool . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172.5 Les descripteurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192.6 La structure d'un �chier .dex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192.7 Ordonnancement, alignement et unicité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202.8 Les �chiers .odex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21

3 Les �chiers .apk 213.1 Génération d'un �chier .apk . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21

i

TABLE DES MATIÈRES

4 Comparaison entre la JVM et Dalvik 22

V Présentation de AsmDex 23

1 Présentation générale 231.1 Changements par rapport à ASM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 231.2 Réutilisation de l'existant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24

2 Adaptation de la Core API 242.1 Lecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 242.2 Écriture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26

3 Adaptation de la Tree API 31

4 Création de l'outil AsmDexi�er 324.1 Présentation de AsmDexi�er . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 324.2 Implémentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32

5 Tests 335.1 Le test de l'ApplicationReader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 335.2 Le test de l'ApplicationWriter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 345.3 Le test des Adapter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 345.4 Le test de l'AsmDexifier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34

6 Performances 356.1 Comparaison entre les Reader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 356.2 Comparaison entre les Writer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 356.3 Comparaison entre le Reader et le Writer d'ASM et d'AsmDex . . . . . . . . . . . . . . . . 356.4 Core API vs Tree API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 366.5 Utilisation du court-circuit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 366.6 Conclusion sur les performances . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36

7 L'avenir d'AsmDex 36

VI Conclusion 37

VII Annexes 38

1 Patron de conception � Visiteur � 38

2 Exemple d'utilisation de AsmDexi�er 38

3 Diagramme de classes de ASM 41

4 Organisation du test de l'ApplicationReader 42

ii

Première partie

Introduction

Mon travail au cours de ce stage a consisté à adapter la bibliothèque ASM pour le système d'exploitationAndroid. ASM est à l'origine prévue pour manipuler du bytecode Java. Il existe déjà plusieurs bibliothèquessimilaires, mais ASM dispose de nombreux atouts, comme sa rapidité, sa faible consommation en mémoire etsa simplicité d'utilisation. Orange Labs souhaite pouvoir manipuler le bytecode Android de leurs applicationsde manière e�cace. L'entreprise a logiquement porté son choix sur une adaptation d'ASM pour le bytecodede Dalvik, la machine virtuelle des appareils utilisant Android.

La nouvelle bibliothèque, baptisée AsmDex, devra réutiliser les mêmes interfaces que la bibliothèquedont elle est issue et si possible en garder tous les avantages. On veillera également à réutiliser son code aumaximum.

Je vous présenterai d'abord ASM, à quoi la bibliothèque peut servir, son architecture et comment l'utiliser.Ensuite, après une brève présentation du système d'exploitation Android, je vous présenterai la machine

virtuelle Dalvik et la manière dont elle stocke et gère ses données. Nous étudierons alors le format dex quiregroupe tout le bytecode que la machine virtuelle devra exécuter.

Puis nous étudierons en détail AsmDex, la bibliothèque que j'ai développée, sous ses deux aspects prin-cipaux : d'une part, la génération de bytecode de manière linéaire, d'autre part grâce à un parcours d'arbre.Je parlerai des principales di�cultés techniques que j'ai rencontrées, des di�érentes solutions envisagées etde celle que j'ai �nalement choisie. Une dernière partie concernera AsmDexi�er, l'un des outils dont disposeASM et qui est le seul qu'Orange Labs avait besoin de retrouver dans AsmDex.

En�n, je vous parlerai de l'élaboration des tests de notre nouvelle bibliothèque et établirai une comparaisonentre ses performances et celles d'ASM.

1

2 ORANGE LABS

Deuxième partie

Présentation de l'entreprise

1 L'entreprise

1.1 Le groupe France Télécom

1.1.1 Historique du groupe

France Télécom est l'opérateur historique en France. Descendant des P&T puis des PTT, l'entreprise sesépare de l'état en 1990 tout en restant nationalisée. Il faut attendre 1996 pour que France Télécom devienneune société anonyme. En 1997 une partie du capital de France Télécom est ouverte aux investisseurs et depuis2004, l'état n'est plus actionnaire majoritaire. France Télécom est devenue une entreprise privée compétitiveet leader sur ses principaux segments de marché, autour d'Orange, la marque phare de France Télécom.

Fig. 1 � Implantation de France Télécom dans le monde

1.1.2 Activités du groupe

France Télécom est présent dans 32 pays et territoires, emploie près de 200 000 salariés et comptabilise 216millions de clients dans le monde au 31 mars 2011. Le chi�re d'a�aires réalisé au premier trimestre 2011 s'élèveà 11,2 milliards d'euros. Orange est l'un des principaux opérateurs européens du mobile et l'accès internetADSL et l'un des leaders mondiaux des services de télécommunications aux entreprises multinationales, sousla marque Orange Business Services.

2 Orange Labs

2.1 Activité

Orange Labs est le moteur de l'innovation du groupe France Télécom et de ses �liales, en France et à l'étranger.Ses laboratoires sont à l'origine d'environ 80% des produits et services commercialisés par le Groupe. Avecses 3 700 ingénieurs, scienti�ques et chercheurs, ses implantations sur 4 continents et des investissementsimportants (900 millions d'euros chaque année), Orange Labs joue un rôle moteur dans la croissance dugroupe France Télécom, avec pour missions principales de développer des produits et services, de dégager denouvelles sources de croissance et d'imaginer les solutions du futur.

Ainsi, tous les ans plus de 500 inventions sont brevetées, soit un total de plus de 8300 brevets déposés.

2

2.2 Organisation

2.2 Organisation

Sous la responsabilité de Marie-Noëlle Jégo-Laveissière, Orange Labs se compose de 6 centres de recherche etdéveloppement (CRD) localisés majoritairement en France : Issy-les-Moulineaux, Lannion, Grenoble, Caen,Rennes et Sophia Antipolis. J'ai intégré le CRD Middleware et plate-formes avancées (MAPS). Ce CRD estdirigé par Nadine Foulon-Belkacémi. Il a pour mission de faire émerger des concepts innovants dans le domainedes plate-formes de services Orange. Il contribue aussi à la spéci�cation et au développement technique desnouveaux produits du groupe.

MAPS se concentre tout particulièrement sur le masquage de la complexité technologique dans les usagesmobiles, multimédia et audiovisuels. Le but est d'enrichir l'expérience utilisateur avec simplicité et en luigarantissant un service de qualité et sécurisé.

Fig. 2 � Organisation du Centre de Recherche et Développement MAPS

2.3 Le laboratoire Devices

J'ai été a�lié au laboratoire Devices (DVC), dont le but est de développer des applications sur mobiles. Unecentaine de personnes en font parti, réparties dans divers laboratoires répartis en France (Lannion, Rennes,Caen, Paris) mais aussi à l'étranger (Angleterre, Chine, Pologne, République dominicaine).

Le laboratoire se consacrait à l'origine aux mobiles équipés des systèmes d'exploitation Symbian, WindowsMobile et utilisant l'API1 Java MIDP. Cependant, l'arrivée des iPhones et des systèmes Android changeal'orientation de DVC. Les travaux du laboratoire concernent également la sécurité et la normalisation de latéléphonie mobile.

1API : Interface de programmation (Application Programming Interface).

3

1 PRÉSENTATION GÉNÉRALE

Troisième partie

Présentation de ASM

1 Présentation générale

ASM2 est une bibliothèque développée en Java par France Télécom et l'INRIA à Grenoble, et di�usée parObjectWeb, un consortium visant à créer des applications middleware en open-source. ASM permet la dé-compilation, la modi�cation et la recomposition de classes binaires Java grâce à une API simple qui parcourtles données compilées. Une seconde API, aposée à la première, permet le parcours en arbre.

ASM peut être utilisée à de nombreuses �ns :

� Analyser un programme a�n de le déboguer ou véri�er son comportement.

� Générer du code, partiellement ou non.

� Obfusquer un programme.

� Faire du reverse-engineering.

� Faire de la programmation orienté aspect, en � ouvrant � une classe et en ajoutant du code avant, aprèscertaines méthodes, ou appel de méthodes.

Il existe déjà quelques outils permettant de manipuler du bytecode Java, notamment BCEL et Javassist.Cependant c'est sur ASM qu'Orange Labs a porté son intérêt :

� ASM dispose d'une API simple, prend peu de mémoire avec l'API de base.

� BCEL est trois fois plus lent qu'ASM et l'API est complexe.

� Javassist est lui aussi plus lent qu'ASM et dispose d'un mode � source � qui rend la manipulation dubytecode très aisée, mais très gourmande en mémoire.

� En�n, ASM traite Java 6 dans son intégralité, notamment au niveau de la généricité et des annotations.

Le laboratoire DVC auquel j'ai été a�lié souhaiterait manipuler le bytecode d'applications compilées a�n :

� d'explorer les programmes utilisés par certaines applications que DVC développe en sous-traitance maisdont il ne dispose pas des sources, à des �ns de débogages.

� de tester et améliorer la sécurité de ces mêmes programmes.

� de dresser des statistiques sur un grand nombre d'applications pour voir quelles instructions sont lesmoins utilisées. Le but est de les remplacer par de nouvelles instructions utiles à certains traitementsspéci�ques, comme l'optimisation de méthodes récursives.

2ASM : http://asm.ow2.org/

4

2 La Core API

Comme son nom l'indique, cette API est la base d'ASM et donc également de la seconde API et des outilsannexes. Cette API utilise le patron de conception � Visiteur � (voir annexe 1 page 38) a�n de parcourir demanière séquentielle toutes les structures d'une classe Java compilée. Des � événements � sont produits parla lecture du �chier. On peut faire le rapprochement avec le fonctionnement de SAX3 pour le traitement de�chiers XML. L'API se base sur les classes décrites sur le schéma 3. Elle dispose de trois pans bien distincts :

La production d'événements en lisant du code : La classe ClassReader, comme son nom l'indique, litles données d'une classe compilée et appelle les méthodes de tout visiteur héritant de ClassVisitor,

donné en paramètre à sa méthode accept. Par exemple, la rencontre avec un champ appellera laméthode visitField du visiteur, accompagné des paramètres permettant de l'identi�er.

Les �ltres : Dérivant de ClassVisitor, les �ltres ne font par défaut que transmettre les événements versun autre visiteur. L'idée est de leur ajouter du comportement a�n de retirer, ajouter ou transformerdu code.

La génération de code en recevant des événements : Le visiteur ClassWriter (héritant donc de ClassVisitor) a pour rôle de transformer tout événement reçu en bytecode. En sortie, il fournira un tableaud'octets représentant un �chier .class, c'est-à-dire en bytecode Java.

Un visiteur donné par l'utilisateur et à qui on envoie des événements dé�nit le comportement à adopter :

� Accepter et transmettre les événements : la continuité du programme n'est pas perturbée et il demeureinchangé.

� Les ignorer : le programme perd des éléments. On a donc retiré du code par rapport à l'original.

� En ajouter de nouveaux : il y a production de code. Du � nouveau � code est ajouté.

Fig. 3 � Diagramme de classes de ASM

3SAX (Simple API for XML) : http://www.saxproject.org/

5

2 LA CORE API

2.1 Génération d'une classe

Pour générer une classe, il su�t d'utiliser un ClassWriter. Nous allons pour l'instant l'utiliser seul : lagénération de code se fait donc de manière programmatique, à partir � de rien �. Regardons la petite interfacesuivante :

public interface MyInterface {

public static final int MIN = 0 ;

public static final int MAX = 10 ;

public int compare(int a) ;

}

Cette interface pourra être générée par le ClassWriter grâce au programme suivant :

ClassWriter cw = new ClassWriter(0) ;

// Déclaration de l'interface à générer.

cw.visit(V1_6, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE, "nevo/MyInterface",

null, "java/lang/Object", null) ;

// Déclarations des champs que l'interface contient.

cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "MIN", "I", null,

0).visitEnd() ;

cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "MAX", "I", null,

10).visitEnd() ;

// Déclarations de la méthode que l'interface contient.

cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "compare",

"(I)I", null, null).visitEnd() ;

// On annonce la fin de l'interface.

cw.visitEnd() ;

// On génère le bytecode.

byte[] b = cw.toByteArray() ;

Les visiteurs d'ASM ont chacun une interface précise et leur documentation respective dé�nit l'ordre danslequel les visites doivent être e�ectuées. Il est très important de le respecter pour ne pas créer de sorties malformées. L'ordre des appels d'un ClassVisitor est le suivant :

visit [visitSource] [visitOuterClass] (visitAnnotation | visitAttribute)*

(visitInnerClass | visitField | visitMethod)* visitEnd.

Signaler le début et la �n de la visite de la classe est obligatoire, tandis que visitSource (qui informe dunom du �chier source de la classe) et visitOuterClass (qui informe du nom de la classe qui englobe la classeactuelle dans le cas d'une classe interne) sont optionnels. Notre programme ci-dessus respecte bien l'ordredé�ni par l'interface.

2.2 Génération de code à partir d'un �chier compilé existant

La plupart du temps, on voudra modi�er une classe compilée existante. ASM le permet simplement en liantun ClassReader à un ClassWriter. Tous les événements déclenchés par le Reader sont traduits en bytecodepour le Writer. On obtient donc exactement le même �chier en sortie, comme le montre le schéma suivant :

6

2.3 Exemple d'utilisation de �ltre

Fig. 4 � Exemple simple de la Core API

Le programme Java suivant montre comme e�ectuer cette opération :

byte[] b1 = ... ; // b1 est le bytecode à lire.

ClassWriter cw = new ClassWriter() ;

ClassReader cr = new ClassReader(b1) ; // Donne le bytecode au Reader.

cr.accept(cw, 0) ; // Les événements du Reader sont transmis au Writer.

byte[] b2 = cw.toByteArray() ; // On récupère le bytecode généré par le Writer.

Bien sûr, le véritable intérêt consiste à ajouter des �ltres a�n de transformer la classe. Ces �ltres sont appelésdes Adapter :

Fig. 5 � Exemple simple de transformation avec la Core API

Cela se traduit par le code Java suivant :

byte[] b1 = ... ;

ClassWriter cw = new ClassWriter() ;

// L'Adapter 2 transmet les événements au Writer.

ClassAdapter ca2 = new ClassAdapterAddMethods(cw) ;

// L'Adapter 1 transmet les événements à l'Adapter 2.

ClassAdapter ca1 = new ClassAdapterSuppressMethods(ca2) ;

ClassReader cr = new ClassReader(b1) ; // Donne le bytecode au Reader.

cr.accept(ca1, 0) ; // Les événements du Reader sont transmis à l'Adapter 1.

byte[] b2 = cw.toByteArray() ; // On récupère le bytecode généré par le Writer.

2.3 Exemple d'utilisation de �ltre

Imaginons un �ltre volontairement simpliste qui pré�xe tous les champs privés de la chaîne � m_ �. Il su�tde dériver ClassAdapter, qui va modi�er l'événement visitField selon la nature du champ :

public class ChangeFieldNameAdapter extends ClassAdapter {

public ChangeFieldNameAdapter(ClassVisitor cv) {

// Le constructeur sauvegarde le ClassVisitor donné afin de lui

// transmettre les évènements, modifiés ou non.

super(cv) ;

}

@Override

public void visitField(int access, String name, String desc,

7

3 LA TREE API

String signature, Object value) {

if (((access & ACC_PRIVATE) != 0) && ( !name.startsWith("m_"))) {

name = "m_" + name ;

}

// Transmets l'événement, modifié ou non.

cv.visitField(access, name, desc, signature, value) ;

}

...

}

Fig. 6 � Diagramme de séquence simpli�é pour ChangeFieldNameAdapter

Bien sûr, changer le nom des champs lors de leur rencontre n'est pas su�sant pour produire un programmevalide : il faudra aussi faire de même avec les références à ces champs, mais le but ici n'est que de montrerle principe de fonctionnement d'ASM. Ce qui amène une remarque importante : il est tout à fait possiblede produire un programme qui sera refusé par la JVM. Di�érents outils présents dans ASM permettent devéri�er la validité du bytecode généré, mais nous n'étudierons pas ces outils dans le cadre de ce rapport.

Le diagramme de séquence 6 montre le cheminement des événements entre le Reader, l'Adapter et leWriter.

La visite des éléments se contente de donner des informations sur ce qu'elle lit immédiatement, ce quiconstitue une limite de la lecture séquentielle : transformer une classe peut devenir di�cile sans avoir l'ap-plication complète sur laquelle travailler. C'est pour résoudre ce problème qu'a été introduite la secondeAPI.

3 La Tree API

Tout comme la Core API peut être rapprochée de SAX, la Tree API peut l'être de DOM 4 dans la manièrede représenter tout élément comme un objet. Cette API produit un arbre de données a�n de pouvoir ou-trepasser la limite de la lecture séquentielle et permettre l'accès à n'importe quel élément n'importe quand.L'inconvénient est la quantité de mémoire nécessaire au stockage des structures de l'application complète.Cette API est également plus lente que la Core API, de par la création de nombreux éléments. Ces élémentssont appelés � noeuds � (nodes) et le schéma 7 page ci-contre montre leur hiérarchie. Trois choses sont ànoter :

4DOM (Document Object Model) : http://www.w3.org/DOM/

8

Fig. 7 � Diagramme de classe de la Tree API

� Tout d'abord, la Tree API est basée sur la Core API. En e�et, chaque noeud dérive de manière directeou indirecte d'un visiteur.

� Ensuite, chaque noeud dispose lui-même des attributs qui le caractérise, qui lui sont donnés lors de soninstanciation.

� En�n, les noeuds possèdent une méthode accept qui produisent les événements nécessaires à la visitede sa structure et de ses sous-structures.

La Tree API fonctionne sur ce principe : la Core API permet de paramétrer les noeuds, tandis les méthodeshéritées de MemberNode gèrent l'accès et le parcours des sous-structures. Par exemple, voici le code partiel deClassNode :

public class ClassNode extends MemberNode implements ClassVisitor {

public List<MethodNode> methods ;

public void visit(final int version, final int access, final String name,

final String signature, final String superName,

final String[] interfaces) {

this.version = version ;

this.access = access ;

this.name = name ;

this.signature = signature ;

this.superName = superName ;

if (interfaces != null) {

this.interfaces.addAll(Arrays.asList(interfaces)) ;

9

3 LA TREE API

}

}

public MethodVisitor visitMethod(final int access, final String name,

final String desc, final String signature, final String[] exceptions) {

MethodNode mn = new MethodNode(access, name, desc, signature, exceptions) ;

methods.add(mn) ;

return mn ;

}

...

}

On peut voir que si exécuter la méthode visit ne fait qu'initialiser la classe, exécuter visitMethod instancieun noeud de méthode (ce qui initialise celle-ci) et la stocke dans la liste de méthodes que la classe possède.Les méthodes de visite de la classe génèrent donc l'arbre de données qui la compose.

En�n, la méthode accept envoie les évènements nécessaires à la génération de l'élément courant et de sessous-structures. Le code suivant montre comment un ClassNode génère ses évènements auprès du visiteurqui lui est donné :

public void accept(ClassVisitor cv) {

// Début de la visite de la classe.

cv.visit(version, access, name, signature, superName, interfaces) ;

...

// Visite les champs.

for (i = 0 ; i < fields.size() ; ++i) ((FieldNode) fields.get(i)).accept(cv) ;

// Visite les méthodes.

for (i = 0 ; i < methods.size() ; ++i) ((MethodNode) methods.get(i)).accept(cv) ;

// Fin de la visite.

cv.visitEnd() ;

}

3.1 Génération d'une classe

Pour générer une classe telle que celle décrite par l'interface en page 6 et de manière programmatique commeen section 2.1 page 6, il su�t de créer les noeuds comme le programme ci-dessous le montre. Rappelons quecette API nous permet de créer les éléments de la classe dans l'ordre désiré.

ClassNode cn = new ClassNode() ;

cn.version = V1_6 ;

cn.access = ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE ;

cn.name = "nevo/MyInterface" ;

cn.superName = "java/lang/Object" ;

cn.fields.add(new FieldNode(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "MIN", "I", null,

0)) ;

cn.fields.add(new FieldNode(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "MAX", "I", null,

10)) ;

cn.methods.add(new MethodNode(ACC_PUBLIC + ACC_ABSTRACT, "compare", "(I)I",

null, null) ;

ClassWriter cw = new ClassWriter(0) ;

cn.accept(cw) ; // Génération du bytecode.

byte[] b = cw.toByteArray() ;

Comme dit précédemment, les noeuds possèdent une méthode accept. Cette méthode est appelée par l'élé-ment hiérarchique supérieur et correspond à une demande d'envoi des événements au visiteur qui lui est donné.

10

3.2 Génération de code à partir d'un �chier compilé

Il su�t par exemple qu'un programme appelle la méthode accept d'un ClassNode avec un ClassWriter

en paramètre, pour que le ClassNode en question envoie les événements nécessaires à sa génération. Cela setraduit également par un appel à la méthode accept de chacune de ses sous-structures. Le diagramme deséquence 8 montre la création de la classe MyInterface décrite en page 6.

Fig. 8 � Diagramme de séquence d'une génération de classe avec la Tree API

3.2 Génération de code à partir d'un �chier compilé

Pour générer une classe à partir d'un �chier .class, il faut composer la Tree API avec la Core API :

byte[] b1 = ... ;

ClassReader cr = new ClassReader(b1) ;

ClassNode cn = new ClassNode() ;

cr.accept(cn, 0) ;

Chaque événement produit par le Reader est envoyé au visiteur qui lui est donné, soit le ClassNode dans lecas présent. Cela a pour e�et la création de la classe entière, comme le montre le schéma 9 page suivante.Notons que la visite de la méthode ne consiste que d'un visitEnd, car il s'agit d'une interface et ne contientaucune instruction.

On peut alors maintenant générer le bytecode de la classe nouvellement créée en faisant visiter le ClassNodepour un ClassWriter :

ClassWriter cw = new ClassWriter(0) ;

cn.accept(cw) ;

b2 = cw.toByteArray() ;

3.3 Exemple de transformation

E�ectuer la même transformation qu'avec la Core API (voir section 2.3 page 7) est un peu di�érent. Une foisla classe visitée (et donc stockée), il faut parcourir ses éléments pour les modi�er.

11

4 LES OUTILS ANNEXES

Fig. 9 � Diagramme de séquence de génération de code à partir d'un �chier compilé, avec la Tree API

byte[] b1 = ... ;

ClassReader cr = new ClassReader(b1) ; // Donne le bytecode au Reader.

ClassNode cn = new ClassNode() ;

cr.accept(cn, 0) ; // Initialise la classe et génère ses éléments grâce au Reader.

// Parcours des champs de la classe pour transformation.

for (FieldNode field : (List<FieldNode>)cn.fields) {

if (((field.access & Opcodes.ACC_PRIVATE) != 0) &&

( !field.name.startsWith("m_"))) {

field.name = "m_" + field.name ;

}

}

// Génération du bytecode à partir du noeud de la classe.

ClassWriter cw = new ClassWriter(0) ;

cn.accept(cw) ;

byte[] b = cw.toByteArray() ;

Le procédé est plus lent, car il s'e�ectue en au moins deux passes alors qu'avec la Core API, la transformations'e�ectue au moment de la visite. Cependant, tous les éléments de la classe sont connus ce qui permet defaire des traitements plus conséquents.

4 Les outils annexes

ASM dispose de plusieurs outils annexes mais seul l'un d'entre eux sera étudié et adapté dans notre nouvellebibliothèque.

ASMi�er Cet outil génère le code Java qu'il faudrait exécuter pour générer le �chier de classe compilé donnéà ASMi�er. Cet utilitaire étant adapté dans AsmDex, il sera étudié plus en détail en section 4.1 page 32.

TraceClassVisitor Cet outil permet d'a�cher le texte des classes visitées et de leur contenu. Cela permetdonc d'obtenir une trace des événements, ce qui peut être très utile pour du déboguage.

CheckClassAdapter Cet utilitaire permet de valider l'ordre des événements (et leurs paramètres) qu'ilreçoit et transmet, a�n d'assurer que le bytecode généré soit valide et ainsi accepté par la JVM.

12

Quatrième partie

Présentation de Dalvik

1 Présentation de la machine virtuelle Dalvik

1.1 Contexte de Dalvik : Android

Dalvik est la machine virtuelle du système d'exploitation Android. Celui-ci fut créé à la base par la sociétéAndroid Inc. en 2003, avant que Google ne la rachète en 2005. On peut trouver Android sur de nombreuxappareils mobiles tels que les SmartPhones, Netbooks et tablettes. Le nombre de mobiles équipés de cet OSdépassait les 101 millions d'unité au premier quart de 2011, représentant alors 35% du marché des mobiles.

Android est basé sur Linux, di�usé principalement en open-source et permet le développement d'appli-cations en Java. Leur code compilé est interprété par une machine virtuelle appelée Dalvik 5, mais ce codeest incompatible avec la machine virtuelle de Java (JVM). Dalvik permet aux applications d'accéder aumiddleware d'Android.

1.2 Architecture

La machine virtuelle Dalvik a été créée spéci�quement pour Android. C'est une machine à registres, à l'inversede la JVM qui est une machine à pile. Une étude[4] montre que, bien que plus complexe à mettre en oeuvre,une machine à registres nécessite 47% moins d'instructions qu'une machine à pile lors de l'exécution d'unprogramme, pour un code 26% plus large. Le gain en rapidité est notamment dû aux similitudes entrel'architecture des instructions lues et celles des processeurs exécutant Dalvik.

Puisqu'elle est une machine virtuelle, Dalvik interprète le bytecode des programmes qui lui sont donnés.Ceux-ci sont compilés au format dex qui sera étudié en section 2 page 15. Depuis la version 2.2, Dalvikdispose d'un compilateur Just-In-Time (JIT), dont la stratégie est de compiler le bytecode, le mettre en cacheet le réutiliser autant de fois que nécessaire. JIT permet un gain de vitesse propre à la compilation statique,avec cependant un coût en mémoire pour le stockage du code compilé et un léger temps de compilation aulancement de l'application pendant que la compilation JIT s'e�ectue.

1.3 Les instructions

Dalvik dispose de 217 instructions[3], ce qui permet un encodage de l'opcode (le � numéro � qui identi�el'instruction) sur 8 bits. En revanche, l'adressage se fera toujours par mot de 16 bits, ce qui permet demultiplier par 2 le champ d'adressage. En conséquence, même la plus petite instruction sera encodée sur 16bits et les autres par multiples de 16 bits. La plupart des instructions ayant des paramètres, il n'y a que trèspeu d'espace mémoire perdu par cette organisation de données.

Les arguments d'une instruction sont ordonnés : la destination apparaît en premier, suivi de la ou lessources. Les opérations e�ectuées par Dalvik sont aussi classiques que peuvent être celles de n'importe quelprocesseur : a�ectations entre registres, a�ectations d'une valeur à un registre, sauts conditionnels ou non,opérations arithmétiques etc. On trouve cependant des instructions de plus haut niveau qui permettent lagestion de tableaux uni-dimensionnels, de switch/case et try/catch, d'invocations de méthodes avec ousans paramètres, et d'instanciations d'objets.

Il existe également des instructions optimisées qui seront évoquées en 2.8 page 21.

1.3.1 Les registres

Dalvik étant basée sur une machine à registres, il est logique qu'il en manipule. Ceux-ci font systématiquement32 bits. La taille allouée à leur désignation varie selon l'instruction, mais elle peut atteindre 16 bits, soit 65536

5Ce nom étrange est en fait le nom du village Islandais où auraient vécu les ancêtres du créateur de la machine virtuelle, DanBornstein.

13

1 PRÉSENTATION DE LA MACHINE VIRTUELLE DALVIK

valeurs, bien qu'en pratique la plupart des méthodes en utiliseront moins de 16. A�n de gagner de la placemémoire, les registres sont encodés sur un nombre minimum de bits. Certaines instructions ne réserventque 4 bits pour indiquer quel registre est utilisé. Le compilateur a donc tout intérêt à utiliser en prioritéles registres numérotés de 0 à 15, d'autant plus que certaines instructions ne peuvent manipuler que ces 16premiers registres. Une mauvaise gestion pourrait entraîner de nombreux échanges entre registres.

En�n, les instructions apposées du su�xe � wide � signi�ent qu'elles traitent le registre donné et sonsuccesseur comme une paire, leur permettant alors de représenter des valeurs de 64 bits.

1.3.2 Les valeurs immédiates

Les valeurs immédiates, ou constantes, peuvent être encodées également de manière optimisée, sur 4, 8, 16,32 ou 64 bits. Certaines instructions acceptent des valeurs 16 bits, mais les considèrent comme des valeursplus larges, avec des zéros non encodés � à la droite � du nombre.

1.3.3 Le typage

Les instructions de Dalvik ne sont pas toutes typées. Ainsi, une a�ectation d'une valeur issue d'un registrevers un autre registre ne fera aucune interprétation quant à son contenu. Les instructions qui prennent letypage en compte le précisent dans leur mnémonique, en apposant l'un des su�xes suivant : boolean, byte,char, short, int, long, float, double ou object. Les instructions dont les paramètres sont des indexesleur ajoute un pré�xe précisant leur nature, comme par exemple meth@, field@, string@, suivi de l'index del'élément désiré.

1.3.4 Quelques exemples d'instructions

Voici quelques instructions, présentées avec leur mnémonique et une petite description de l'opération qu'ellese�ectuent.

Instruction E�et

nop Aucun e�et. Peut-être utilisé pour gérerl'alignement de structures.

move 1, 5 Copie le contenu du registre 5 vers le 1.const/4 1, -4 A�ecte la valeur -4 (codée sur 4 bits) au registre 1.

new-instance 1, type@3 Créé une instance de l'objet de type n°3 etdonne sa référence au registre 1.

goto/16 +7 Saut inconditionnel (codé sur 16 bits) 14 octets plus loin.packed-switch 4, +10 Switch/case testant le registre 4, selon la table de valeurs

et de sauts se trouvant 20 octets plus loin.invoke-direct 3, 4, meth@5 Appelle la méthode n°5 avec les paramètres

se trouvant dans les registres 3 et 4.

Tab. 1 � Quelques instructions de Dalvik

1.4 Comparaison entre le bytecode de Dalvik et de Java

Bien qu'utilisant toutes deux le même langage de base, Java, les deux machines virtuelles interprètent unbytecode di�érent. Comme cela a été dit dans la section précédente, Dalvik est basée sur une machine àregistres alors que la JVM est une machine à pile. Une petite comparaison entre les deux bytecodes estintéressante. Considérons le programme Java suivant :

int a = 5 ;

for (int i = 1 ; i < 10 ; i++) {

a += i ;

14

}

return a ;

Le tableau 2 montre ce même programme compilé pour Dalvik et JVM et désassemblé avec, respectivement,Baksmali6 et JBE7.

Dalvik Java Virtual Machine

const/4 v0, 0x5 iconst_5

const/4 v1, 0x1 istore_2

:goto_2 iconst_1

const/16 v2, 0xa istore_3

if-lt v1, v2, :cond_7 goto :14

return v0 :7

:cond_7 iload_2

add-int/2addr v0, v1 iload_3

add-int/lit8 v1, v1, 0x1 iadd

goto :goto_2 istore_2

iinc 3 by 1

:14

iload_3

bipush 10

if_icmplt 7

iload_2

ireturn

Tab. 2 � Comparaison entre le bytecode Dalvik et JVM

On peut remarquer que même pour un programme si petit, le bytecode Dalvik est plus court et, détailqui peut être intéressant, est plus lisible. En�n, les deux programmes occupent tous les deux une même taillede 20 octets.

2 Le format dex

2.1 Présentation

Dex signi�e � Dalvik Executable Format �[2]. Il s'agit d'un format binaire, illisible par l'oeil humain, quicomprend tout le code source d'une application. Celle-ci comporte obligatoirement un unique �chier de ceformat, nommé classes.dex. La di�érence est notable par rapport à un �chier .class pour la JVM, car il necomporte pas une seule classe mais toutes les classes de l'application. Cette accumulation de données en un�chier en augmente la complexité, mais permet la mise en commun de toutes les structures, a�n que plusieurséléments adressant par exemple la même constante partagent également le même emplacement mémoire.

Un �chier .dex comportera donc les éléments de haut niveau, comme les classes avec leurs champs etméthodes, ainsi que de bas niveau, comme les instructions que ces dernières contiennent.

2.2 Compilation d'un �chier .dex

Pour compiler une application Android, on peut utiliser l'outil en ligne de commande dx :

dx --dex [--output=<fichier de sortie>] <sources Java compilés>

6Smali/Baksmali : http://code.google.com/p/smali/7JBE : http://www.cs.ioc.ee/~ando/jbe/

15

2 LE FORMAT DEX

En sortie, on obtient un unique �chier .dex qui contient l'intégralité du bytecode de l'application. Il estimportant de noter que la compilation s'e�ectue sur des sources Java compilés. Il faudra donc au préalableutiliser javac pour compiler les sources Java en bytecode Java, puis dx pour créer le �chier .dex, comme lemontre le schéma suivant :

Fig. 10 � Chaîne de création d'un �chier .dex

Bien entendu, il est tout à fait possible d'utiliser un IDE8 comme Eclipse9 qui rendra tout le processusde compilation transparent.

2.3 Les di�érents éléments à encoder

Voici un aperçu de ce à quoi pourrait ressembler un programme à encoder :

1 public MaClasse extends MaSuperClasse implements MonInterface {

2

3 private static int monChamp1 ;

4 private final static float monChamp2 = 4.5f ;

5

6 private int maMethode(boolean param1) throws IOException {

7 param1 = !param1 ;

8 return param1 ? 0 : 128 ;

9

10 class ClasseInterneAMethode {

11 private int x ;

12 public int getX() { return x ; }

13 public void setX(int x) { this.x = x ; }

14 }

15 }

16

17 class ClassInterne {

18 public String monNom ;

19 }

20 }

On distingue les di�érents éléments de ce programme :

2.3.1 Les classes

Une classe constitue en tout logique l'élément le plus haut placé dans la hiérarchie des éléments. Elle possèdedes modi�cateurs, peut hériter d'une classe et implémenter une ou plusieurs interfaces (l. 1). Une classe peutêtre interne à une autre classe (l. 17), ou à une méthode (l. 10). Elle peut également être anonyme.

8IDE : Integrated Development Environment9Eclipse : http://www.eclipse.org/

16

2.4 Le Constant Pool

2.3.2 Les méthodes

Les méthodes appartiennent à une et une seule classe. Elles sont considérées comme directes si statiques,privées et de type constructeur, et virtuelles dans le cas contraire. Elles possèdent des paramètres ou aucun,un type de retour, des modi�cateurs et bien sûr des instructions. Elles peuvent éventuellement lever desexceptions (l. 6).

2.3.3 Les champs

Ils sont identi�és par leur nom, appartiennent à une unique classe, ont un ou plusieurs modi�cateurs etéventuellement d'une valeur (l. 3-4). Si le champ est final static, sa valeur est encodée directement, sinonil sera initialisé par le constructeur statique.

2.3.4 Les annotations

Les classes, méthodes, champs et paramètres des méthodes peuvent être annotées. Tout comme en Java, uneannotation peut comporter un simple champ, ou au contraire, en comporter de nombreux, dont des tableauxd'éléments primitifs, de chaînes ou d'énumération, voire même d'une autre annotation. Voila un programmequi montre une utilisation restreinte des annotations :

@MaPremiereAnnotation(a=3, b=true) // Annotation de classe.

public class MaClasse extends MaSuperClasse implements MonInterface {

@MaPremiereAnnotation(a=48, b=false) // Annotation de champs.

private static int monChamp1 ;

@MaSecondeAnnotation(nom="bonjour") // Annotation de méthode.

private int maMethode(@MaSecondeAnnotation(nom="au revoir")

boolean param1) { // Annotation de paramètre.

...

}

}

En plus des trois états internes d'une annotation en Java dé�nissant leur portée (CLASS, RUNTIME, SOURCE),dx en propose une quatrième : SYSTEM. Les annotations portant cet état sont compilées mais uniquementaccessibles au système et non aux applications. Les annotations de classes internes ou externes en fontnotamment partie.

2.3.5 Les éléments de débogage

Les programmes générés disposent de nombreux éléments de débogage liés à chacune des méthodes, à moinsque leur génération soit annulée dans les options de compilation. Ces éléments sont utiles aux divers outils dedéveloppement car ils permettent de faire la corrélation entre les opcodes générés et les numéros de ligne ducode source. On y trouve également des informations sur la durée de vie des variables locales. Les élémentssont encodés dans une variante du format DWARF-310. Leur encodage, purement technique, ne sera pastraité dans ce rapport.

2.4 Le Constant Pool

Le Constant Pool est une structure comprenant l'ensemble des éléments de l'application : les chaînes decaractères, les classes, méthodes, champs etc. Le format dex visant l'optimisation de la mémoire et la non-redondance d'informations, ces éléments font référence à des structures plus simples qu'elles se partagent. Enplus des éléments présentés plus haut viennent s'ajouter ceux décrits ci-après.

10DWARF-3 : http://dwarfstd.org/Dwarf3Std.php

17

2 LE FORMAT DEX

2.4.1 Les String

Une String est une chaîne de caractère encodée au format MUTF-8, une variante d'UTF-8. Chaque nom declasse, méthode, champs est une String. Il en va bien sûr de même pour les di�érentes chaînes de caractèresdont l'application aurait besoin. Il ne s'agit cependant pas d'un dictionnaire tel que l'on pourrait en trouverdans les algorithmes de compression. Chaque String peut être utilisée individuellement et aucune n'estformée par la juxtaposition d'autres (cependant certaines structures peuvent utiliser des tableaux de Stringpour justement former une chaîne plus complexe). Voici quelques exemples de String :

Hello, world ! Une chaîne a�chée par l'application.

maMethode Un nom de méthode.

<init> Le nom, généré, du constructeur.

monCompteur Une variable.

[Ljava/lang/String ; Le descripteur d'un paramètre (voir section 2.5 page ci-contre).

Les éléments présentés ci-dessous se basent généralement sur les String.

2.4.2 Les Type

Les Type désignent les identi�cateurs des classes et interfaces (entièrement quali�ées), champs, types primitifset références. Leur identi�cateur utilise des descripteurs décrits dans la section suivante. Les Type ne sont enfait qu'une référence vers une String.

2.4.3 Les Prototype

Les Prototype sont utiles aux méthodes a�n de dé�nir leurs éventuels paramètres et leur type de retour.Ils peuvent inclure des types primitifs (entiers, �ottants, booléens etc.) mais également des tableaux et desréférences.

2.4.4 De nombreuses sous-structures

Nous ne présenterons pas ici les nombreuses sous-structures constituant un �chier .dex, mais voici tout demême un aperçu de cette décomposition pour les annotations. Pour de plus amples détails, veuillez vousréférer au schéma 15 page 25.

Fig. 11 � Exemple de sous-structures : les annotations

De manière à réutiliser les éléments au maximum, ceux-ci sont décomposés autant que possible. Ainsi, ilest tout à fait possible qu'une classe partage la même annotation qu'une méthode (qui pourrait appartenir àune toute autre classe) :

@MaPremiereAnnotation(a=3, b=true) // Annotation de classe.

public class MaClasse extends MaSuperClasse implements MonInterface {

18

2.5 Les descripteurs

@MaPremiereAnnotation(a=3, b=true) // Annotation de champs.

private static int monChamp1 ;

...

Dans le cas de ce source, une seule annotation est encodée, alors que deux éléments la référencent.

2.5 Les descripteurs

Les descripteurs regroupent la manière dont un élément dé�nit son type. Il existe plusieurs types de descrip-teurs, mais seuls deux sont présentés ici car ils sont les plus utilisés :

� Le descripteur ShortyDescriptor : il s'agit d'une forme raccourcie et simpli�ée de la représentation d'untype. Les références et tableaux ne sont pas di�érenciés.

� Le descripteur TypeDescriptor : il permet de distinguer les tableaux des références et de les expliciter.Les tableaux, désignés par le caractère [ doivent dé�nir le type qu'ils contiennent. Ils peuvent éventuel-lement contenir d'autres tableaux, avec une limite de 255 dimensions. Les références, désignées par lalettre L, doivent être suivies du nom de la classe entièrement quali�ée, puis du caractère ;.

Le tableau 3 présente le descripteur lié à chaque type.

Descripteur Type

V void.Z booléen.B octet (byte), encodé sur 8 bits signés.S mot (short), encodé sur 16 bits signés.C caractère (char), encodé sur 8 bits non signés.I entier (integer), encodé sur 32 bits signés).J long, encodé sur 64 bits signés.F �ottant (�oat), encodé sur 32 bits signés).D double, encodé sur 64 bits signés.L ShortyDescriptor : référence ou tableau

TypeDescriptor : référence, suivie du type et de � ; �.[ TypeDescriptor : Tableau suivi du type qu'il contient.

Tab. 3 � Descripteurs

Le tableau 4 donne des exemples de descripteurs pour quelques éléments.

Élément ShortyDescriptor TypeDescriptor

int a I IString s L Ljava/lang/String ;void m() V V

void m(int a) VI VIString m(String s, int b) LLI Ljava/lang/String ;Ljava/lang/String ;I

int[] m(String[]) LL [I[Ljava/lang/String ;

Tab. 4 � Exemples de descripteurs

2.6 La structure d'un �chier .dex

Un �chier au format dex est un �chier binaire qui a sa structure propre et est absolument illisible par l'oeilhumain, si l'on excepte les String que l'on peut facilement repérer. On peut découper un �chier .dex en troisparties :

19

2 LE FORMAT DEX

� L'en-tête (header) : Il débute le �chier et permet d'identi�er le format du �chier, sa validité (grâce àun checksum et une signature) et donne l'emplacement de chacune des listes de références décrites plusbas. La taille de l'en-tête est �xe.

� La liste des références : On trouve ici une liste d'o�set pour toutes les instances des structures suivantes :les String, Type, Prototype, champs, méthodes, classes.

� Les données (data) : C'est ici que sont véritablement encodées toutes les structures de l'application.

Le schéma 12 montre la structure globale d'un �chier .dex d'une application de type � hello world � ainsi queles références principales qui peuvent lier les structures.

Fig. 12 � Structure globale d'un �chier .dex

2.7 Ordonnancement, alignement et unicité

La documentation de Dalvik précise que certains éléments doivent être triés lors de leur encodage.

� La plupart d'entre eux doivent être stockés selon un index de String croissant.

� Un Prototype doit être trié selon l'index de son type de retour puis de ses arguments.

� Les méthodes seront triées par la nom de la classe qui les possède, leur nom, puis par l'indice de leurPrototype.

� Les classes n'ont pas d'ordre particulier, mais une classe ne peut apparaître avant sa super classe oul'interface qu'elle implémente.

L'ordonnancement peut paraître anodin a priori, cependant il complique les choses puisqu'on ne pourra pas� �ger � de zones faisant référence à, par exemple, des String tant que nous ne sommes pas sûrs qu'ellessont toutes connues. La problématique sera exposée en section 2.2.3 page 28.

20

2.8 Les �chiers .odex

Les éléments apparaissent dans un ordre non déterminé dans la section data. En revanche, AsmDexprendra soin de produire les structures dans le même ordre que celui du compilateur Android. Bien que celacomplique la programmation, cela permet également de comparer plus facilement les �chiers originaux etgénérés, ce qui facilite le débogage.

A�n d'optimiser le mapping des éléments en mémoire, le format dex impose que certains d'entre euxutilisent un alignement quatre octets dans le �chier .dex. Cette contrainte peut là encore paraître anodine,mais elle complique parfois l'encodage, notamment lorsqu'une structure fait référence à une autre par sono�set. Un encodage de structures en deux passes est alors nécessaire.

En�n, l'unicité est préconisée, bien que non obligatoire a�n d'optimiser de la place en mémoire. Commecela a été dit précédemment, les éléments sont partagés entre les classes, méthodes, champs et paramètresde l'application. Par exemple, deux annotations identiques pourront n'être encodées qu'une seule fois maisréférencée par plusieurs éléments.

2.8 Les �chiers .odex

Certaines instructions ne sont pas accessibles directement. Lorsqu'une application est téléchargée sur unappareil mobile, Dalvik e�ectue une optimisation du bytecode, spéci�que à la plate-forme, qu'il compileradans un �chier .odex (pour Optimised Dex) placé soit dans un cache système, soit aux cotés de l'archivequi contient le �chier .dex (décrite dans la partie suivante). L'optimisation la plus importante consiste àremplacer les instructions ayant accès aux méthodes et champs par de nouvelles, su�xées � quick �. Lesméthodes seront désormais adressées par une table d'index tandis que les champs le seront par un o�set.

Ces instructions optimisées ne peuvent être produites par dx, car elles sont considérées comme non sécu-risées. AsmDex, manipulant du bytecode � en dehors � de la machine virtuelle, n'y aura donc pas accès nonplus. Nous n'étudierons pas ces instructions plus en détail dans ce rapport.

3 Les �chiers .apk

Un �chier .apk, pour Android PacKage, est une archive contenant tout ce dont une application à besoin pours'installer sur une plate-forme Android. Ce format est le pendant des .jar pour Java. Ainsi, un même �chierregroupera le code compilé de l'application au format .dex, et ses ressources. Nous n'étudierons cependantpas le contenu d'un �chier .apk ici, car ils ne sont pas utilisés par notre bibliothèque.

3.1 Génération d'un �chier .apk

La génération d'un �chier apk se fait grâce à plusieurs outils fournis dans le SDK d'Android et le JDK. Làencore, l'utilisation d'un IDE rend cette chaîne entièrement transparente. Le schéma 13 page suivante montrela chaîne de génération, volontairement simpli�ée ici.

Les outils suivants sont utilisés, en plus de dx, évoqué en section 2.2 page 15 :

aapt L'outil Android Asset Packaging Tool génère le �chier R.java à partir des ressources qui lui sont données,tels les �chiers XML, et compile ces dernières.

apkbuilder Cet outil construit le �chier .apk à partir du �chier dex, les ressources compilées par aapt etles ressources non compilées comme les images, �chiers sons etc.

jarsigner Cet outil signe le �chier .apk a�n de permettre son installation sur un appareil mobile.

Une fois le �chier .apk prêt, on utilise l'outil adb (Android Debug Bridge) pour l'installer et l'exécuter surun appareil connecté en USB.

21

4 COMPARAISON ENTRE LA JVM ET DALVIK

Fig. 13 � Chaîne de création simpli�ée d'un �chier .apk

4 Comparaison entre la JVM et Dalvik

Les deux machines virtuelles JVM et Dalvik ont pour base le même langage, mais ne produisent pas unbytecode compatible pour autant. Voici un bref aperçu des di�érences, mais aussi des nombreuses similitudesentre leur architecture.

� Comme dit précédemment, la JVM utilise une machine à pile alors que Dalvik utilise une machine àregistres.

� Malgré tout, la JVM dispose de registres pour contenir les valeurs à empiler ou dépilées. La plupartdes instructions en relation avec ces registres peuvent en adresser 256, les instructions de type � wide �65536, mais certaines sont spéci�ques à l'un des quatre premiers registres. Cette architecture se retrouvedéveloppée dans le bytecode de Dalvik, dont certaines instructions ont la possibilité d'adresser les 16,256 ou 65536 premiers registres.

� Dalvik sait gérer les mêmes annotations que la JVM, mais n'implémente pas les � attributs �. Un attributest une structure donnant des informations supplémentaires à l'élément auquel il est lié (comme uneclasse, un champ, une méthode, son code). Il existe des attributs prédé�nis permettant de déclarer lesexceptions, les classes internes, l'état Deprecated etc. mais il est également possible d'en générer denouveaux. Cependant, depuis Java 5, les attributs sont délaissés au pro�t des annotations.

� Le format MUTF-8, variante d'UTF-8 utilisé par Dalvik pour encoder des chaînes de caractères, res-semble très fortement au format utilisé par la JVM.

� Les descripteurs présentés en 2.5 page 19 sont pratiquement identiques à ceux de la JVM. La seule dif-férence concerne le descripteur des méthodes qui n'utilise pas la même syntaxe. Celui de la JVM placele type de retour après le type des paramètres, ces derniers mis entre parenthèses. Par exemple, une mé-thode ayant pour signature Object mymethod(int i, double d, Thread t) sera désignée dans Dal-vik par Ljava/lang/Object ;IDLjava/lang/Thread ; et dans la JVM par (IDLjava/lang/Thread ;)Ljava/lang/Object ;.

� Les constantes identi�ant les accesseurs sont également presque identiques.

22

Cinquième partie

Présentation de AsmDex

1 Présentation générale

AsmDex est l'adaptation de ASM pour le bytecode Dalvik. Le but est de garder une compatibilité maximum,de manière à ce qu'un programme utilisant ASM puisse être transposé sur AsmDex avec un minimum demodi�cation.

Cependant, une première observation doit être faite : ASM traite des �chiers .class, qui ne comportentqu'une seule classe, élément de base géré par ASM. En revanche, AsmDex manipule des �chiers .dex, contenantune application entière et donc de nombreuses classes. On obtient donc un niveau � supérieur � à ASM :l'application, qui devient l'élément de base. On ne résonnera plus en terme de ClassReader et ClassWriter,mais d'ApplicationReader et ApplicationWriter. La hiérarchie d'ASM est cependant toujours respectée.

Fig. 14 � Diagramme de classes d'AsmDex

1.1 Changements par rapport à ASM

Bien qu'un maximum d'e�orts ait été fait pour ne pas modi�er les interfaces d'ASM, certains changementsont été nécessaires. On peut les classer en trois catégories :

1. Les éléments traités sont gérés di�éremment. Par exemple, les signatures de Dalvik (utilisées notammentpour la généricité) sont désignées par la concaténation de chaînes de caractères a�n d'optimiser leurplace en mémoire, alors qu'une simple chaîne su�t en bytecode Java. Les méthodes des interfaces lestraitant ne gèrent donc plus des String, mais des String[].

2. Certains éléments de la JVM n'ont pas d'équivalent pour Dalvik. Ainsi, les stack map frames, sortes� d'empreintes � mémoire de la pile disséminées dans le bytecode a�n d'en optimiser la véri�cation,n'existent pas pour Dalvik. Dans ce cas, plutôt que de retirer les méthodes y faisant accès, nous avonsfait le choix de les laisser, mais de les rendre inopérantes. Cela permet de transposer sans erreurs decompilation les programmes faits pour ASM.

23

2 ADAPTATION DE LA CORE API

3. Dalvik introduit de nouveaux éléments. Nous avons déjà abordé l'ajout du niveau � application �, maisil existe également celui des instructions, qui sont très di�érentes. Nous sommes obligés de rajouter denouvelles méthodes dans l'interface de base d'ASM. Nous avons cependant retiré les instructions dontne dispose pas Dalvik car il serait perturbant, voire trompeur, d'appeler des méthodes censées générerdu bytecode et ne rien voir en sortie.

1.2 Réutilisation de l'existant

Certaines parties du code d'AsmDex ne sont pas originales et proviennent de di�érentes sources :

� Dalvik2Jimple : Il s'agit d'une bibliothèque transformant le bytecode d'un �chier .dex en un codeJimple[5]. La partie concernant la lecture d'un �ux de données contenant des valeurs numériques(entiers, �ottants etc.) a pu être intégralement réutilisée, ce qui a facilité le début du développementdu Reader, celui-ci ayant besoin d'interpréter convenablement les octets provenant d'un �chier .dex.

� Smali/Baksmali11 : Cet assembleur/désassembleur pour Dalvik di�usé en open-source dispose de mé-thodes e�caces pour produire di�érents formats de valeurs numériques. Bien que n'ayant pas réutiliséle code directement, j'ai pu m'inspirer de certaines techniques de génération.

� ASM : Le code de la Tree API a été réutilisé à plus de 70%. La classe ByteVector permettant la gestionbu�erisée d'un tableau d'octets a été également réutilisée.

Le code de la Core API a été presque intégralement repensé, de part la manière fondamentalement di�érentede gérer l'organisation des structures, comme les sections suivantes l'expliquent.

2 Adaptation de la Core API

2.1 Lecture

La lecture s'e�ectue par l'intermédiaire d'un ApplicationReader auquel on injecte un �ux d'octets consti-tuant un �chier .dex. Comme cela a été expliqué en section 2 page 5, chaque élément trouvé provoque unévènement vers le visiteur donné à l'ApplicationReader.

L'élément de base d'AsmDex est l'application. On donne à l'utilisateur la possibilité de visiter toutes lesclasses qu'elle contient ou juste certaines d'entre elles, par l'intermédiaire de la méthode accept. Un petitextrait de la classe ApplicationReader est présenté :

public class ApplicationReader {

// Construit le Reader avec du bytecode provenant de sources diverses.

public ApplicationReader(final byte[] byteCode) { ... }

public ApplicationReader(final InputStream inputStream) { ... }

public ApplicationReader(final String fileName) { ... }

// Fait visiter le bytecode au visiteur donné.

// Si classesToVisit est null, alors toutes les classes sont visitées.

public void accept(final ApplicationVisitor applicationVisitor,

final String[] classesToVisit, final int flags) { ... }

}

Une interface ApplicationVisitor a également été créée de toute pièce. En revanche, elle est extrêmementsuccincte :

public interface ApplicationVisitor {

// Visite l'en-tête de l'application.

11Smali/Baksmali : http://code.google.com/p/smali/

24

2.1 Lecture

void visit() ;

// Visite une classe de l'application.

ClassVisitor visitClass(int access, String name, String[] signature, String superName,

String[] interfaces) ;

// Visite la fin de l'application.

void visitEnd() ;

}

Le principe de la lecture est simple. En e�et, la structure d'un �chier .dex est facilement parsable et onpeut atteindre tous les éléments rapidement, notamment grâce aux références dans l'en-tête et les listes deréférences qui suivent (voir section 12 page 20). Parser les classes et leurs éléments est relativement direct,mais pas linéaire : si les structures de même type se suivent e�ectivement, les éléments qu'elles contiennentne sont bien souvent que des références. On se laissera � guider � par ces références la plupart du temps,comme le montre le schéma 15.

Fig. 15 � Organisation simpli�ée des structures d'un �chier .dex

Les interfaces des visiteurs d'ASM se transposent facilement avec la structure des �chiers .dex puisqu'ils sebasent tous les deux sur le même langage, Java. Il existe cependant quelques exceptions à cette simplicité. Parexemple, ASM impose qu'on puisse, au début de la visite d'une méthode, visiter une annotation par défaut(AnnotationDefault). Or, le format dex les stocke au niveau de la classe, et non au niveau de la méthodecomme c'est le cas du bytecode Java. Il faut alors parser l'annotation et la stocker en vue de l'utiliser plustard lors de la visite de la méthode.

Notons �nalement que mis à part certains o�sets pour accélérer le traitement, ou les structures devantêtre réutilisées ultérieurement à leur lecture, le Reader ne stocke pas de données sur le long terme. Lesinformations relatives à chaque classe, méthode, champ ou instruction sont éliminées lorsque leur visiterespective se termine. En�n, quelques optimisations intéressantes peuvent être obtenues en créant des mapspour retrouver facilement des éléments souvent utilisés, comme les String.

25

2 ADAPTATION DE LA CORE API

2.2 Écriture

L'écriture d'un �chier .dex se produit par l'interprétation des évènements reçus par l'ApplicationWriter.Comme cela a été dit en section 2.1 page 6, ces événements peuvent provenir directement d'un Application

Reader, d'un Adapter ayant un rôle de �ltre, ou avoir été envoyés programmatiquement.

2.2.1 Gestion des éléments

L'un des principaux atouts d'ASM est sa rapidité et la faible quantité de mémoire qu'il utilise. Il fallait sipossible qu'AsmDex ait ces avantages également. Cependant, l'architecture d'un �chier .dex est complexe :

� Le Constant Pool est partagé par toutes les classes, il doit donc être gardé en mémoire tout au longdu traitement, qui peut s'avérer conséquent.

� Garder l'unicité des éléments oblige à connaître tous ceux qui ont été précédemment trouvés.

� En�n, Dalvik imposant également leur tri, on ne peut se permettre d'encoder directement les structuresconnues.

Pour toutes ces raisons, AsmDex ne peut pas être aussi rapide et minimaliste qu'ASM. Aussi, puisque l'ondoit stocker des structures, nous aurons besoin d'une hiérarchie d'objets pour les représenter. Celle-ci estlargement inspirée des structures constituant le �chier .dex (cf. page 25), comme le montre le schéma 16.

Fig. 16 � Hiérarchie partielle des éléments de l'application

26

2.2 Écriture

Le Constant Pool n'est pas représenté ici mais il contient des listes ou ensembles, ordonnés si nécessaire,de ces éléments. Cela permet de connaître à tout moment le nombre exact de structures à encoder et d'avoirun accès direct sur elles.

2.2.2 Gestion du bytecode, discussion sur la minimisation de l'instanciation des objets

Le bytecode étant l'élément occupant le plus de place, il est logique de concentrer ses e�orts sur sa gestion.ASM génère le bytecode de sortie pendant qu'il le parse en entrée, ce qui implique deux choses :

� ASM ne travaille principalement pas sur des objets mais sur des octets. Il faut savoir précisément surquel octet le travail est e�ectué pour comprendre ce qui se passe, ce qui est fastidieux aussi bien pourle développement que pour la relecture.

� La modi�cation d'instructions nécessite la copie du bytecode généré dans un nouveau tableau et lesinstructions faisant référence à des labels susceptibles d'avoir bougé doivent être encodés de nouveau.

Ces deux points me posaient problème : je n'étais pas décidé à manipuler des octets, alors que la programma-tion orientée objet propose d'encapsuler des instructions dans des classes et de manipuler le tout de manièrebien plus explicite, propre et extensible. Le schéma 17 montre la hiérarchie partielle des instructions.

Fig. 17 � Diagramme de classe simpli�é des instructions

De plus, en considérant que les instructions d'une méthode sont chaînées, la modi�cation d'une instructionn'aurait aucun incidence sur ses instructions voisines. Une première version fut programmée et fonctionnaitcorrectement. En revanche, elle avait un point faible : la totalité des instructions était gardée en mémoire etle bytecode n'était généré qu'à la �n de la visite de l'application. Cela posait trois problèmes :

� Tout d'abord, la place mémoire prise par les instructions était importante.

� L'optimisation � court-circuit � décrite plus loin en section 2.2.7 page 31 ne pouvait être e�ectuée.

� En�n, cette gestion n'était pas cohérente par rapport à l'API d'ASM : si tous les objets représentantl'application lue sont créés en mémoire, la Core API est en train de devenir l'équivalent de la Tree API.

27

2 ADAPTATION DE LA CORE API

En exposant ceci à M. Crégut, nous avions tout d'abord décidé de supprimer ma gestion des instructionsen tant qu'objets pour se concentrer sur une gestion plus � bas-niveau � comme le fait ASM. N'étant passatisfait par celle-ci, j'ai cherché une solution alternative et l'ai proposée à M. Crégut qui l'accepta :

� Les instructions pourront être gérées comme des objets à condition de les éliminer à la �n de la méthode.L'avantage sera un code plus lisible et extensible, l'inconvénient étant la création de nombreux objetstemporaires, donnant plus de travail au garbage collector.

� A la �n de la visite de la méthode, on générera un bytecode intermédiaire qui utilise des référencessymboliques.

C'est la solution utilisée actuellement, expliquée dans la section suivante.

2.2.3 Gestion d'éléments symboliques

Chaque méthode visitée produit du bytecode. Comme cela a été évoqué en section 2.7 page 20, la plupart deséléments sont uniques et triés, ce qui signi�e que le bytecode de chaque méthode doit prendre en compte leséléments qui seront ajoutés par la suite. L'exemple suivant montre la problématique.

Nous désirons, tout comme ASM le fait, produire le bytecode d'une méthode lorsqu'elle est intégralementparsée. Imaginons les String du tableau ci-dessous avec leurs indexes à un instant T. Notons qu'elles sontdéjà triées en mémoire :

String IndexInstant T : Bonjour 0 � Méthode n°1 générée.

Parsing de la méthode n°1 Guten tag 1Hello 2

Le bytecode d'une méthode n°1 utilisera l'indice 0 pour adresser � Bonjour �, 2 pour � Hello �. A l'instantT+1, une nouvelle méthode est parsée et utilise une nouvelle chaîne, � Au revoir �. Cela bouleverse l'ordredes Strings :

String IndexInstant T+1 : Au revoir 0 � Méthode n°2 générée.

Parsing de la méthode n°2 Bonjour 1 Indexes de méthode n°1Guten tag 2 incorrects !Hello 3

Les références de la nouvelle méthode sont correctes. En revanche, le bytecode � �gé � de la premièreméthode est devenu faux. L'indice 0 n'adresse plus � Bonjour �, mais � Au revoir �.

Une possibilité consiste à stocker tous les éléments (instructions, labels etc.) de cette méthode et de negénérer le bytecode qu'à la �n de la visite de l'application complète. Cela fonctionne en e�et (voir section 2.2.2page précédente), mais l'un des inconvénients est l'empreinte mémoire de ces éléments.

La solution utilisée est hybride. Le schéma 18 page ci-contre montre ces di�érentes étapes.On génère bien du bytecode en �n de visite de la méthode, éliminant tout objet relatif à celle-ci, mais on

utilise une table de référence symbolique, non triée, de manière à ce que chaque nouvel élément soit ajoutéà la �n de cette table et ne remette pas en cause les références précédemment établies. Une fois l'intégralitéde l'application parsée, on génère la table de référence réelle, et donc triée, puis on parse le bytecode générépour substituer aux références symboliques les nouvelles références.

L'étape du mapping n'est pas présente dans ASM pour une raison très simple : la JVM ne requiert pas queles éléments soient triés. Chaque méthode peut donc être encodée de manière dé�nitive lorsque son analyseest terminée.

Finalement, notons que seuls quatre éléments nécessitent l'utilisation de références symboliques, car seulsces quatre éléments sont encodés dans du bytecode. Il s'agit des String, Type, Field et Method.

28

2.2 Écriture

Fig. 18 � Gestion des références dans le bytecode des méthodes

2.2.4 Problème des encodages à taille variable

Parser le bytecode et changer les références est rapide à exécuter (une simple substitution d'entiers). Cepen-dant, Dalvik pose un problème supplémentaire : l'encodage des try/catch, annexe au bytecode, ainsi que desinformations de débogage font également référence à des éléments triés, mais ces références sont encodéesdans le format ULeb12812, de taille variable. On ne peut donc pas se permettre de substituer simplement unnouvel indice par un autre car la taille du nouvel élément peut très bien être di�érente de l'ancienne. Unepossibilité serait de décaler le reste du bytecode pour y insérer ou supprimer un octet, mais cela alourdiraitconsidérablement l'exécution, de nombreux décalage pouvant se produire, impliquant un nombre importantde copies de blocs.

Ma solution consiste à parser le bytecode tout en dupliquant ses informations dans un nouveau bu�er :chaque élément lu est simplement ajouté au bu�er secondaire et est éventuellement modi�é. Une optimisationa également été implémentée : lors de la visite du bytecode, un booléen est mis à vrai si un index utilisantune référence symbolique est rencontré. En e�et, il est courant de trouver du bytecode de débogage qui ne lesutilise pas. Si le booléen est à faux à la �n de la méthode, il est inutile de parser ce bytecode. On évite ainside générer un second bu�er qui serait de toute façon identique au premier.

2.2.5 Gestion des labels

Un point important de la génération de code concerne les labels. Ils représentent les positions dans le by-tecode qui sont référencées par des instructions. Par exemple, une instruction de saut va pointer sur uno�set. Une instruction peut également référencer une autre instruction, comme c'est le cas des try/catch etswitch/case. Les o�sets du bytecode lu par le Reader sont connus, mais la di�culté réside dans le fait quede nouvelles instructions peuvent être rajoutées ou retirées par les Adapter.

La solution utilisée consiste à parser le bytecode en deux passes :

Première passe : Le bytecode original est lu et chaque instruction faisant référence à un o�set provoquela création d'un label qui contiendra cet o�set. Aucune visite d'instruction n'est e�ectuée, les Adaptern'entrent pas encore en jeu : aucun changement dans le bytecode n'est pris en compte pour le moment.Le but est uniquement de dé�nir les labels.

Deuxième passe : Les instructions sont parsées de nouveau, mais on déclenche les visites qui leur sontrelatives : le comportement des Adapter est exécuté. En parallèle, on visite les labels si l'o�set courantdans le bytecode original correspond à celui de l'un d'entre eux. Le Writer, en bout de chaîne, stocke celabel dans la structure CodeItem qui contient le bytecode généré. De nouvelles instructions ont très bienpu être ajoutées par d'éventuels Adapter, mais le Writer prend toujours en compte la taille totale desinstructions générées pour a�ecter un o�set au label qui lui est donné, il est donc toujours cohérent.

12Description du format ULeb128 : http://source.android.com/tech/dalvik/dex-format.html

29

2 ADAPTATION DE LA CORE API

Une fois les instructions de la méthode parsées, il faut s'assurer que les instructions ayant une référence àune position dans le bytecode puisse e�ectivement l'atteindre. En e�et, l'encodage de leur portée peut-êtrestocké sur 8, 16 ou 32 bits. L'ajout de nouvelles instructions peut très bien rendre cette portée insu�sante.Imaginons l'exemple suivant :

L'instruction insn2 fait référence à insn11, sa portée le lui permet. Puis un Adapter génère de nouvellesinstructions. insn2 ne peut plus accéder à insn11 :

On remplace alors insn2 par une instruction équivalente ayant une portée supérieure (16 ou 32 bits) :

La modi�cation a augmenté la taille du programme, ce qui peut nuire à d'autres instructions. Il faut alorsrefaire une passe et rechercher les instructions dont la portée serait éventuellement trop petite.

Notons que le � redimensionnement � des éléments d'une méthode ne peut-être in�ni et se termineraobligatoirement. Le nombre de passes maximum nécessaire est proportionnel au nombre d'instructions sujettesau redimensionnement, qui au pire ne se produira que deux fois par instruction (passage de 8 à 16 bits, puisde 16 à 32).

2.2.6 Gestion du tri et de l'unicité

Comme cela a été expliqué en section 2.7 page 20, la plupart des éléments sont triés. Bien que non requisepartout, l'unicité est également de mise, a�n d'optimiser la place mémoire prise par l'application. L'exemplele plus évident est le cas des String, classés par ordre alphabétique. Techniquement, la gestion est la plupartdu temps très simple, puisque Java propose des collections qui gèrent l'unicité et/ou le tri de ses éléments :

� TreeSet : ensemble d'éléments triés selon leur ordre naturel. Du fait que ce soit un ensemble, un élémentinséré est toujours unique.

� HashSet : ensemble d'éléments uniques non triés.

� PriorityQueue : collection d'éléments triés selon leur ordre naturel. Autorise les doublons.

L'utilisation de TreeSet et de PriorityQueue implique que les éléments de la collection implémentent l'in-terface Comparable et la méthode compareTo, ce qui est le cas de String.

Les classes encodées n'ont pas d'ordre particulier, mais une classe doit apparaître après sa super-classe oul'interface qu'elle implémente. Pour faire cela, on utilise un parcours récursif sur chacune des classes trouvéeset qui � remonte � le plus haut possible dans la hiérarchie a�n de trouver la classe parente et la positionneravant ses enfants.

En�n, des HashMap ou HashSet sont couramment utilisés pour stocker des structures uniques et les retrou-ver rapidement. Celles-ci doivent surcharger les méthodes equals et hashcode. Par soucis d'optimisation, lesvaleurs de hachage sont mises en cache pour éviter de les recalculer à chaque fois.

30

2.2.7 Optimisation de la génération du bytecode : court-circuit

ASM dispose d'un mécanisme intéressant pour optimiser la génération du bytecode. S'il détecte que le visiteurlié au Reader est de type MethodWriter, cela signi�e que la méthode ne sera pas modi�ée par un quelconqueAdapter et qu'il est donc possible de dupliquer le bytecode sans avoir à passer par l'envoi des événements etleur interprétation par le Writer. On gagne ainsi un temps précieux. Dans ASM, cette technique impose dedupliquer le Constant Pool de la classe vers la classe transformée. Dans AsmDex, on se contente de ne copierque les quatre structures auxquelles une méthode peut faire référence (cf. section 2.2.3 page 28).

Cette technique dispose d'un inconvénient : si un �ltre est utilisé sur certaines méthodes et qu'il en retiredes éléments, voire la méthode complète, on obtient au �nal un Constant Pool qui n'est pas optimisé enconséquence : les éléments retirés auront toujours une référence encodée. C'est pourquoi les concepteursd'ASM préconisent d'utiliser cette optimisation principalement si des �ltres qui ajoutent des éléments sontutilisés, et non s'ils en enlèvent.

En�n, il est important de noter qu'avec AsmDex, ce court-circuit implique toujours la phase de parsing(voir section 2.2.3 page 28) lorsque l'application a été entièrement visitée. En e�et, la copie partielle duConstant Pool ne se fait que sur les tables de références symboliques car il est tout à fait possible d'ajouterde nouvelles méthodes qui viendront modi�er les indexes des éléments. Malgré tout, cette optimisation donnedes résultats intéressants pouvant jusqu'à doubler la vitesse de génération d'une application.

2.2.8 Di�érences entre un �chier généré par dx et AsmDex

Des e�orts ont été faits pour qu'un �chier .dex produit par dx et AsmDex soient identiques. En pratique, ilexiste quelques di�érences mineures :

� Certaines structures n'ont pas d'ordonnancement particulier. La logique de dx n'est pas forcément lamême qu'AsmDex, ce qui a une in�uence sur l'ordre de leur encodage.

� Les structures de type TypeList sont correctement triées par AsmDex, mais dx brise la logique du trien plaçant certains types de TypeList au début de la liste. Ne voulant pas complexi�er le code avecdes règles de tri super�ues, je n'ai pas adapté ce comportement.

� Les informations de débogage ne sont pas forcément encodées identiquement. En e�et, l'interface d'ASMne permet pas de transmettre le numéro de la première ligne de chaque méthode. On tente donc dele recréer en utilisant le premier numéro de ligne trouvé dans les informations de débogage, qui necorrespond pas toujours à la véritable première ligne de la méthode. Ces numéros étant encodés demanière relative au précédent et dans un format de taille variable, il peut arriver que la sortie dedébogage d'AsmDex soit plus importante d'un ou deux octets.

3 Adaptation de la Tree API

L'adaptation vers AsmDex de la Tree API s'est fait rapidement et sans di�culté majeure. Elle se décomposeen trois parties, qui n'ont pas eu à subir beaucoup de modi�cations :

� L'interface des visiteurs : bien que celles de AsmDex soient légèrement di�érentes et disposent d'unniveau supérieur (� application �), leur principe est le même.

� Les méthodes accept des noeuds : ici également, il su�t d'adapter les méthodes aux changements quenécessite AsmDex, mais ils restent minimes.

� Le parcours des instructions : les listes d'instruction sont du type InsnList, dont les maillons sontdoublement chaînés. Ici, la totalité du code a été réutilisée sans adaptation.

Le travail a constitué principalement à la création de noeuds dont ASM ne dispose pas et pouvait presques'apparenter à du boiler plate. En�n, ASM ayant été développé pour Java 4, de nombreuses collections nontypées sont déclarées, ce qu'il fallait mieux corriger par sécurité.

31

4 CRÉATION DE L'OUTIL ASMDEXIFIER

4 Création de l'outil AsmDexi�er

4.1 Présentation de AsmDexi�er

L'outil AsmDexi�er est l'adaptation de l'outil ASMi�er d'ASM. Il permet, à partir d'un code compilé Android,de produire le code source Java qu'il faudrait exécuter pour produire le code compilé donné. Pour comprendrecomment cela fonctionne, il su�t d'imaginer devoir produire du code compilé de manière programmatiqueavec le Writer d'AsmDex. Ainsi, pour produire l'instruction RETURN-VOID, il su�t de taper :

mv.visitInsn(Opcodes.INSN_RETURN_VOID) ;

Il faut bien sûr au préalable créer une instance d'ApplicationWriter, de ClassWriter et de MethodWriter.Fort heureusement, AsmDexi�er se charge de tout cela, ainsi que la gestion des labels. Il n'y a qu'à fournirle �chier .dex à décompiler et un code complet Java directement exécutable sera créé. Un exemple de codeJava généré peut être trouvé en annexe 2 page 38.

L'intérêt de cet outil est principalement de générer des blocs de code réutilisables à volonté à partir deprogrammes existants.

4.2 Implémentation

4.2.1 La visite

L'implémentation est assez simple et se base encore une fois sur la visite des éléments grâce au Reader,comme le montre le schéma 19.

Fig. 19 � Diagramme de classes de l'outil AsmDexi�er

La visite des éléments produit du texte que l'on cumule dans un StringBuffer (plus e�cace que deconcaténer des String, car leur immuabilité provoque de nombreuses copies). On construit un arbre de cesStringBuffer grâce au patron de conception � Composite �[1]. Ainsi, une structure peut comporter autantde rami�cations que nécessaire qui appelleront leurs enfants lors de l'a�chage �nal. Une feuille de l'arbrepossédera un StringBuffer qui sera a�ché.

32

Notons que, puisque cet outil lit les données de manière linéaire et en profondeur, il n'était pas nécessaired'implémenter ce composite. Cependant, la classe AbstractVisitor sert de base à d'autres outils d'ASM,qui eux utilisent une lecture non linéaire. J'ai donc choisi cette implémentation pour permettre aux futursoutils d'utiliser le même code, mais aussi pour suivre la mise en oeuvre d'ASM.

A�n de faciliter la lecture du code généré et l'ajout de nouvelles instructions dans ce code, trois optionspratiques ont été mises en place :

� Le code généré est correctement indenté et la gestion de l'indentation est en fait rendue aisée par l'aspectrécursif de la visite, puisqu'il su�t de fournir à chaque visiteur créé un indice de profondeur supérieurà celui que l'élément actuel possède.

� Les opcodes des instructions sont remplacés par des constantes lisibles par un être humain. Il s'agitd'une simple lecture dans un tableau, mais celui-ci n'a pas été écrit � à la main �, mais généré parintrospection, grâce aux constantes qui se trouvent dans l'interface Opcodes.

� Les accesseurs sont également écrits de manière lisibles (exemple : ACC_PUBLIC + ACC_FINAL).

4.2.2 La gestion des labels

Lorsqu'une instruction fait appel à un label, il est impératif de le déclarer dans le source Java. On possèdedonc une HashMap qui stocke l'ensemble des labels rencontrés, liés au nom arbitraire � l � suivi d'un nombrequi croit au fur et à mesure que les labels sont rencontrés. Si le label utilisé par l'instruction est inconnu, onle stocke dans la HashMap et générons le code Java pour déclarer ce label. S'il existe, on peut retrouver sonnom grâce à la table.

4.2.3 Limite de taille des méthodes

ASM peut se permettre de ne fournir qu'une seule méthode qui génère la classe demandée. En revanche,AsmDex traite une application complète. Java impose une limite de 64 kilo-octets par méthode. Cette limiteest su�sante pour contenir le code de génération de très petites applications, mais pas plus. La solution estdonc de faire une méthode principale qui appelle autant de méthodes qu'il y a de classes. Une améliorationau programme pourrait être de séparer ces méthodes en autant de �chiers. En e�et, la taille du code Javagénéré est importante et Eclipse, ou tout autre éditeur, pourrait ne pas supporter le �chier généré d'unegrosse application.

5 Tests

Les tests d'AsmDex sont décomposés principalement en quatre parties : le test de l'ApplicationReader, del'Application Writer, des Adapter et de l'AsmDexifier. En toute logique, chaque élément testé peut seservir de ceux qui ont été testés auparavant.

Le principal outil utilisé est JUnit 4.713, qui dispose d'options intéressantes comme la gestion de répertoirestemporaires ou la possibilité de créer des assertions sur les exceptions, rendant leur test bien plus proprequ'avec les versions précédentes.

5.1 Le test de l'ApplicationReader

L'ApplicationReader lit un �chier .dex et produit des événements qu'il transmet à un visiteur. Le testconsiste donc à créer un visiteur qui véri�era que :

� La bonne méthode du visiteur est appelée.

� Les paramètres sont correctement renseignés, s'ils existent.

13JUnit : http://www.junit.org/

33

5 TESTS

� Le séquencement des appels est correct.

� Le nombre d'appel est exactement le même que ce qui est attendu.

En pratique, nous désirons le plus simplement possible lier un �chier .dex à une liste d'appels et de véri�erleur concordance. J'ai donc mis en oeuvre un système de log (voir l'annexe 4 page 42). On fournit à un Logger

une liste (LogList) qui contiendra des LogElement. La liste est liée au �chier .dex qui lui correspond. UnLogElement est une classe abstraite qui possède un type et d'éventuels champs, le tout correspondant à laméthode (et ses paramètres) du visiteur que le LogElement représente. Chacun d'eux peut se comparer auxautres : lorsqu'on ajoute un élément au Logger grâce à la méthode foundElement, il peut savoir si l'élémentde log est conforme.

5.2 Le test de l'ApplicationWriter

Pour tester l'ApplicationWriter, on le lie à l'ApplicationReader a�n qu'il fournisse des événements. Onse retrouve dans le cas simpliste d'un Reader lié à un Writer, comme montré auparavant dans le schéma 4page 7. Les �chiers .dex à tester sont placés dans un repertoire spéci�que, il est donc très facile d'ajouter denouveaux �chiers à tester.

Pour tester que le �chier .dex généré par le Writer correspond à celui donné, on lance Baksmali sur lesdeux �chiers, puis on compare les deux sorties.

A�n de valider que les �chiers .dex disposent également de la même structure, on compare leur Map (voir leschéma 12 page 20) qui renseigne sur le nombre de chaque type d'éléments du �chier .dex. Il n'est pas possiblede comparer les �chiers .dex octets par octets, car comme nous l'avons signalé en section 2.2.8 page 31, lalogique de compilation de dx n'est pas entièrement reproduite par AsmDex. Le résultat serait donc forcémentfaux.

5.3 Le test des Adapter

Tester les Adapter revient à faire la même chose qu'avec le test de l'ApplicationWriter, mais en ajoutantun ou plusieurs Adapter entre le Reader et le Writer (voir schéma 5 page 7).

La di�culté consiste à fournir d'une part un �chier .dex de départ, mais également un .dex d'arrivéeque l'on puisse comparer avec celui généré par le Writer. Chaque cas de test comporte donc une générationde �chiers .dex. Cette génération est faite programmatiquement grâce au Writer, ce qui permet de générerprécisément les cas de test désirés. Par exemple, on partira souvent d'un programme simpliste comportantune classe contenant deux petites méthodes. On générera ensuite le .dex dans le cas d'un ajout d'instructions,un autre pour celui d'une méthode, etc.

5.4 Le test de l'AsmDexifier

L'AsmDexifier génère, à partir d'un .dex, une classe Java qu'il faut exécuter pour générer ce même .dex.Tester l'AsmDexifier revient donc, comme dans le cas du Writer, à tester des �chiers .dex un par un etcomparer le �chier d'origine et le �chier généré. Il faut suivre le processus suivant :

� Lancer l'AsmDexifier avec un �chier .dex.

� Créer un �chier .java à partir de la sortie de l'AsmDexifier.

� Compiler le .java généré pour obtenir un .class, grâce à la commande javac.

� Exécuter ce .class a�n qu'il genère un .dex, grâce à la commande java.

� Comparer avec Baksmali ce .dex avec celui d'origine.

Comme on exécute les commandes javac et java à partir de notre programme, il convient à l'utilisateur debien con�gurer sa variable d'environnement PATH. Chaque cas de test nécessitant la compilation d'un �chier,le test de l'AsmDexifier est très long ; le temps de génération des .dex est in�me en comparaison.

34

6 Performances

ASM est réputé pour sa rapidité d'exécution. Les tests de performance qui suivent comparent l'e�cacitéd'ASM et celle d'AsmDex.

6.1 Comparaison entre les Reader

Nous testons dans un premier temps les Reader d'ASM et d'AsmDex. Un test de performance est e�ectué100000 fois sur une petite classe comportant deux methodes et quelques instructions. Pour palier le coût dela couche application du �chier .dex parsé, on teste d'abord une application comportant 1 classe, puis 50. Onobtient les résultats suivants :

ASM AsmDex (1 classe) AsmDex (50 classes)

0.00385 ms/classe 0.0092 ms/classe 0.0047 ms/classe

Le Reader d'AsmDex est plus lent que celui d'ASM. Parser une unique application comportant une seuleclasse prend deux fois plus de temps avec Asmdex, mais plus le nombre de classes augmente, plus AsmDexrattrape son retard pour devenir presque aussi rapide qu'ASM. Parser la couche application, stocker lesclasses disponibles et les String est ce qui provoque ce départ poussif dont ASM n'a pas du tout à secharger.

6.2 Comparaison entre les Writer

Un second test de performance est e�ectué pour tester les Writer, dans les mêmes conditions que précédem-ment. On obtient les résultats suivants :

ASM AsmDex (1 classe) AsmDex (50 classes)

0.00625 ms/classe 0.045 ms/classe 0.031ms/classe

On constate qu'AsmDex est de 5 à 7 fois plus lent qu'ASM. Ces résultats sont encore une fois à modérer :AsmDex construit une application autour de la classe, ce qui n'est pas le cas d'ASM.

6.3 Comparaison entre le Reader et le Writer d'ASM et d'AsmDex

On désire maintenant tester le couple Reader/Writer avec une petite application. Le bytecode en sortie duWriter est réinjecté dans le Reader.

ASM AsmDex (1 classe) AsmDex (50 classes)

0.01 ms/classe 0.06 ms/classe 0.039/classe

On peut noter deux choses de ces résultats :

� AsmDex est 4 à 6 fois plus lent sur le traitement global d'une application, soit légèrement moins qu'avecuniquement l'écriture. Cela con�rme que l'ApplicationReader n'est pas le point faible d'AsmDex auniveau de la rapidité.

� La couche application d'AsmDex diminue les performances de 35% entre une petite application et uneautre presque vide. Notons qu'augmenter encore le nombre de classes n'améliore pas le résultat.

35

7 L'AVENIR D'ASMDEX

6.4 Core API vs Tree API

Les tests de performance d'ASM indiquent que la Tree API est 30% plus lente que la Core API. Pourtant,dans notre cas, générer une application de taille modeste (plus de 500 classes) donne un résultat pratiquementidentique :

AsmDex Core API AsmDex Tree API

0.33 ms 0.332 ms

La raison est probablement que les objets créés par la Tree API ne représentent pas un poids importantface à ceux de la Core API.

6.5 Utilisation du court-circuit

Le � court-circuit � évoqué en section 2.2.7 permet d'optimiser de manière très intéressante l'interprétationdes événements entre le Reader et le Writer s'ils sont tous les deux liés. Cette optimisation permet un gainde vitesse de 15 à 20% dans ASM. Le gain est bien plus élevé avec AsmDex :

Normal Court-circuit

0.33 ms 0.157 ms

On obtient un gain de plus de 200%. Parser le bytecode, créer des objets pour chaque instruction etgénérer du code est un processus gourmand. Optimiser AsmDex nécessiterait probablement d'améliorer cettepartie du code.

6.6 Conclusion sur les performances

AsmDex est clairement moins e�cace qu'ASM. Pourtant, notre bibliothèque ne présente pas de mauvaisesperformances alors qu'elle est encore très récente, n'a pas subi d'optimisations particulières et que la gé-nération d'un �chier .dex est plus complexe que celle d'un .class. Les résultats obtenus sont donc plutôtpositifs.

7 L'avenir d'AsmDex

Bien qu'AsmDex fonctionne correctement, il existe de nombreux points qui peuvent être améliorés.

� Optimiser le code serait utile, car la génération peut devenir assez lente dès qu'un projet dépasse les500 classes.

� Les outils annexes à ASM pourraient être adaptés, notamment le CheckClassAdapter évoqué en sec-tion 4 page 12.

� On pourrait renforcer la véri�cation de la cohésion des événements reçus, car l'application peut stopperprématurément s'ils sont incohérents.

AsmDex est une biblothèque qui sera maintenue par les ingénieurs d'Orange Labs au besoin (découvertes debogues, ajout de fonctionnalités etc.), mais également par la communauté open-source où elle sera hébergée.ASM étant toujours utilisée aujourd'hui, on peut supposer qu'AsmDex le sera elle aussi dans les années àvenir.

36

Sixième partie

Conclusion

Le projet a été mené à terme et le cahier des charges a été respecté : les deux APIs d'ASM ont été adaptéesavec succès ainsi que l'outil ASMi�er dont Orange Labs a besoin. Il est donc possible de lire une applicationAndroid compilée, la modi�er et la faire exécuter par la machine virtuelle Dalvik.

L'élaboration de la bibliothèque a été un travail très technique, rendu assez di�cile principalement pourdeux raisons.

La première concerne la documentation Android. Si l'API est très bien documentée et de nombreux forumsdiscutent de son utilisation, le format dex dispose en tout et pour tout d'une seule et unique source : celle deGoogle. Celle-ci étant relativement succincte, avare en exemples et parfois incorrecte, il n'a pas toujours étéaisé de comprendre l'utilité ou l'encodage de certaines structures. Fort heureusement, il est facile de produiredu code Android pour tester les mécanismes de compilation. De plus, le desassembleur Baksmali, par la clartéde ses sorties ou l'intransigeance de ses messages d'erreur, m'a beaucoup aidé à comprendre pourquoi mespremiers �chiers étaient mal formés.

Une autre di�culté porte sur la bibliothèque ASM elle-même. Bien que la documentation fournie expliqueconvenablement le fonctionnement global, de nombreux mécanismes sont obscurs et ont demandé des essaispour les comprendre. En�n, explorer le code source d'ASM est une aventure que l'on abandonne bien vite,tant il est hermétique et monolithique.

Cependant, l'un des points intéressants de ce stage était d'une part de devoir suivre les contraintesimposées par une bibliothèque, mais également de devoir les repousser du fait que Dalvik ne fonctionne pasexactement comme la JVM. Chaque choix doit cependant être argumenté et ne pas être la marque de lafacilité.

De plus, le parti-pris de devoir garder un programme e�cace était intéressant. Je me suis refusé à créerune bibliothèque que l'on ne puisse relire et étendre. Il a donc fallu faire des compromis entre une architectureouverte et un code de traitement brut.

En�n, je pense avoir acquis avec ce stage des connaissances sur Android que peu de développeurs pos-sèdent, manipuler du bytecode relevant plus du domaine de la compilation que du développement en général.Ces connaissances me permettent de mieux appréhender la plate-forme Android, comprendre son fonction-nement interne et par la suite me permettra probablement de produire des programmes de meilleur qualité,adaptés à la machine, en plus de me permettre de travailler sur des projets bas-niveau.

Pour conclure, le projet sur lequel j'ai travaillé à considérablement amélioré mes connaissances en Java,m'a donné l'opportunité de connaître la plate-forme Android et m'a permis de produire une bibliothèque open-source répondant à un besoin spéci�que. Il est très satisfaisant de penser que des développeurs trouverontleur travail facilité par ce que j'ai créé.

37

2 EXEMPLE D'UTILISATION DE ASMDEXIFIER

Septième partie

Annexes

1 Patron de conception � Visiteur �

Le schéma 20 décrit le patron de conception � Visiteur �, tel qu'il a été décrit dans le livre Design Patterns- Catalogue de modèles de conceptions réutilisables[1].

Fig. 20 � Patron de conception � Visiteur �

Chaque Element pouvant être � visité � doit mettre à disposition une méthode publique accept prenantcomme argument un Visitor. La méthode accept appellera la méthode visit du visiteur avec pour argumentl'objet visité. Cela permet à un visiteur d'appeler les méthodes publiques de l'élément visité pour obtenir lesdonnées nécessaires au traitement à e�ectuer (calcul, génération de rapport, etc.).

2 Exemple d'utilisation de AsmDexi�er

On se propose d'utiliser l'outil AsmDexi�er avec le code compilé du programme suivant (les sources desannotations ne sont pas données ici) :

@MaPremiereAnnotation(a=3, b=true) // Annotation de classe.

public class FirstActivity extends Activity {

@MaPremiereAnnotation(a=48, b=false) // Annotation de champs.

private static int monChamp1 ;

@Override public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState) ;

setContentView(R.layout.main) ;

}

@MaSecondeAnnotation(nom="bonjour") // Annotation de méthode.

private int maMethode(@MaSecondeAnnotation() // Annotation de paramètre.

boolean param1) {

return 0 ;

}

}

La sortie d'AsmDexi�er nous donne le programme ci-dessous, reproduit en partie. Son exécution produit untableau d'octet constituant un �chier .dex :

38

import java.util.* ;

import org.objectweb.asmdex.* ;

import org.objectweb.asmdex.structureCommon.* ;

public class ClassesDump implements Opcodes {

public static byte[] dump() throws Exception {

ApplicationWriter aw = new ApplicationWriter() ;

aw.visit() ;

dumpFirstActivity(aw) ;

dumpMaPremiereAnnotation(aw) ;

dumpMaSecondeAnnotation(aw) ;

dumpR$attr(aw) ;

dumpR$drawable(aw) ;

dumpR$layout(aw) ;

dumpR$string(aw) ;

dumpR(aw) ;

aw.visitEnd() ;

return aw.toByteArray() ;

}

public static void dumpFirstActivity(ApplicationWriter aw) {

ClassVisitor cv ;

FieldVisitor fv ;

MethodVisitor mv ;

AnnotationVisitor av0 ;

cv = aw.visitClass(ACC_PUBLIC, "Lft/jn/FirstActivity ;", null,

"Landroid/app/Activity ;", null) ;

cv.visit(0, ACC_PUBLIC, "Lft/jn/FirstActivity ;", null, "Landroid/app/Activity ;",

null) ;

cv.visitSource("FirstActivity.java", null) ;

{

av0 = cv.visitAnnotation("Lft/jn/MaPremiereAnnotation ;", true) ;

av0.visit("a", 3) ;

av0.visit("b", Boolean.TRUE) ;

av0.visitEnd() ;

}

{

fv = cv.visitField(ACC_PRIVATE + ACC_STATIC, "monChamp1", "I", null) ;

{

av0 = fv.visitAnnotation("Lft/jn/MaPremiereAnnotation ;", true) ;

av0.visit("a", 48) ;

av0.visit("b", Boolean.FALSE) ;

av0.visitEnd() ;

}

fv.visitEnd() ;

}

{

mv = cv.visitMethod(ACC_PUBLIC + ACC_CONSTRUCTOR, "<init>", "V", null) ;

mv.visitCode() ;

Label l0 = new Label() ;

mv.visitLabel(l0) ;

mv.visitLineNumber(10, l0) ;

mv.visitMethodInsn(INSN_INVOKE_DIRECT, "Landroid/app/Activity ;", "<init>",

39

2 EXEMPLE D'UTILISATION DE ASMDEXIFIER

"V", new int[] { 0 }) ;

mv.visitInsn(INSN_RETURN_VOID) ;

mv.visitMaxs(1, 0) ;

mv.visitEnd() ;

}

{

mv = cv.visitMethod(ACC_PRIVATE, "maMethode", "IZ", null) ;

{

av0 = mv.visitAnnotation("Lft/jn/MaSecondeAnnotation ;", false) ;

av0.visit("nom", "bonjour") ;

av0.visitEnd() ;

}

{

av0 = mv.visitParameterAnnotation(0, "Lft/jn/MaSecondeAnnotation ;", false) ;

av0.visitEnd() ;

}

mv.visitCode() ;

mv.visitParameters(new String[] { "param1" }) ;

Label l0 = new Label() ;

mv.visitLabel(l0) ;

mv.visitLineNumber(23, l0) ;

mv.visitVarInsn(INSN_CONST_4, 0, 0) ;

mv.visitIntInsn(INSN_RETURN, 0) ;

mv.visitMaxs(3, 0) ;

mv.visitEnd() ;

}

{

mv = cv.visitMethod(ACC_PUBLIC, "onCreate", "VLandroid/os/Bundle ;", null) ;

mv.visitCode() ;

mv.visitParameters(new String[] { "savedInstanceState" }) ;

Label l0 = new Label() ;

mv.visitLabel(l0) ;

mv.visitLineNumber(17, l0) ;

mv.visitMethodInsn(INSN_INVOKE_SUPER, "Landroid/app/Activity ;", "onCreate",

"VLandroid/os/Bundle ;", new int[] { 1,2 }) ;

Label l1 = new Label() ;

mv.visitLabel(l1) ;

mv.visitLineNumber(18, l1) ;

mv.visitVarInsn(INSN_CONST_HIGH16, 0, 2130903040) ;

mv.visitMethodInsn(INSN_INVOKE_VIRTUAL, "Lft/jn/FirstActivity ;",

"setContentView", "VI", new int[] { 1, 0 }) ;

Label l2 = new Label() ;

mv.visitLabel(l2) ;

mv.visitLineNumber(19, l2) ;

mv.visitInsn(INSN_RETURN_VOID) ;

mv.visitMaxs(3, 0) ;

mv.visitEnd() ;

}

cv.visitEnd() ;

}

...

}

40

3 Diagramme de classes de ASM

Le schéma 21 montre l'organisation des classes de ASM de manière plus détaillée.

Fig. 21 � Diagramme de classes de ASM avec méthodes détaillées

41

RÉFÉRENCES

4 Organisation du test de l'ApplicationReader

Fig. 22 � Diagramme de classe du test de l'ApplicationReader

Références

[1] Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, Design Patterns - Catalogue de modèlesde conceptions réutilisables, Vuibert, 1999, 490p.

[2] The Android Open Source Project, � .dex - Dalvik executable format �, <http://source.android.com/tech/dalvik/dex-format.html>, 2007.

[3] The Android Open Source Project, � Bytecode for the Dalvik VM �, <http://source.android.com/tech/dalvik/dalvik-bytecode.html>, 2007.

[4] Yunhe Shi, Kevin Casey, Anton M. Ertl, and David Gregg. Virtual machine showdown : Stack versusregisters. ACM Transaction on Architecture and Code Optimisation (TACO), 4(4) : 1-36, January 2008.

[5] Raja Vallée-Rai. The jimple framework. Technical report, Mc Gill University - Sable Research Group,feb 1998.

42