Skip to content

Instantly share code, notes, and snippets.

@sheepla
Last active March 17, 2024 07:40
Show Gist options
  • Save sheepla/f1a12603e3ef3ed6798b806e1ccb9f0c to your computer and use it in GitHub Desktop.
Save sheepla/f1a12603e3ef3ed6798b806e1ccb9f0c to your computer and use it in GitHub Desktop.
ILSpyをLinuxで使う.md

🕶️ .NETの逆コンパイラ「ILSpy」をLinuxで使う

ILSpyとは

ILSpyは.NETのアセンブリブラウザおよび逆コンパイラ。 C#やF#などの言語で書かれたアプリケーションのDLLを解析してIL (.NETの中間表現)やC#のコードに逆コンパイルすることができる。開発時のデバッグやリバースエンジニアリングなどに使われる。

icsharpcode/ILSpy

環境

  • Arch Linux x64

CLIツール(ilspycmd)を使う

インストールには、.NET Runtime 6.0および.NET SDKが必要。

sudo pacman -S dotnet-runtime-6.0 dotnet-sdk
dotnet tool install -g ilspycmd
$ ilspycmd --help

ilspycmd: 8.2.0.7535
ICSharpCode.Decompiler: 8.2.0.7535

dotnet tool for decompiling .NET assemblies and generating portable PDBs

Usage: ilspycmd [options] <Assembly file name(s)>

Arguments:
  Assembly file name(s)            The list of assemblies that is being
                                   decompiled. This argument is mandatory.

Options:
  -v|--version                     Show version of ICSharpCode.Decompiler used.
  -h|--help                        Show help information.
  -o|--outputdir <directory>       The output directory, if omitted decompiler
                                   output is written to standard out.
  -p|--project                     Decompile assembly as compilable project.
                                   This requires the output directory option.
  -t|--type <type-name>            The fully qualified name of the type to
                                   decompile.
  -il|--ilcode                     Show IL code.
  --il-sequence-points             Show IL with sequence points. Implies -il.
  -genpdb|--generate-pdb           Generate PDB.
  -usepdb|--use-varnames-from-pdb  Use variable names from PDB.
  -l|--list <entity-type(s)>       Lists all entities of the specified type(s).
                                   Valid types: c(lass), i(nterface), s(truct),
                                   d(elegate), e(num)
  -lv|--languageversion <version>  C# Language version: CSharp1, CSharp2,
                                   CSharp3, CSharp4, CSharp5, CSharp6, CSharp7,
                                   CSharp7_1, CSharp7_2, CSharp7_3, CSharp8_0,
                                   CSharp9_0, CSharp10_0, Preview or Latest
                                   Allowed values are: CSharp1, CSharp2,
                                   CSharp3, CSharp4, CSharp5, CSharp6, CSharp7,
                                   CSharp7_1, CSharp7_2, CSharp7_3, CSharp8_0,
                                   CSharp9_0, CSharp10_0, CSharp11_0, Preview,
                                   Latest.
                                   Default value is: Latest.
  -r|--referencepath <path>        Path to a directory containing dependencies
                                   of the assembly that is being decompiled.
  --no-dead-code                   Remove dead code.
  --no-dead-stores                 Remove dead stores.
  -d|--dump-package                Dump package assemblies into a folder. This
                                   requires the output directory option.
  --nested-directories             Use nested directories for namespaces.
  --disable-updatecheck            If using ilspycmd in a tight loop or fully
                                   automated scenario, you might want to disable
                                   the automatic update check.

Remarks:
  -o is valid with every option and required when using -p.

