This blog covers the IL Assembler (ilasm) and IL Disassembler (ildasm) tools and how you can use them to debug architecture specific issues in your .NET apps. You will also learn to directly edit the intermediate language (IL) code when source code is not available on the host.
At a very high level, ilasm generates a portable executable (PE) file from intermediate IL assembly whereas ildasm generates IL assembly from a PE file.
These tools are useful if you are facing issues during code generation when you are porting any library/exe from one target architecture to another. In this situation, you can convert the PE file to IL, understand the issue, fix your issue directly in the IL file, regenerate the PE file, and then test it.
But before going into more details, let us first understand the PE file and IL Assembly.
PE file and IL Assembly
As you know, .NET is a platform independent programming language. But what does platform independent mean? Being platform independent means that the assemblies (.dll and .exe) that are compiled using one language compiler on one platform can be run on another platform as well.
The assemblies do not contain native instructions. The assemblies are also called portable executables (PE), and they run on any other architecture. PEs contain IL code and metadata.
Compilation of .NET Applications:
There are two levels of compilation in .NET:
- Language specific compiler generates the PE from source code.
- Just-In-Time (JIT) compiler generates the machine code from IL code inside the PE.
The language specific compiler translates your source code into IL, which is a CPU-independent set of instructions that can be efficiently converted into machine code.
As mentioned, .NET assemblies (dll/exe) contain IL and metadata. IL includes instructions for loading, storing, initializing, and calling methods on objects, as well as instructions for arithmetic and logical operations, control flow, direct memory access, exception handling, and other operations.
Metadata describes the types in your code, including the definition of each type, the signatures of each type's members, the members that your code references, and other data that the runtime uses at execution time.
We cannot directly see the contents of .NET assemblies. To help examine the .NET assemblies, .NET provides two useful ILTools namely, ildasm and ilasm.
Getting ilasm and ildasm tools
These ILTools (ilasm are ildasm) are architecture dependent. Hence, we need to have these tools available for respective architectures.
For ppc64le architecture, we need to build these tools since currently Microsoft does not provide prebuild ILTools nuget packages for ppc64le so you cannot directly download and install them on ppc64le. Instead, you have to cross-build the tools. Refer to this blog, Cross building .NET 7 on x86 for IBM Power to learn how to cross-build ilasm and ildasm on Power. After building, you can find these tools under the build artifacts folder.
For x86_64 architecture, you can download and install iladm and ildasm nuget packages using below steps.
Download the .nupkg files.
$ wget https://globalcdn.nuget.org/packages/runtime.linux-x64.microsoft.netcore.ildasm.7.0.0.nupkg
$ wget https://globalcdn.nuget.org/packages/runtime.linux-x64.microsoft.netcore.ilasm.7.0.0.nupkg
The output of the command is displayed below:
$ wget https://globalcdn.nuget.org/packages/runtime.linux-x64.microsoft.netcore.ildasm.7.0.0.nupkg
--2023-02-22 15:33:29-- https://globalcdn.nuget.org/packages/runtime.linux-x64.microsoft.netcore.ildasm.7.0.0.nupkg
Resolving globalcdn.nuget.org (globalcdn.nuget.org)... 152.199.40.167
Connecting to globalcdn.nuget.org (globalcdn.nuget.org)|152.199.40.167|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 764725 (747K) [application/octet-stream]
Saving to: ‘runtime.linux-x64.microsoft.netcore.ildasm.7.0.0.nupkg’
runtime.linux-x64.microsoft.netcore.ild 100%[============================================================================>] 746.80K --.-KB/s in 0.04s
2023-02-22 15:33:30 (18.4 MB/s) - ‘runtime.linux-x64.microsoft.netcore.ildasm.7.0.0.nupkg’ saved [764725/764725]
$ wget https://globalcdn.nuget.org/packages/runtime.linux-x64.microsoft.netcore.ilasm.7.0.0.nupkg
--2023-02-22 15:33:31-- https://globalcdn.nuget.org/packages/runtime.linux-x64.microsoft.netcore.ilasm.7.0.0.nupkg
Resolving globalcdn.nuget.org (globalcdn.nuget.org)... 152.199.40.167
Connecting to globalcdn.nuget.org (globalcdn.nuget.org)|152.199.40.167|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 737336 (720K) [application/octet-stream]
Saving to: ‘runtime.linux-x64.microsoft.netcore.ilasm.7.0.0.nupkg’
runtime.linux-x64.microsoft.netcore.ila 100%[============================================================================>] 720.05K 796KB/s in 0.9s
2023-02-22 15:33:32 (796 KB/s) - ‘runtime.linux-x64.microsoft.netcore.ilasm.7.0.0.nupkg’ saved [737336/737336]
Unzip the .nupkg files in a directory.
$ unzip runtime.linux-x64.microsoft.netcore.ilasm.7.0.0.nupkg -d iltools/
$ unzip runtime.linux-x64.microsoft.netcore.ildasm.7.0.0.nupkg -d iltools/
The output of the command is displayed below:
$ unzip runtime.linux-x64.microsoft.netcore.ilasm.7.0.0.nupkg -d iltools/
Archive: runtime.linux-x64.microsoft.netcore.ilasm.7.0.0.nupkg
inflating: iltools/_rels/.rels
inflating: iltools/runtime.linux-x64.Microsoft.NETCore.ILAsm.nuspec
inflating: iltools/Icon.png
inflating: iltools/LICENSE.TXT
inflating: iltools/runtimes/linux-x64/native/ilasm
inflating: iltools/THIRD-PARTY-NOTICES.TXT
inflating: iltools/[Content_Types].xml
inflating: iltools/package/services/metadata/core-properties/db8c0f8ad00849f5b469521ef2588095.psmdcp
extracting: iltools/.signature.p7s
$ unzip runtime.linux-x64.microsoft.netcore.ildasm.7.0.0.nupkg -d iltools/
Archive: runtime.linux-x64.microsoft.netcore.ildasm.7.0.0.nupkg
replace iltools/_rels/.rels? [y]es, [n]o, [A]ll, [N]one, [r]ename: N
inflating: iltools/runtime.linux-x64.Microsoft.NETCore.ILDAsm.nuspec
inflating: iltools/runtimes/linux-x64/native/ildasm
inflating: iltools/package/services/metadata/core-properties/b607a2c6ed3e4aabb18fcb98fdc3deda.psmdcp
You will see ilasm and ildasm under below directory.
$ cd iltools/runtimes/linux-x64/native/
$ ls -l
total 3360
-rw-r--r-- 1 root root 1685744 Oct 18 16:10 ilasm
-rw-r--r-- 1 root root 1742776 Oct 18 16:10 ildasm
Add the executable permission to use them.
$ chmod +x ilasm ildasm
$ ls -l
total 3368
drwxr-xr-x 2 root root 4096 Feb 22 15:34 ./
drwxr-xr-x 3 root root 4096 Feb 22 15:34 ../
-rwxr-xr-x 1 root root 1685744 Oct 18 16:10 ilasm*
-rwxr-xr-x 1 root root 1742776 Oct 18 16:10 ildasm*
ILTools (ilasm and ildasm) are ready to use.
Understand IL code in C#
Let us create a simple C# console application using the dotnet new console
command.
$ dotnet new console
The output of the command is displayed below:
$ dotnet new console
The template "Console App" was created successfully.
Processing post-creation actions...
Restoring /home/helloworld/helloworld.csproj:
Determining projects to restore...
Restored /home/helloworld/helloworld.csproj (in 467 ms).
Restore succeeded.
The C# code in Program.cs file looks as below.
$ vim Program.cs
using System;
namesapce HelloWorld
{
class Program
{
static void Main(string[] args)
Console.WriteLine("Hello World !");
}
}
When you build this application, the above source code gets compiled and the IL code is generated and packaged into an assembly (PE file).
Build the program using ’dotnet build’ command.
$ dotnet build
The output of the command is displayed below:
$ dotnet build
MSBuild version 17.4.0+18d5aef85 for .NET
Determining projects to restore...
All projects are up-to-date for restore.
helloworld-> /home/helloworld/bin/Debug/net7.0/helloworld.dll
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:08.99
$ ls -l bin/Debug/net7.0/
total 168
drwxr-xr-x 2 root root 4096 Jan 25 06:58 ./
drwxr-xr-x 3 root root 4096 Jan 25 06:58 ../
-rwxr-xr-x 1 root root 134576 Jan 25 06:58 helloworld*
-rw-r--r-- 1 root root 400 Jan 25 06:58 helloworld.deps.json
-rw-r--r-- 1 root root 5120 Jan 25 06:58 helloworld.dll
-rw-r--r-- 1 root root 10884 Jan 25 06:58 helloworld.pdb
-rw-r--r-- 1 root root 139 Jan 25 06:58 helloworld.runtimeconfig.json
$ dotnet run
Hello World !
Generate IL assembly from PE fiIe using ildasm
Run the below command on the PE file to view the contents of PE file.
$ ildasm /home/helloworld/bin/Debug/net7.0/helloworld.exe
After you run the command, you will see that PE file contains the metadata and IL code as below:
// Microsoft (R) .NET IL Disassembler. Version 7.0.0-dev
// Metadata version: v4.0.30319
.assembly extern System.Runtime
{
.publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) // .?_....:
.ver 7:0:0:0
}
.assembly extern System.Console
{
.publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) // .?_....:
.ver 7:0:0:0
}
.assembly HelloWorld
{
.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 )
.custom instance void [System.Runtime]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.
// --- The following custom attribute is added automatically, do not uncomment -------
// .custom instance void [System.Runtime]System.Diagnostics.DebuggableAttribute::.ctor(valuetype [System.Runtime]System.Diagnostics.DebuggableAttribute/DebuggingModes) = ( 01 00 07 01 00 00 00 00 )
.custom instance void [System.Runtime]System.Runtime.Versioning.TargetFrameworkAttribute::.ctor(string) = ( 01 00 18 2E 4E 45 54 43 6F 72 65 41 70 70 2C 56 // ....NETCoreApp,V
65 72 73 69 6F 6E 3D 76 37 2E 30 01 00 54 0E 14 // ersion=v7.0..T..
46 72 61 6D 65 77 6F 72 6B 44 69 73 70 6C 61 79 // FrameworkDisplay
4E 61 6D 65 00 ) // Name.
.custom instance void [System.Runtime]System.Reflection.AssemblyDefaultAliasAttribute::.ctor(string) = ( 01 00 0A 48 65 6C 6C 6F 57 6F 72 6C 64 00 00 ) // ...HelloWorld..
.custom instance void [System.Runtime]System.Reflection.AssemblyCompanyAttribute::.ctor(string) = ( 01 00 15 4D 69 63 72 6F 73 6F 66 74 20 43 6F 72 // ...Microsoft Cor
70 6F 72 61 74 69 6F 6E 00 00 ) // poration..
.custom instance void [System.Runtime]System.Reflection.AssemblyCopyrightAttribute::.ctor(string) = ( 01 00 2E C2 A9 20 4D 69 63 72 6F 73 6F 66 74 20 // ..... Microsoft
43 6F 72 70 6F 72 61 74 69 6F 6E 2E 20 41 6C 6C // Corporation. All
20 72 69 67 68 74 73 20 72 65 73 65 72 76 65 64 // rights reserved
2E 00 00 ) // ...
.custom instance void [System.Runtime]System.Reflection.AssemblyDescriptionAttribute::.ctor(string) = ( 01 00 0A 48 65 6C 6C 6F 57 6F 72 6C 64 00 00 ) // ...HelloWorld..
.custom instance void [System.Runtime]System.Reflection.AssemblyFileVersionAttribute::.ctor(string) = ( 01 00 0E 34 32 2E 34 32 2E 34 32 2E 34 32 34 32 // ...42.42.42.4242
34 00 00 ) // 4..
.custom instance void [System.Runtime]System.Reflection.AssemblyInformationalVersionAttribute::.ctor(string) = ( 01 00 09 37 2E 30 2E 30 2D 64 65 76 00 00 ) // ...7.0.0-dev..
.custom instance void [System.Runtime]System.Reflection.AssemblyProductAttribute::.ctor(string) = ( 01 00 10 4D 69 63 72 6F 73 6F 66 74 C2 AE 20 2E // ...Microsoft.. .
4E 45 54 00 00 ) // NET..
.custom instance void [System.Runtime]System.Reflection.AssemblyTitleAttribute::.ctor(string) = ( 01 00 0A 48 65 6C 6C 6F 57 6F 72 6C 64 00 00 ) // ...HelloWorld..
.custom instance void [System.Runtime]System.Reflection.AssemblyMetadataAttribute::.ctor(string,
string) = ( 01 00 0D 52 65 70 6F 73 69 74 6F 72 79 55 72 6C // ...RepositoryUrl
21 68 74 74 70 73 3A 2F 2F 67 69 74 68 75 62 2E // !https://github.
63 6F 6D 2F 64 6F 74 6E 65 74 2F 72 75 6E 74 69 // com/dotnet/runti
6D 65 00 00 ) // me..
.publickey = (00 24 00 00 04 80 00 00 94 00 00 00 06 02 00 00 // .$..............
00 24 00 00 52 53 41 31 00 04 00 00 01 00 01 00 // .$..RSA1........
4B 86 C4 CB 78 54 9B 34 BA B6 1A 3B 18 00 E2 3B // K...xT.4...;...;
FE B5 B3 EC 39 00 74 04 15 36 A7 E3 CB D9 7F 5F // ....9.t..6....._
04 CF 0F 85 71 55 A8 92 8E AA 29 EB FD 11 CF BB // ....qU....).....
AD 3B A7 0E FE A7 BD A3 22 6C 6A 8D 37 0A 4C D3 // .;......"lj.7.L.
03 F7 14 48 6B 6E BC 22 59 85 A6 38 47 1E 6E F5 // ...Hkn."Y..8G.n.
71 CC 92 A4 61 3C 00 B8 FA 65 D6 1C CE E0 CB E5 // q...a<...e......
F3 63 30 C9 A0 1F 41 83 55 9F 1B EF 24 CC 29 17 // .c0...A.U...$.).
C6 D9 13 E3 A5 41 33 3A 1D 05 D9 BE D2 2B 38 CB ) // .....A3:.....+8.
.hash algorithm 0x00008004
.ver 7:0:0:0
}
.module HelloWorld.exe
// MVID: {40f1ef76-6aec-499c-941e-f919daabed03}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003 // WINDOWS_CUI
.corflags 0x00000009 // ILONLY
// Image base: 0x00007801390B0000
The ILCode contains the instructions in readable format which are passed to JIT to convert into machine code. You can compare these ILCode against your helloworld C# program.
// =============== CLASS MEMBERS DECLARATION ===================
.class public auto ansi beforefieldinit HelloWorld.Program
extends [System.Runtime]System.Object
{
.method public hidebysig static void Main() cil managed
{
.entrypoint
// Code size 13 (0xd)
.maxstack 8
IL_0000: nop
IL_0001: ldstr "Hello World !"
IL_0006: call void [System.Console]System.Console::WriteLine(string)
IL_000b: nop
IL_000c: ret
} // end of method Program::Main
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 8 (0x8)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [System.Runtime]System.Object::.ctor()
IL_0006: nop
IL_0007: ret
} // end of method Program::.ctor
} // end of class HelloWorld.Program
// =============================================================
// *********** DISASSEMBLY COMPLETE ***********************
Metadata contains information such as, the name of the assembly, the version number of the assembly, culture, and strong name information as shown .
Metadata also contains information about the referenced assemblies. Each reference includes the dependent assembly’s name, assembly metadata (version, culture, operating system, and so on), and public key, if the assembly is strongly named.
Save and edit IL file
Run the below command to redirect the output of the ildasm command into an .il file.
$ ildasm /home/helloworld/bin/Debug/net7.0/helloworld.exe > /home/helloworld/bin/Debug/net7.0/helloworld_IL.il
Use any text editor to view and edit your IL file to find the root cause for your bug. Fix the bug directly in the IL code and save the IL file.
Regenerate PE file from IL assembly using ilasm
In the previous section, you learned how to generate IL from a PE file using ildasm. Now, let us regenerate the PE file from the above IL using ilasm.
Run the below command to convert the IL code to an executable.
$ ./ilasm -exe /home/helloworld/bin/Debug/net7.0/HelloWorld_IL.il
The output of the command is displayed below:
$ ./ilasm -exe /home/helloworld/bin/Debug/net7.0/HelloWorld_IL.il
Microsoft (R) .NET IL Assembler. Version 7.0.0-dev
C
Assembling ' /home/helloworld/bin/Debug/net7.0/HelloWorld_IL.il' to EXE --> ' /home/helloworld/bin/Debug/net7.0/HelloWorld_IL.exe'
Source file is UTF-8
Assembled method HelloWorld.Program::Main
Assembled method HelloWorld.Program::.ctor
Creating PE file
Emitting classes:
Class 1: HelloWorld.Program
Emitting fields and methods:
Global
Class 1 Methods: 2;
Emitting events and properties:
Global
Class 1
Writing PE file
Operation completed successfully
You can see the new exe is generated at path /home/helloworld/bin/Debug/net7.0/HelloWorld_IL.exe. You can now execute this exe.
$ /home/helloworld/bin/Debug/net7.0/HelloWorld_IL.exe
Hello World!!!
Conclusion
In this blog you learned about the .NET Program Execution Flow and ILTools (ilasm and ildasm). Using these tools you can debug architecture specific issues. In addition, you can see the flow, update the ILCODE, and play with it.
References
https://learn.microsoft.com/en-us/dotnet/standard/managed-execution-process
https://www.youtube.com/watch?v=VR8mj5qx07U
https://www.youtube.com/watch?v=iot1bmlmUMc
https://learn.microsoft.com/en-us/dotnet/framework/tools/ildasm-exe-il-disassembler
https://learn.microsoft.com/en-us/dotnet/framework/tools/ilasm-exe-il-assembler