# Saturday, January 03, 2009

[Cecil.Decompiler] Decompilateur .net OpenSource basé sur Cecil

J'ai eu le plaisir de participer à un CodeCamp en Ardèche organisé par Jb Evain afin de débuter le développement d'un décompilateur .net Open Source basé sur Mono.Cecil.

Ces 4 jours furent l'occasion de revoir quelques amis tels que Jb, Vincent et mathieu, mais aussi de faire de nouvelles connaissances puisque Robert Pickering (Auteur de l'ouvrage Foundations of F#), Romain Verdier et Sebastien Lebreton (développeur de Reflexil, un addin Reflector permettant de modifier les assemblys .net) étaient également présents.

J'ai débuté ma modeste contribution en écrivant un addin pour NUnit destiné à permettre de tester le résultat des décompilations en mode Debug ET en mode Release. L'objectif étant d'écrire un test une fois pour toute et de générer en correspondance un test pour le mode debug et un test pour le mode release.

Pour cela, nous avons décidé de partir de fichiers .cs ou .vb inclus dans les projets de tests, de les compiler "dynamiquement" en debug et en release grâce à CodeDom, de les décompiler via Cecil.Decompiler et de tester le résultat de cette décompilation avec le contenu de fichiers textes contenant le résultat attendu. Cela nous a permis de définir un ensemble de TestCases permettant de tester la décompilation des structures communes ou propres à chaque langage.

Rien de bien compliqué donc mais cela m'a permis de découvrir les mécanismes d'extensibilité de NUnit et ainsi de regretter le fait qu'il n'y ait que le client NUnit qui ne supporte les AddIns NUnit, cela peut paraître évident, mais j'aurais pensé que R# ou TestDriven.net supportait ce type d'addin, car le fait de développer un addin NUnit impose donc d'utiliser NUnit comme test runner... Ce qui est quand même assez dommage...

Le développement de cette couche de test m'a également permis de découvrir un bug dans le compilateur de Visual Basic qui m'a fait perdre un peu de temps. Le compilateur de VB gère en effet de la paramètre /out de manière incorrecte. Celui-ci se comporte de manière différente que celui de C# mais également différemment que ce qu'indique la documentation du compilo VB !

En fait un appel de la commande suivante :

c:\Program Files\Microsoft Visual Studio 9.0\VC>"C:\Windows\Microsoft.NET\Framew
ork\v2.0.50727\vbc.exe" /t:library /utf8output /out:"C:\Users\plamarche\AppData\
Local\Temp\tmpEEC8.tmp" /debug- "C:\Users\plamarche\Documents\Visual Studio 200
8\Projects\ClassLibrary6\ClassLibrary6\Class1.vb"

Ne génère pas un fichier tmpEEC8.tmp comme on pourrait s'y attendre (et comme le compilateur C# le fait) mais génère un fichier tmpEEC8.tmp.dll à la place. Le compilateur concatène l'extension .dll au chemin spécifié dans le paramètre /out.

Ce problème peut sembler de premier abord anodin, mais il n'a pas été évident de le diagnostiquer puisque j'ai rencontré le problème non pas en appelant le compilateur VB directement mais en utilisant CodeDom. Utilisant CodeDom afin de compiler du code C# ou VB en debug ou en release, j'ai défini la propriété OutputAssembly afin de définir le chemin de l'assembly, résultat de la compilation, grâce à un appel à Path.GetTempFileName(). Le problème est que la création d'assembly via CodeDom diffère en fonction du langage compilé (à cause de la différence des comportement des compilateurs utilisés), ce qui est n'est pas du tout normal ! Alors que lors de la compilation de code C# donne bien le fichier attendu (un .tmp) la compilation de code VB donne un nom de fichier différent (.tmp.dll) (d'ailleurs au passage, je pensais au début que le compilateur ne compilait pas du tout puisque le .tmp est bien présent mais reste vide laissant penser que le compilo n'a rien fait du tout, mais en réalité c'est l'appel à GetTempFileName qui créé le fichier vide afin de réserver le nom de fichier).

De plus, la documentation du compilateur VB n'indique pas tout cela puisque il est indiqué que :
"The compiler expects to find the output file following the /out option.
Specify the full name and extension of the file to create. If you do not, the .exe file takes its name from the source-code file containing the Sub Main procedure, and the .dll file takes its name from the first source-code file"
http://msdn.microsoft.com/en-us/library/std9609e.aspx

Vous pouvez consulter la soumission de bug et voter pour celui-ci en consultant sa fiche sur Connect : http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=387915. On verra bien comment Microsoft le traitera sachant qu'il y a 3 options possibles :

  • La meilleure (si cela n'a pas d'impact au niveau de la compatibilité) : corriger le compilateur
  • La moins mauvaise : corriger la documentation
  • la mauvaise : ne rien faire du tout :)

codecamp2

Pas rancunier pour un sou, j'ai ensuite rejoint l'ami Vincent pour débuter le décompilateur VB. Cela ne fut pas sans problème puisque l'on s'est vite rendu compte que le compilateur VB générait des structures de code IL ou des instructions IL très particulières, pas du tout utilisés par le compilateur C#, et donc pas encore gérées par les couches basses du décompilateur. Ce fut pour moi l'occasion de redécouvrir le VB et ainsi me rappeler des différentes particularités de ce langage, comme par exemple dans le domaine de la gestion d'exceptions avec la clause When qui permet de filtrer plus finement les exceptions, ou encore le fameux On Error Resume Next qui au niveau de la compilation génère quelques surprises...

A noter qu'une partie du travail réalisé est à présent disponible sur le svn de Mono : http://anonsvn.mono-project.com/source/trunk/cecil/decompiler

En savoir plus sur le CodeCamp :
Le post de Jb : http://evain.net/blog/articles/2008/12/16/decompiler-codecamp et http://evain.net/blog/articles/2008/12/15/cecil-decompiler
Le post de Robert : http://strangelights.com/blog/archive/2008/12/21/1631.aspx
Le post de Romain : http://codingly.com/2008/12/16/cecildecompiler-un-decompilateur-net-opensource/
Le post de Vincent : http://evilznet.com/post/Evilznet-apres-un-CodeCamp.aspx