Codingly

Cecil.Decompiler : Un décompilateur .NET OpenSource

Posted in Posts by Romain Verdier on décembre 16, 2008

Jean-Baptiste Evain vient de l’annoncer sur son blog : il travaille actuellement sur un nouveau projet particulièrement ambitieux appelé Cecil.Decompiler. Comme son nom l’indique, il s’agit d’un décompilateur .NET basé sur Mono.Cecil (du même auteur) qui permettait déjà de manipuler le langage intermédiaire du CLR.

Quelques jours avant l’annonce officielle, Jb a organisé un Code Camp en Ardèche (près de Lyon) en invitant quelques personnes à travailler avec lui sur ce decompiler. L’idée était avant tout de passer un bon moment, de faire avancer découvrir le projet, mais aussi d’intéresser et d’encourager d’éventuels futurs contributeurs à rejoindre l’initiative.

Comme j’ai eu la chance de faire partie des petits privilégiés, je vais essayer de vous livrer mes impressions à chaud.

Déjà, belle surprise en recevant le mail d’invitation de Jb… Nous ne nous étions croisé qu’une soirée, à Paris, lorsqu’il était venu présenter Mono en novembre pour une de nos réunions ALT.NET. Dans un milieu où le copinage reste bien présent, ce genre de proposition dénote bien une certaine ouverture d’esprit, et un fort degré de coolitude. Merci. Robert Pickering faisait aussi partie des invités mais à part lui (et Jb, donc) je ne connaissais personne. Ce fut l’occasion de faire la connaissance de Patrice Lamarche, Mathieu Szablowski, Vincent Bourdon, et Sébastien Lebreton (auteur de réflexil). Sept personnes en tout ; soit un petit comité.

codecamp2

Je vous avouerai que j’étais un peu impressionné à l’idée de devoir rejoindre tout ce beau monde pour travailler sur un projet de cette envergure, dont la complexité moyenne dépasse quand même celle d’une application de gestion classique… Mais l’ambiance était excellente d’une part, et le sujet carrément intéressant d’autre part.

Ca ne suffit pas pour faire oublier qu’on est en train de faire du pair programming avec un mec de l’équipe Mono, mais ça aide un peu. En tout cas, ça fait vraiment plaisir de participer à un projet aussi motivant, avec des gens dont la simple proximité rend moins con. J’aurais aimé pouvoir travailler en pair avec tout le monde, mais j’ai principalement codé avec Jb et Rob. Et tout seul aussi, hein. Je n’ai pas envie de rentrer dans les détails techniques aujourd’hui, mais en gros, j’ai principalement travaillé sur la partie « décompilation C# », et plus particulièrement sur les étapes de transformation de l’arbre syntaxique visant à faire apparaitre les structures de haut niveau du langage.

Le decompiler lit le code intermédiaire, en infère un Control Flow Graph, puis un AST assez brut dans lequel, par exemple, toutes les pre/post-tested loops sont modélisées sous la forme de statements while/do-while. Selon le langage, et selon la version du langage, il faudra ensuite exécuter une série d’analyses et de transformations sur cet arbre afin de reconnaitre les constructions spécifiques. Par exemple, et au hasard, le foreach.

Dans un vieux post, j’expliquais que le foreach en C# était syntaxiquement équivalent à :

IEnumerator<Jambon> enumerator = jambons.GetEnumerator();
try
{
	while( enumerator.MoveNext())
	{
		Jambon jambon = enumerator.Current;
		// Manger le jambon, miam, etc.
	}
}
finally
{
	IDisposable disposableEnumerator = enumerator as IDisposable;
	if(disposableEnumerator != null)
	{
		disposableEnumerator.Dispose();
	}
}

C’est donc le pattern qu’il faut identifier dans l’arbre, à quelques subtilités près. Ca m’a pris 3 bons jours, et c’est loin d’être terminé si on veut gérer tous les edge cases, allez donc savoir pourquoi. J’ai aussi travaillé avec Jb sur une implémentation sympa d’un « pattern matching du pauvre » en C#, joyeusement supprimée depuis… On cherche quelque chose de mieux. Mais ça permettait d’écrire des trucs rigolos du genre :

// Création du pattern
var matcher = Pattern.Match<Expression>()
					 .Is<MethodInvocationExpression>()
                     .Select(mie => mie.Method)
                     .Is<MethodReferenceExpression>()
                     .Tag(() => method_key, mre => mre.Method)
                     .Tag(() => target_key, mre => mre.Target);

