Salut à tous, aujourd’hui un petit article sur les bases du dotnet, le MSIL ou Microsoft Inter médiate Langage de son doux et chaleureux nom. Le MSIL est en fait le code obtenus suite à la compilation d’une application dotnet et qui sera ensuite exécuté dans le CLR. C’est grâce à cela que l’on peut facilement porter d’une architecture à une autre. En effet la compilation d’un application dotnet ne produit pas directement du code binaire directement executable mais un code intermédiaire appel (MS)IL interpreté ensuite par la CLR. Ce pseudo langage peut facilement être lu et éditeé. Pour vous montrer cela je vais prendre comme exemple une application dans lequel nous allons rajouter une méthode qui affichera « Salut » dans la console. Nous utiliserons ILDasm pour dumper et ILAsm pour compiler.
Le premier se trouve dans le windows SDK et, est, normalement fournit avec Visual Studio (sinon suffit d’installer le windows sdk) le second est fournis avec le framework dotnet qui se trouve dans %WINDIR%/Microsoft.Net/Framework/. Pour moi ce sera : %WINDIR%/Microsoft.Net/Framework64/v4.0.30319/ilasm.exe et le premier C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\ILdasm.exe ^^
Le premier outil dispose d’un ihm mais le second non. (Il s’agit d’un compiler donc l’ihm serait un peu superflue).
Notre application console contient ce code :
using System; namespace DemoDissamble { class Program { static void Main(string[] args) { Print1(); Console.ReadKey(); } static void Print1() { Console.WriteLine("Je vous dis"); } static void Print2() { Console.WriteLine("salut"); } } }
On a donc deux fonctions, mais print2 n’est pas appelé. On va régler ça.
On lance donc ILDasm.exe, on va dans Fichier->Ouvrir et on ouvre notre exe.
On devrait obtenir le résultat suivant :
On voit donc notre namespace qui contient une classe avec notre méthode main, print1 et print2. Pour le moment, on est encore en read-only. On va donc dumper en allant dans Fichier->Dump
On a toute une série de choses que l’on peut exporter, certaines très intéressantes, d’autres moins. Mais ici nous allons juste exporter le code IL, je laisse à votre discrétion l’exploration des meta-inf et cie 😉
Maintenant, j’ouvre le fichier sous visual studio (ne cherchez pas une coloration syntaxique native 😉 ), voici son contenu :
// Microsoft (R) .NET Framework IL Disassembler. Version 3.5.30729.1 // Copyright (c) Microsoft Corporation. Tous droits r�serv�s. // Metadata version: v4.0.30319 .assembly extern mscorlib { .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4.. .ver 4:0:0:0 } .assembly DemoDissamble { .custom instance void [mscorlib]System.Reflection.AssemblyTitleAttribute::.ctor(string) = ( 01 00 0D 44 65 6D 6F 44 69 73 73 61 6D 62 6C 65 // ...DemoDissamble 00 00 ) .custom instance void [mscorlib]System.Reflection.AssemblyDescriptionAttribute::.ctor(string) = ( 01 00 00 00 00 ) .custom instance void [mscorlib]System.Reflection.AssemblyConfigurationAttribute::.ctor(string) = ( 01 00 00 00 00 ) .custom instance void [mscorlib]System.Reflection.AssemblyCompanyAttribute::.ctor(string) = ( 01 00 09 4D 69 63 72 6F 73 6F 66 74 00 00 ) // ...Microsoft.. .custom instance void [mscorlib]System.Reflection.AssemblyProductAttribute::.ctor(string) = ( 01 00 0D 44 65 6D 6F 44 69 73 73 61 6D 62 6C 65 // ...DemoDissamble 00 00 ) .custom instance void [mscorlib]System.Reflection.AssemblyCopyrightAttribute::.ctor(string) = ( 01 00 1B 43 6F 70 79 72 69 67 68 74 20 C2 A9 20 // ...Copyright .. 4D 69 63 72 6F 73 6F 66 74 20 32 30 31 31 00 00 ) // Microsoft 2011.. .custom instance void [mscorlib]System.Reflection.AssemblyTrademarkAttribute::.ctor(string) = ( 01 00 00 00 00 ) .custom instance void [mscorlib]System.Runtime.InteropServices.ComVisibleAttribute::.ctor(bool) = ( 01 00 00 00 00 ) .custom instance void [mscorlib]System.Runtime.InteropServices.GuidAttribute::.ctor(string) = ( 01 00 24 31 30 66 62 37 30 65 65 2D 63 34 37 30 // ..$10fb70ee-c470 2D 34 66 63 62 2D 61 38 37 37 2D 35 65 30 33 66 // -4fcb-a877-5e03f 36 31 38 37 39 32 34 00 00 ) // 6187924.. .custom instance void [mscorlib]System.Reflection.AssemblyFileVersionAttribute::.ctor(string) = ( 01 00 07 31 2E 30 2E 30 2E 30 00 00 ) // ...1.0.0.0.. .custom instance void [mscorlib]System.Runtime.Versioning.TargetFrameworkAttribute::.ctor(string) = ( 01 00 29 2E 4E 45 54 46 72 61 6D 65 77 6F 72 6B // ..).NETFramework 2C 56 65 72 73 69 6F 6E 3D 76 34 2E 30 2C 50 72 // ,Version=v4.0,Pr 6F 66 69 6C 65 3D 43 6C 69 65 6E 74 01 00 54 0E // ofile=Client..T. 14 46 72 61 6D 65 77 6F 72 6B 44 69 73 70 6C 61 // .FrameworkDispla 79 4E 61 6D 65 1F 2E 4E 45 54 20 46 72 61 6D 65 // yName..NET Frame 77 6F 72 6B 20 34 20 43 6C 69 65 6E 74 20 50 72 // work 4 Client Pr 6F 66 69 6C 65 ) // ofile // --- The following custom attribute is added automatically, do not uncomment ------- // .custom instance void [mscorlib]System.Diagnostics.DebuggableAttribute::.ctor(valuetype [mscorlib]System.Diagnostics.DebuggableAttribute/DebuggingModes) = ( 01 00 02 00 00 00 00 00 ) .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 ) .custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 // ....T..WrapNonEx 63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 ) // ceptionThrows. .hash algorithm 0x00008004 .ver 1:0:0:0 } .module DemoDissamble.exe // MVID: {7CD7C5F3-4B77-4150-8FC1-D694703BE62D} .imagebase 0x00400000 .file alignment 0x00000200 .stackreserve 0x00100000 .subsystem 0x0003 // WINDOWS_CUI .corflags 0x00000003 // ILONLY 32BITREQUIRED // Image base: 0x00970000 // =============== CLASS MEMBERS DECLARATION =================== .class private auto ansi beforefieldinit DemoDissamble.Program extends [mscorlib]System.Object { .method private hidebysig static void Main(string[] args) cil managed { .entrypoint // Code size 12 (0xc) .maxstack 8 IL_0000: call void DemoDissamble.Program::Print1() IL_0005: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey() IL_000a: pop IL_000b: ret } // end of method Program::Main .method private hidebysig static void Print1() cil managed { // Code size 11 (0xb) .maxstack 8 IL_0000: ldstr "Je vous dis" IL_0005: call void [mscorlib]System.Console::WriteLine(string) IL_000a: ret } // end of method Program::Print1 .method private hidebysig static void Print2() cil managed { // Code size 11 (0xb) .maxstack 8 IL_0000: ldstr "salut" IL_0005: call void [mscorlib]System.Console::WriteLine(string) IL_000a: ret } // end of method Program::Print2 .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { // Code size 7 (0x7) .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance void [mscorlib]System.Object::.ctor() IL_0006: ret } // end of method Program::.ctor } // end of class DemoDissamble.Program // ============================================================= // *********** DISASSEMBLY COMPLETE *********************** // AVERTISSEMENT : le fichier de ressources Win32 C:\dump.res a été créé
Dans mon cas il n’est pas très long, 100 lignes.
Notre main va de 58 à 67 suivi de Print1 de 69 à 76 et Print2 de 78 à 85.
Regardons un peu ce qui se fait, pour la suite j’utiliserai les offset qui prennent la forme IL_ pour une question de clarté par rapport aux numéros de lignes.
Dans main, IL_0000 et IL_0005 sont deux call. On comprend aisément qu’il s’agit d’appels de fonctions, ensuite on pop le top la stack et on return. En langage plus « haut niveau », on appelle deux méthodes et on return.
Ensuite nos deux méthodes Print sont sensiblement les mêmes. On crée une référence à un objet string sur le top de la stack, on y stock la valeur entre parenthèse et on appelle Writeline qui se chargera d’afficher dans la console la string sur le top de la stack. Ensuite on return. En langage plus « haut niveau », on crée une variable, que l’on passe en argument à WriteLine et on return. (Rappel de base, ce n’est pas parce que c’est void qu’il n’y a pas de return ;))
Avant de le modifier, on va le tester. Voici la ligne de commande a utiliser :
C:\Windows\Microsoft.NET\Framework64\v4.0.30319>ilasm.exe “C:\dump.il” /exe /out:”C:\run.exe” && C:\run.exe
Donc, en premier argument le chemin du dump à recompiler, ensuite le type d’output (exe ou dll) et enfin le chemin de l’output. Le && C:\run.exe me sert à exécuter le programme à condition que la compile ai réussi (c’est un & conditionnel). Ce qui devrait vous produire quelque chose comme cela :
Microsoft (R) .NET Framework IL Assembler. Version 4.0.30319.1 Copyright (c) Microsoft Corporation. All rights reserved. Assembling 'C:\dump.il' to EXE --> 'C:\run.exe' Source file is UTF-8 Assembled method DemoDissamble.Program::Main Assembled method DemoDissamble.Program::Print1 Assembled method DemoDissamble.Program::Print2 Assembled method DemoDissamble.Program::.ctor Creating PE file Emitting classes: Class 1: DemoDissamble.Program Emitting fields and methods: Global Class 1 Methods: 4; Resolving local member refs: 1 -> 1 defs, 0 refs, 0 unresolved Emitting events and properties: Global Class 1 Resolving local member refs: 0 -> 0 defs, 0 refs, 0 unresolved Writing PE file Operation completed successfully Je vous dis
Comme on le voit il nous manque un output, le salut. On va donc rajouter l’appel à cette fonction après le premier appel. Mais on a un petit « soucis ». En effet, chaque instruction a un offset, il faut donc recalculer les offset afin de pouvoir le compiler. Cela nous donnera :
.method private hidebysig static void Main(string[] args) cil managed { .entrypoint // Code size 12 (0xc) .maxstack 8 IL_0000: call void DemoDissamble.Program::Print1() IL_0005: call void DemoDissamble.Program::Print2() IL_000a: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey() IL_000b: pop IL_000c: ret } // end of method Program::Main
Et notre output :
Pour aller plus loin voici la liste des OpCodes : http://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.pop.aspx
Et la référence de ILAsm : http://msdn.microsoft.com/fr-fr/library/496e4ekx%28v=vs.80%29.aspx
Inform@tiquement
Istace Emmanuel
Merci pour ce post. Tout ce qui concerne la plateforme DotNET est très important pour mes développements. J’attends avec impatience tous vos posts sur ce sujet, merci.