Troubles getting IL on Linux

Posted on January 18, 2023 by Michael Keane Galloway
Tags: Csharp, IL, Compilers, Rust

In two previous articles: Tuple Labels Considered Harmful, and Do I Really Understand Generics, I wanted to show case the intermediate language the the C# compiler generates. I thought that would provide some illustrative points for my arguments.

Unforutnately, the only thing that I could get working at the time were some bare-bones powershell scripts (like the following) that leveraged IL Spy assemblies to decompile DLLs. This for the most part just gave me back the source code, and not the IL that I was looking for.

#!/usr/bin/pwsh

$modulePath = $HOME + '/.pwsh/modules/ILSpy/ICSharpCode.Decompiler.PowerShell.dll'

Import-Module $modulePath
$version = Get-DecompilerVersion
Write-Output $version

Write-Output $args[0]

$decompiler = Get-Decompiler $args[0]


# 
# # different test assemblies - it makes a difference wrt .deps.json so there are two netstandard tests here
# $asm_netstdWithDepsJson = $basePath + '\bin\Debug\netstandard2.0\ICSharpCode.Decompiler.Powershell.dll'
# $asm_netstd = $basePath + '\bin\Debug\netstandard2.0\ICSharpCode.Decompiler.dll'
# 
# $decompiler = Get-Decompiler $asm_netstdWithDepsJson
# 

$classes = Get-DecompiledTypes $decompiler -Types class
$classes.Count


foreach ($c in $classes)
{
    Write-Output $c.FullName
    Get-DecompiledSource $decompiler -TypeName $c.FullName
}
 
# Get-DecompiledProject $decompiler -OutputPath .\decomptest

I started looking for alternatives for getting IL thinking that it wasn’t well supported on Linux. It took me a while to finally find the ilspycmd here. I installed that, and immediately got a segmentation fault.

My first guess as to why I was segfaulting was that I had the dotnet-sdk installed via flat pack. I removed the flat pack. Removed my .dotnet directory. Installed dotnet-sdk-6.0 via apt-get. Finally installed the ilspycmd again, and this time didn’t get a segmentation fault.

I revisited my tuple example using the following command:

$ ilspycmd -il tuple-lib.dll

That command gaveme the following IL output:

// IL code: tuple-lib
.class private auto ansi '<Module>'
{
} // end of class <Module>

.class public auto ansi abstract sealed beforefieldinit TupleLib.Geometry
	extends [System.Runtime]System.Object
{
	// Methods
	.method public hidebysig static 
		valuetype [System.Runtime]System.ValueTuple`2<float64, float64> Midpoint (
			valuetype [System.Runtime]System.ValueTuple`2<float64, float64> p1,
			valuetype [System.Runtime]System.ValueTuple`2<float64, float64> p2
		) cil managed 
	{
		.param [0]
			.custom instance void [System.Runtime]System.Runtime.CompilerServices.TupleElementNamesAttribute::.ctor(string[]) = (
				01 00 02 00 00 00 01 78 01 79 00 00
			)
		.param [1]
			.custom instance void [System.Runtime]System.Runtime.CompilerServices.TupleElementNamesAttribute::.ctor(string[]) = (
				01 00 02 00 00 00 01 78 01 79 00 00
			)
		.param [2]
			.custom instance void [System.Runtime]System.Runtime.CompilerServices.TupleElementNamesAttribute::.ctor(string[]) = (
				01 00 02 00 00 00 01 78 01 79 00 00
			)
		// Method begins at RVA 0x2050
		// Header size: 12
		// Code size: 57 (0x39)
		.maxstack 3
		.locals init (
			[0] valuetype [System.Runtime]System.ValueTuple`2<float64, float64>
		)

		IL_0000: nop
		IL_0001: ldarg.0
		IL_0002: ldfld !0 valuetype [System.Runtime]System.ValueTuple`2<float64, float64>::Item1
		IL_0007: ldarg.1
		IL_0008: ldfld !0 valuetype [System.Runtime]System.ValueTuple`2<float64, float64>::Item1
		IL_000d: add
		IL_000e: ldc.r8 2
		IL_0017: div
		IL_0018: ldarg.0
		IL_0019: ldfld !1 valuetype [System.Runtime]System.ValueTuple`2<float64, float64>::Item2
		IL_001e: ldarg.1
		IL_001f: ldfld !1 valuetype [System.Runtime]System.ValueTuple`2<float64, float64>::Item2
		IL_0024: add
		IL_0025: ldc.r8 2
		IL_002e: div
		IL_002f: newobj instance void valuetype [System.Runtime]System.ValueTuple`2<float64, float64>::.ctor(!0, !1)
		IL_0034: stloc.0
		IL_0035: br.s IL_0037

		IL_0037: ldloc.0
		IL_0038: ret
	} // end of method Geometry::Midpoint

} // end of class TupleLib.Geometry

As we can see from the IL code, we then revert back to Item1 and Item2. It would be nice if there was a way to keep the symbols around so that other programs compiling against the library could leverage them, but there not there.

I then revisited my generic linked list impementation with the following command:

$ ilspycmd -il generic-obj-decompiled.dll

That gave me this IL output:

// IL code: generic-obj-decompiled
.class private auto ansi '<Module>'
{
} // end of class <Module>

.class private auto ansi sealed beforefieldinit Microsoft.CodeAnalysis.EmbeddedAttribute
	extends [System.Runtime]System.Attribute
{
	.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
		01 00 00 00
	)
	.custom instance void Microsoft.CodeAnalysis.EmbeddedAttribute::.ctor() = (
		01 00 00 00
	)
	// Methods
	.method public hidebysig specialname rtspecialname 
		instance void .ctor () cil managed 
	{
		// Method begins at RVA 0x2050
		// Header size: 1
		// Code size: 8 (0x8)
		.maxstack 8

		IL_0000: ldarg.0
		IL_0001: call instance void [System.Runtime]System.Attribute::.ctor()
		IL_0006: nop
		IL_0007: ret
	} // end of method EmbeddedAttribute::.ctor

} // end of class Microsoft.CodeAnalysis.EmbeddedAttribute

.class private auto ansi sealed beforefieldinit System.Runtime.CompilerServices.NullableAttribute
	extends [System.Runtime]System.Attribute
{
	.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
		01 00 00 00
	)
	.custom instance void Microsoft.CodeAnalysis.EmbeddedAttribute::.ctor() = (
		01 00 00 00
	)
	.custom instance void [System.Runtime]System.AttributeUsageAttribute::.ctor(valuetype [System.Runtime]System.AttributeTargets) = (
		01 00 84 6b 00 00 02 00 54 02 0d 41 6c 6c 6f 77
		4d 75 6c 74 69 70 6c 65 00 54 02 09 49 6e 68 65
		72 69 74 65 64 00
	)
	// Fields
	.field public initonly uint8[] NullableFlags

	// Methods
	.method public hidebysig specialname rtspecialname 
		instance void .ctor (
			uint8 ''
		) cil managed 
	{
		// Method begins at RVA 0x2059
		// Header size: 1
		// Code size: 24 (0x18)
		.maxstack 8

		IL_0000: ldarg.0
		IL_0001: call instance void [System.Runtime]System.Attribute::.ctor()
		IL_0006: nop
		IL_0007: ldarg.0
		IL_0008: ldc.i4.1
		IL_0009: newarr [System.Runtime]System.Byte
		IL_000e: dup
		IL_000f: ldc.i4.0
		IL_0010: ldarg.1
		IL_0011: stelem.i1
		IL_0012: stfld uint8[] System.Runtime.CompilerServices.NullableAttribute::NullableFlags
		IL_0017: ret
	} // end of method NullableAttribute::.ctor

	.method public hidebysig specialname rtspecialname 
		instance void .ctor (
			uint8[] ''
		) cil managed 
	{
		// Method begins at RVA 0x2072
		// Header size: 1
		// Code size: 15 (0xf)
		.maxstack 8

		IL_0000: ldarg.0
		IL_0001: call instance void [System.Runtime]System.Attribute::.ctor()
		IL_0006: nop
		IL_0007: ldarg.0
		IL_0008: ldarg.1
		IL_0009: stfld uint8[] System.Runtime.CompilerServices.NullableAttribute::NullableFlags
		IL_000e: ret
	} // end of method NullableAttribute::.ctor

} // end of class System.Runtime.CompilerServices.NullableAttribute

.class private auto ansi sealed beforefieldinit System.Runtime.CompilerServices.NullableContextAttribute
	extends [System.Runtime]System.Attribute
{
	.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
		01 00 00 00
	)
	.custom instance void Microsoft.CodeAnalysis.EmbeddedAttribute::.ctor() = (
		01 00 00 00
	)
	.custom instance void [System.Runtime]System.AttributeUsageAttribute::.ctor(valuetype [System.Runtime]System.AttributeTargets) = (
		01 00 4c 14 00 00 02 00 54 02 0d 41 6c 6c 6f 77
		4d 75 6c 74 69 70 6c 65 00 54 02 09 49 6e 68 65
		72 69 74 65 64 00
	)
	// Fields
	.field public initonly uint8 Flag

	// Methods
	.method public hidebysig specialname rtspecialname 
		instance void .ctor (
			uint8 ''
		) cil managed 
	{
		// Method begins at RVA 0x2082
		// Header size: 1
		// Code size: 15 (0xf)
		.maxstack 8

		IL_0000: ldarg.0
		IL_0001: call instance void [System.Runtime]System.Attribute::.ctor()
		IL_0006: nop
		IL_0007: ldarg.0
		IL_0008: ldarg.1
		IL_0009: stfld uint8 System.Runtime.CompilerServices.NullableContextAttribute::Flag
		IL_000e: ret
	} // end of method NullableContextAttribute::.ctor

} // end of class System.Runtime.CompilerServices.NullableContextAttribute

.class private auto ansi beforefieldinit LinkedListNode
	extends [System.Runtime]System.Object
{
	.custom instance void System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = (
		01 00 01 00 00
	)
	.custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = (
		01 00 00 00 00
	)
	// Fields
	.field private object '<Data>k__BackingField'
	.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
		01 00 00 00
	)
	.custom instance void [System.Runtime]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [System.Runtime]System.Diagnostics.DebuggerBrowsableState) = (
		01 00 00 00 00 00 00 00
	)
	.field private class LinkedListNode '<Next>k__BackingField'
	.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
		01 00 00 00
	)
	.custom instance void [System.Runtime]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [System.Runtime]System.Diagnostics.DebuggerBrowsableState) = (
		01 00 00 00 00 00 00 00
	)

	// Methods
	.method public hidebysig specialname 
		instance object get_Data () cil managed 
	{
		.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
			01 00 00 00
		)
		// Method begins at RVA 0x2092
		// Header size: 1
		// Code size: 7 (0x7)
		.maxstack 8

		IL_0000: ldarg.0
		IL_0001: ldfld object LinkedListNode::'<Data>k__BackingField'
		IL_0006: ret
	} // end of method LinkedListNode::get_Data

	.method public hidebysig specialname 
		instance void set_Data (
			object 'value'
		) cil managed 
	{
		.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
			01 00 00 00
		)
		// Method begins at RVA 0x209a
		// Header size: 1
		// Code size: 8 (0x8)
		.maxstack 8

		IL_0000: ldarg.0
		IL_0001: ldarg.1
		IL_0002: stfld object LinkedListNode::'<Data>k__BackingField'
		IL_0007: ret
	} // end of method LinkedListNode::set_Data

	.method public hidebysig specialname 
		instance class LinkedListNode get_Next () cil managed 
	{
		.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
			01 00 00 00
		)
		// Method begins at RVA 0x20a3
		// Header size: 1
		// Code size: 7 (0x7)
		.maxstack 8

		IL_0000: ldarg.0
		IL_0001: ldfld class LinkedListNode LinkedListNode::'<Next>k__BackingField'
		IL_0006: ret
	} // end of method LinkedListNode::get_Next

	.method public hidebysig specialname 
		instance void set_Next (
			class LinkedListNode 'value'
		) cil managed 
	{
		.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
			01 00 00 00
		)
		// Method begins at RVA 0x20ab
		// Header size: 1
		// Code size: 8 (0x8)
		.maxstack 8

		IL_0000: ldarg.0
		IL_0001: ldarg.1
		IL_0002: stfld class LinkedListNode LinkedListNode::'<Next>k__BackingField'
		IL_0007: ret
	} // end of method LinkedListNode::set_Next

	.method public hidebysig specialname rtspecialname 
		instance void .ctor () cil managed 
	{
		// Method begins at RVA 0x20b4
		// Header size: 1
		// 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 LinkedListNode::.ctor

	// Properties
	.property instance object Data()
	{
		.get instance object LinkedListNode::get_Data()
		.set instance void LinkedListNode::set_Data(object)
	}
	.property instance class LinkedListNode Next()
	{
		.get instance class LinkedListNode LinkedListNode::get_Next()
		.set instance void LinkedListNode::set_Next(class LinkedListNode)
	}

} // end of class LinkedListNode

I think this would have really worked well in the previous article to showcase how C# builds out one class entity to support multiple data types using the LinkedList at run time. That then constrasts with the Rust example I gave where the Rust compiler will generate a type per use statically at compile time.

I think now that I have a way of grabbing the IL for code on my Ubuntu laptop, I’m going to have a lot more opportunity to dig into what the compiler is doing under the hood. Hopefully, I can gather some insights and use that at work either in optimization, or explaining what’s going on with our code to more junior developers.