// Compilation du pattern matcher pour récupérer un delegate qui pourra être mis en cache
// et invoqué avec de bonnes performances
var methodInvocationMatcher = matcher.Compile<Func<MatchResult, Expression, bool>> ();

// Exécution du pattern matching : on obtient un MatchResult
var result = Pattern.Run (MethodInvocationMatcher, expression);

// On peut maintenant, en cas de succès, travailler avec les valeurs qui ont été tagguée
// lors du pattern matching, si elles ont été trouvées
if (result.Success)
{
	Expression targetExpression;
    if (result.TryGetValue(target_key, out targetExpression))
    {
		// ...
	}

	MethodReference methodReference;
	if (result.TryGetValue(method_key, out methodReference))
	{
		// ...
	}
}

Le code du matcher pouvait être généré dynamiquement via System.Reflection.Emit, puis compilé pour obtenir un delegate capable d’être exécuté sans ruiner les perfs. Ca peut vous rappeler quelque chose… mais l’idée n’était pas de moi !

J’ai également eu l’opportunité de travailler avec Rob pour réaliser une version F# du matcher capable de détecter les foreach à titre d’essai. Pas vraiment l’initiation idéale à F# pour un profane comme moi, mais heureusement Rob a gardé le clavier durant la session. Ce fut pour moi l’occasion d’avoir un bon aperçu de la puissance du langage en matière de pattern matching et de tester rapidement pour la première fois l’intérop C#/F#.

codecamp1
Le projet en lui-même est à mon sens prometteur. Ce n’est pas juste un Reflector-bis. Il s’agit d’un framework de décompilation, libre et open source, qui peut être embarqué n’importe où. Ca ouvre pas mal de perspectives pour ceux qui travaillent sur des outils d’analyse statique de code par exemple. Jason Bock, auteur de CIL Programming avec lequel j’ai été méchant par le passé, va même jusqu’à trouver Cecil.Decompiler et sa licence MIT/X11 rassurants dans la mesure où la visibilité sur le devenir de Reflector est compromise.

Je ne suis pas sûr de vouloir crier hourra pour ça, mais ce dont je suis sûr, c’est que pouvoir disposer d’un décompilateur .NET open source, managé, qui repose sur une base solide, c’est tout bon. Si on considère en plus que Sébastien est déjà lancé à fond sur la réalisation d’une interface pour exploiter Cecil.Decompiler, dont le modèle d’extensibilité devrait être plus souple que celui de Reflector, c’est encore plus alléchant.

Quoi qu’il en soit, le decompiler en est à ses débuts. Il reste vraiment beaucoup de choses à faire, et ce à plusieurs niveaux :

  • Core
  • Support des langages : C#, VB, F#, etc.
  • Interface, plugins

Mais au moins, vous savez à présent que le projet existe.

20 Réponses