Examples:
    Decompile assembly to console out.
        ilspycmd sample.dll

    Decompile assembly to destination directory (single C# file).
        ilspycmd -o c:\decompiled sample.dll

    Decompile assembly to destination directory, create a project file, one source file per type.
        ilspycmd -p -o c:\decompiled sample.dll

    Decompile assembly to destination directory, create a project file, one source file per type, 
    into nicely nested directories.
        ilspycmd --nested-directories -p -o c:\decompiled sample.dll

試しにF#のHelloWorldを逆コンパイルしてみる。

$ cd "$(mktemp -d)"

$ dotent new condole -lang=F#

$ ls
 bin/   obj/   Program.fs   tmp.qVus8o47GS.fsproj
$ dotnet publish
MSBuild のバージョン 17.8.5+b5265ef37 (.NET)
  Determining projects to restore...
  All projects are up-to-date for restore.
  tmp.qVus8o47GS -> /tmp/tmp.qVus8o47GS/bin/Release/net8.0/tmp.qVus8o47GS.dll
  tmp.qVus8o47GS -> /tmp/tmp.qVus8o47GS/bin/Release/net8.0/publish/

基本的な使い方としてはDLLファイル(.NETアセンブリ)のパスを引数に指定して起動すればよい。

$ ilspycmd ./bin/Release/net8.0/tmp.qVus8o47GS.dll

実行するとこのようなC#のソースが出力される。

using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using <StartupCode$tmp-qVus8o47GS>;
using Microsoft.FSharp.Core;

[assembly: FSharpInterfaceDataVersion(2, 0, 0)]
[assembly: TargetFramework(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")]
[assembly: AssemblyCompany("tmp.qVus8o47GS")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("tmp.qVus8o47GS")]
[assembly: AssemblyTitle("tmp.qVus8o47GS")]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default)]
[assembly: AssemblyVersion("1.0.0.0")]
[CompilationMapping(SourceConstructFlags.Module)]
public static class Program
{
	[CompilationMapping(SourceConstructFlags.Value)]
	internal static PrintfFormat<Unit, TextWriter, Unit, Unit> format@1 => $Program.format@1;
}
namespace <StartupCode$tmp-qVus8o47GS>
{
	internal static class $Program
	{
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		internal static PrintfFormat<Unit, TextWriter, Unit, Unit> format@1;

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		[CompilerGenerated]
		[DebuggerNonUserCode]
		internal static int init@;

		public static void main@()
		{
			format@1 = new PrintfFormat<Unit, TextWriter, Unit, Unit, Unit>("Hello from F#");
			PrintfModule.PrintFormatLineToTextWriter(Console.Out, Program.format@1);
		}
	}
}
namespace <StartupCode$tmp-qVus8o47GS>.$Tmp.qVus8o47GS
{
	internal static class AssemblyInfo
	{
	}
}
namespace <StartupCode$tmp-qVus8o47GS>.$.NETCoreApp,Version=v8.0
{
	internal static class AssemblyAttributes
	{
	}
}

また、-ilオプションを付けるとC#ではなくILを出力させることができる。

$ ilspycmd -il ./bin/Release/net8.0/tmp.qVus8o47GS.dll
// IL code: tmp.qVus8o47GS
.class private auto ansi '<Module>'
	extends [System.Runtime]System.Object
{
} // end of class <Module>

.class public auto ansi abstract sealed Program
	extends [System.Runtime]System.Object
{
	.custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = (
		01 00 07 00 00 00 00 00
	)
	// Methods
	.method assembly specialname static 
		class [FSharp.Core]Microsoft.FSharp.Core.PrintfFormat`4<class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [System.Runtime]System.IO.TextWriter, class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [FSharp.Core]Microsoft.FSharp.Core.Unit> get_format@1 () cil managed 
	{
		// Method begins at RVA 0x2050
		// Header size: 1
		// Code size: 6 (0x6)
		.maxstack 8

		IL_0000: ldsfld class [FSharp.Core]Microsoft.FSharp.Core.PrintfFormat`4<class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [System.Runtime]System.IO.TextWriter, class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [FSharp.Core]Microsoft.FSharp.Core.Unit> '<StartupCode$tmp-qVus8o47GS>.$Program'::format@1
		IL_0005: ret
	} // end of method Program::get_format@1

	// Properties
	.property class [FSharp.Core]Microsoft.FSharp.Core.PrintfFormat`4<class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [System.Runtime]System.IO.TextWriter, class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [FSharp.Core]Microsoft.FSharp.Core.Unit> format@1()
	{
		.custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = (
			01 00 09 00 00 00 00 00
		)
		.get class [FSharp.Core]Microsoft.FSharp.Core.PrintfFormat`4<class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [System.Runtime]System.IO.TextWriter, class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [FSharp.Core]Microsoft.FSharp.Core.Unit> Program::get_format@1()
	}

} // end of class Program

