Troubles getting IL on Linux
Posted on January 18, 2023 by Michael Keane GallowayIn 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
-DecompiledSource $decompiler -TypeName $c.FullName
Get}
# 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
[System.Runtime]System.Object
extends {
// Methods
.method public hidebysig static
[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
valuetype ) 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>
)
: nop
IL_0000: ldarg.0
IL_0001: ldfld !0 valuetype [System.Runtime]System.ValueTuple`2<float64, float64>::Item1
IL_0002: ldarg.1
IL_0007: ldfld !0 valuetype [System.Runtime]System.ValueTuple`2<float64, float64>::Item1
IL_0008: add
IL_000d: ldc.r8 2
IL_000e: div
IL_0017: ldarg.0
IL_0018: ldfld !1 valuetype [System.Runtime]System.ValueTuple`2<float64, float64>::Item2
IL_0019: ldarg.1
IL_001e: ldfld !1 valuetype [System.Runtime]System.ValueTuple`2<float64, float64>::Item2
IL_001f: add
IL_0024: ldc.r8 2
IL_0025: div
IL_002e: newobj instance void valuetype [System.Runtime]System.ValueTuple`2<float64, float64>::.ctor(!0, !1)
IL_002f: stloc.0
IL_0034: br.s IL_0037
IL_0035
: ldloc.0
IL_0037: ret
IL_0038} // 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
[System.Runtime]System.Attribute
extends {
.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
void .ctor () cil managed
instance {
// Method begins at RVA 0x2050
// Header size: 1
// Code size: 8 (0x8)
.maxstack 8
: ldarg.0
IL_0000: call instance void [System.Runtime]System.Attribute::.ctor()
IL_0001: nop
IL_0006: ret
IL_0007} // end of method EmbeddedAttribute::.ctor
} // end of class Microsoft.CodeAnalysis.EmbeddedAttribute
.class private auto ansi sealed beforefieldinit System.Runtime.CompilerServices.NullableAttribute
[System.Runtime]System.Attribute
extends {
.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
75 6c 74 69 70 6c 65 00 54 02 09 49 6e 68 65
4d 72 69 74 65 64 00
)
// Fields
.field public initonly uint8[] NullableFlags
// Methods
.method public hidebysig specialname rtspecialname
void .ctor (
instance
uint8 '') cil managed
{
// Method begins at RVA 0x2059
// Header size: 1
// Code size: 24 (0x18)
.maxstack 8
: ldarg.0
IL_0000: call instance void [System.Runtime]System.Attribute::.ctor()
IL_0001: nop
IL_0006: ldarg.0
IL_0007: ldc.i4.1
IL_0008: newarr [System.Runtime]System.Byte
IL_0009: dup
IL_000e: ldc.i4.0
IL_000f: ldarg.1
IL_0010: stelem.i1
IL_0011: stfld uint8[] System.Runtime.CompilerServices.NullableAttribute::NullableFlags
IL_0012: ret
IL_0017} // end of method NullableAttribute::.ctor
.method public hidebysig specialname rtspecialname
void .ctor (
instance [] ''
uint8) cil managed
{
// Method begins at RVA 0x2072
// Header size: 1
// Code size: 15 (0xf)
.maxstack 8
: ldarg.0
IL_0000: call instance void [System.Runtime]System.Attribute::.ctor()
IL_0001: nop
IL_0006: ldarg.0
IL_0007: ldarg.1
IL_0008: stfld uint8[] System.Runtime.CompilerServices.NullableAttribute::NullableFlags
IL_0009: ret
IL_000e} // end of method NullableAttribute::.ctor
} // end of class System.Runtime.CompilerServices.NullableAttribute
.class private auto ansi sealed beforefieldinit System.Runtime.CompilerServices.NullableContextAttribute
[System.Runtime]System.Attribute
extends {
.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
75 6c 74 69 70 6c 65 00 54 02 09 49 6e 68 65
4d 72 69 74 65 64 00
)
// Fields
.field public initonly uint8 Flag
// Methods
.method public hidebysig specialname rtspecialname
void .ctor (
instance
uint8 '') cil managed
{
// Method begins at RVA 0x2082
// Header size: 1
// Code size: 15 (0xf)
.maxstack 8
: ldarg.0
IL_0000: call instance void [System.Runtime]System.Attribute::.ctor()
IL_0001: nop
IL_0006: ldarg.0
IL_0007: ldarg.1
IL_0008: stfld uint8 System.Runtime.CompilerServices.NullableContextAttribute::Flag
IL_0009: ret
IL_000e} // end of method NullableContextAttribute::.ctor
} // end of class System.Runtime.CompilerServices.NullableContextAttribute
.class private auto ansi beforefieldinit LinkedListNode
[System.Runtime]System.Object
extends {
.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
object get_Data () cil managed
instance {
.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
: ldarg.0
IL_0000: ldfld object LinkedListNode::'<Data>k__BackingField'
IL_0001: ret
IL_0006} // end of method LinkedListNode::get_Data
.method public hidebysig specialname
void set_Data (
instance 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
: ldarg.0
IL_0000: ldarg.1
IL_0001: stfld object LinkedListNode::'<Data>k__BackingField'
IL_0002: ret
IL_0007} // end of method LinkedListNode::set_Data
.method public hidebysig specialname
class LinkedListNode get_Next () cil managed
instance {
.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
: ldarg.0
IL_0000: ldfld class LinkedListNode LinkedListNode::'<Next>k__BackingField'
IL_0001: ret
IL_0006} // end of method LinkedListNode::get_Next
.method public hidebysig specialname
void set_Next (
instance 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
: ldarg.0
IL_0000: ldarg.1
IL_0001: stfld class LinkedListNode LinkedListNode::'<Next>k__BackingField'
IL_0002: ret
IL_0007} // end of method LinkedListNode::set_Next
.method public hidebysig specialname rtspecialname
void .ctor () cil managed
instance {
// Method begins at RVA 0x20b4
// Header size: 1
// Code size: 8 (0x8)
.maxstack 8
: ldarg.0
IL_0000: call instance void [System.Runtime]System.Object::.ctor()
IL_0001: nop
IL_0006: ret
IL_0007} // 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.