Subscribe to comments with RSS.

  1. Olivier said, on décembre 16, 2008 at 11:09

    Bon, les gars, faut penser à dormir et à se reposer par moment ;)
    Félicitations pour votre WE, même si je ne comprends qu’un mot sur 2, cela vous passionne, et ça, c’est le principal. Ca doit intéressant de travailler en pair-programming avec des pointures du domaine, pour l’apprentissage, ça doit être assez positif.

    Bon code. @++

  2. Julien said, on décembre 17, 2008 at 7:09

    Je suis comme Olivier, je ne suis pas sur de tout comprendre mais je trouve l’initiative très belle ! J’attends la CTP :-)
    Au delà de ca, je trouve aussi très positif que des gens associés à Alt.net participe au projet, ca vous permettra probablement de faire parler un peu plus du mouvement en france. Bon courage!

  3. bamboo said, on décembre 17, 2008 at 7:18

    Vous êtes assez tarés, j’aime bien <3 ^^

  4. Romain Verdier said, on décembre 17, 2008 at 8:34

    Vous êtes victimes de mon euphorie et ma paresse ! Il aurait fallu que je développe plus ou que je ne dise rien. Le pattern matching mériterait un bel article, au moins, et la décompilation un livre. Quoi qu’il en soit je n’ai rien fait d’impressionnant durant ce codecamp : pour peu qu’on soit intéressé par le sujet et bien encadré, tout se passe bien :)

    Je vous invite à jeter un coup d’oeil au code, moins paresseux que moi : http://anonsvn.mono-project.com/source/trunk/cecil/decompiler

  5. EVilz said, on décembre 17, 2008 at 9:21

    Hello,

    Je suis pas d’accord avec toi Romain, moi je trouve ça très impressionnant de rester 3 jours sur une boucle « foreach » !

    Comme tu l’as dit y a encore un max de taf, et heureusement que notre ami jb avait bien préparé le terrain ! Mais quand même quel kiff quand on voit apparettre les premiers ligne de codes décompilées ! Surtout en couleur ;)

    Vivement la suite…

  6. Romain Verdier said, on décembre 17, 2008 at 9:38

    Haha ! Ce n’est qu’un aperçu de mes capacités : tu peux trembler.

    J’ai précisé que le foreach n’était pas terminé ? Ceux qui sont curieux peuvent s’amuser à décompiler une itération sur :

    – IEnumerable/IEnumerable of T
    – string
    – double[]
    – int[,]
    – int[,,,,]

    J’ai une préférence pour le dernier :)

  7. Olivier said, on décembre 17, 2008 at 2:12

    Romain, je t’aurais bien vu dans mon université P7, où il y avait des cours très portés sur la compilation ou les langages, avec une approche théorique (voire trop), suffit de voir le programme de M1 http://ii.master.univ-paris7.fr/descriptifs_cours.html. En tout cas il y avait des passionnés, ça m’a toujours halluciné.

    Comme quoi, dans le monde réel, on retrouve des éléments qui servent ;)

    Pas mal le dernier en effet, je passe la main aux pros.

  8. Gauthier Segay said, on décembre 17, 2008 at 2:16

    Excellent!

    Ca donne envie de se plonger dans le code, le fait que le projet en soit à ses débuts permet de mieux saisir le mode de développement.

    L’exemple d’API pour le pattern matcher semble excellent, (au vue de la ligne 11 on se doute que l’implémentation se base à fond sur les linq expression…), j’ai également hâte d’avoir un feedback de Robert sur l’utilisation de F# pour construire un pipeline de matching flexible…

    Bien que la session n’ait pas dut être des plus productive (quantativement parlant), il est clair qu’on doit en sortir moins bête avec toutes les discussions qui ont du prendre place, et surtout qu’on doit avoir bien pris pied dans l’API concoctée par JB…

  9. Romain Verdier said, on décembre 17, 2008 at 3:02

    Olivier> La théorie m’intéresse, et j’aime bien intellectualiser pas mal de choses, mais il me faut aussi pouvoir mettre très directement en application tout ça pour que ma motivation et mon intérêt persistent. Le programme de M1 semble vraiment cool, et à première vue tous les sujets pourraient me plaire, à condition qu’il y ait cette mise en pratique. Le decompiler a ce côté génial et gratifiant : on voit le résultat (éventuellement en couleur). C’est motivant…

    Gauthier> Nop, pas de linq expression derrière ça, c’est compilé à la main via Reflection.Emit, en crachant les opcodes à la roots :)

    Il faudrait que je me concentre 3 min pour déterminer s’il aurait été possible de s’en sortir avec un ast linq, mais à première vue ce n’est pas possible, ou trop limitant. Avec la solution que nous avions, il était possible de « descendre » dans différentes branches du graphe, en splittant la construction du matcher :

    // Assertion sur le type de l'expression
    var pattern = Pattern.Match<Expression>()
    					 .Is<AssignExpression>();
    
    // On parcourt la partie gauche de l'assignation, et on y tague quelque chose
    pattern.Select(ae => ae.Target)
    	   .Is<VariableReferenceExpression>()
    	   .Tag(() => variable_key, vre => vre.Variable );
    
    // On repart de l'AssignExpression pour visiter la partie droite de l'assignation
    // et taguer autre chose dans cette branche
    pattern.Select(ae => ae.Expression)
    	   .Is<MethodInvocationExpression>()
    	   .Tag(() => method_key, mie => mie.Method)
    

    En fait on se base sur les linq expressions uniquement pour utiliser les lambdas en tant que paramètres, mais pas pour compiler.

  10. Gauthier Segay said, on décembre 17, 2008 at 3:16

    et ben, j’ai encore pas mal de choses à voir pour pouvoir emettre le IL correspondant à un statement, je vais pas pouvoir vous aider à matcher les lambda!

    J’espère que l’architecture du pattern matcher proposée va porter ses fruits :)

  11. Romain Verdier said, on décembre 17, 2008 at 4:01

    Bah en fait, on a laissé tomber l’implémentation dont on parle ici. Parce qu’elle n’est pas assez pratique (et pas assez puissante) pour répondre au besoin… Mais je trouvais la solution remarquable, au sens premier du terme.

    Tu peux jeter un coup d’oeil ici :

    http://code.google.com/p/cecil/source/browse/trunk/decompiler/Cecil.Decompiler/Cecil.Decompiler/Pattern.cs?r=199

  12. Jb Evain said, on décembre 17, 2008 at 5:06

    Utiliser les Expression Tree de LINQ en .net 3.5 aurait été possible, mais extrêment difficile/compliqué. Parce qu’il aurait fallu les combiner en une sorte de gigantesque opération booléene, juste pour avoir une expression.

    Par contre, ça peut s’écrire et être simplifié avec les Expression Tree de .net 4, où on aura des statements. (Piste à creuser, en piquant le tout dans la DLR (je préferai éviter pour le moment, mais pourquoi pas)?)

  13. Denis said, on décembre 17, 2008 at 10:03

    Excellente initiative, excellent esprit, excellent, bravo. Ca me fait vraiment plaisir de voir se monter des projets/équipes d’une telle qualité (technique et humaine) côté .Net.

  14. Gui said, on décembre 18, 2008 at 11:44

    Très impressionné par ce genre de projet. Je pense qu »il faut quand même un bon background technique pour se lancer dedans.

    En tout cas, bravo. :)

  15. Gauthier Segay said, on décembre 19, 2008 at 1:28

    jb:

    Par contre, ça peut s’écrire et être simplifié avec les Expression Tree de .net 4, où on aura des statements. (Piste à creuser, en piquant le tout dans la DLR (je préferai éviter pour le moment, mais pourquoi pas)?)

    pardon je bifurque:

    avec le prochain DLR, est-il possible qu’un AST de code dynamique soit compilable « statiquement » pour une utilisation précise mesuré par un « bench »? comme une instanciation de template ou truc plus compliqué.

  16. Jb Evain said, on décembre 19, 2008 at 10:26

    Il me semble qu’avec la DLR on peut émettre des assemblies, si c’est ça que t’entends pas statiquement. Mais ça reste du code assez spécifique à la DLR, et donc pas facile d’interopérer à partir de C# par exemple. Mais j’ai pas compris l’analogie à une template.

  17. Safia said, on décembre 19, 2008 at 8:19

    Je vous félicite pour votre travail ! Même moi je sais qui est Cecil. Je suis jalouse !

  18. Cédric M. said, on décembre 21, 2008 at 10:12

    Hello,

    Je ne vais pas parler technique car je comprends pas grand chose. Par contre sur la forme, le « codingcamp » faut vraiment être motivé…
    Je ne pense pas que tous les informaticiens sont (ou doivent) être aussi « captivés » par leur métier pour être performant, si c’est le cas j’arrête mes études et je vais faire un CAP Cuisine. J’admire ce que vous faite, mais franchement, pensez-vous que je devrais me faire du soucis si j’aspire plus à faire le taff « au taff » et en sortant du bureau penser à autre chose ? D’un autre coté je pourrai t’inviter dans mon restaurant Romain !

    Bon je laisse la place aux fans de technique, bonnes fêtes à tous :)

  19. Romain Verdier said, on décembre 22, 2008 at 10:37

    Cedric> Non, je pense pas que ça soit obligatoire. Il y a plein de gens très bien et plus compétents que moi qui n’ont jamais participé à un seul codecamp. Peut-être même qu’ils trouvent ça débile. Mais je me pose pas cette question : j’y participe parce que ça m’intéresse. Et si ça fait de moi un passionné, à la rigueur tant mieux : la passion est une motivation assez efficace, et j’ai trop souvent besoin de motivation…

    Par contre, t’as jamais eu besoin d’avoir un restaurant pour faire un rougail, donc j’ai bon espoir de pouvoir me gaver à nouveau même si tu restes informaticien :’)


Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s

%d blogueurs aiment cette page :