.class private auto ansi abstract sealed '<StartupCode$tmp-qVus8o47GS>.$Program'
	extends [System.Runtime]System.Object
{
	// Fields
	.field assembly static class [FSharp.Core]Microsoft.FSharp.Core.PrintfFormat`4<class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [System.Runtime]System.IO.TextWriter, class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [FSharp.Core]Microsoft.FSharp.Core.Unit> format@1
	.custom instance void [System.Runtime]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [System.Runtime]System.Diagnostics.DebuggerBrowsableState) = (
		01 00 00 00 00 00 00 00
	)
	.field assembly static int32 init@
	.custom instance void [System.Runtime]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [System.Runtime]System.Diagnostics.DebuggerBrowsableState) = (
		01 00 00 00 00 00 00 00
	)
	.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
		01 00 00 00
	)
	.custom instance void [System.Runtime]System.Diagnostics.DebuggerNonUserCodeAttribute::.ctor() = (
		01 00 00 00
	)

	// Methods
	.method public static 
		void main@ () cil managed 
	{
		// Method begins at RVA 0x2058
		// Header size: 1
		// Code size: 32 (0x20)
		.maxstack 8
		.entrypoint

		IL_0000: ldstr "Hello from F#"
		IL_0005: newobj instance void class [FSharp.Core]Microsoft.FSharp.Core.PrintfFormat`5<class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [System.Runtime]System.IO.TextWriter, class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [FSharp.Core]Microsoft.FSharp.Core.Unit>::.ctor(string)
		IL_000a: stsfld class [FSharp.Core]Microsoft.FSharp.Core.PrintfFormat`4<class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [System.Runtime]System.IO.TextWriter, class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [FSharp.Core]Microsoft.FSharp.Core.Unit> '<StartupCode$tmp-qVus8o47GS>.$Program'::format@1
		IL_000f: call class [netstandard]System.IO.TextWriter [netstandard]System.Console::get_Out()
		IL_0014: call class [FSharp.Core]Microsoft.FSharp.Core.PrintfFormat`4<class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [System.Runtime]System.IO.TextWriter, class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [FSharp.Core]Microsoft.FSharp.Core.Unit> Program::get_format@1()
		IL_0019: call !!0 [FSharp.Core]Microsoft.FSharp.Core.PrintfModule::PrintFormatLineToTextWriter<class [FSharp.Core]Microsoft.FSharp.Core.Unit>(class [System.Runtime]System.IO.TextWriter, class [FSharp.Core]Microsoft.FSharp.Core.PrintfFormat`4<!!0, class [System.Runtime]System.IO.TextWriter, class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [FSharp.Core]Microsoft.FSharp.Core.Unit>)
		IL_001e: pop
		IL_001f: ret
	} // end of method $Program::main@

} // end of class <StartupCode$tmp-qVus8o47GS>.$Program

.class private auto ansi abstract sealed '<StartupCode$tmp-qVus8o47GS>.$Tmp.qVus8o47GS.AssemblyInfo'
	extends [System.Runtime]System.Object
{
} // end of class <StartupCode$tmp-qVus8o47GS>.$Tmp.qVus8o47GS.AssemblyInfo

.class private auto ansi abstract sealed '<StartupCode$tmp-qVus8o47GS>.$.NETCoreApp,Version=v8.0.AssemblyAttributes'
	extends [System.Runtime]System.Object
{
} // end of class <StartupCode$tmp-qVus8o47GS>.$.NETCoreApp,Version=v8.0.AssemblyAttributes

便利!

GUIフロントエンド付きのILSpy(AvaloniaILSpy)を使う

Avalonia UIというクロスプラットフォームのGUIツールキットを使用したAvaloniaILSpyというGUIフロントエンドがある。

icsharpcode/AvaloniaILSpy

インストールするには、リリースページからアセットをダウンロードし、ILSpyを起動する。

curl -sLO 'https://github.com/icsharpcode/AvaloniaILSpy/releases/download/v7.2-rc/Linux.x64.Release.zip'
7z x Linux.x64.Release.zip
7z x ILSpy-linux-x64-Release.zip
chmod a+x ./artifacts/linux-x64/ILSpy
./artifacts/linux-x64/ILSpy

System.InvalidOperationException: Default font family name can't be null or empty. Trhown when executing というエラーが出て起動できないことがある。

AvaloniaILSpy - Issue #84

Avalonia - Issue #4427

DejaVuフォントをインストールしてLANGを英語に設定してからILSpyの実行ファイルを叩いたら起動できた。

sudo pacman -S ttf-dejavu
LANG=en_US.UTF8 ./artifacts/linux-x64/ILSpy
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment