Programming Languages on Power

 View Only

Debugging .NET C# apps with ilasm and ildasm

By Vikas Gupta posted Wed February 22, 2023 10:54 AM

  

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:

  1. Language specific compiler generates the PE from source code.
  2. 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

Permalink