Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions eng/Subsets.props
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,9 @@

<ProjectToBuild Include="$(CoreClrProjectRoot)tools\aot\ILCompiler\ILCompiler_publish.csproj" Condition="'$(SdkToolsSupported)' == 'true'" Category="clr" />
<ProjectToBuild Include="$(CoreClrProjectRoot)tools\aot\crossgen2\crossgen2_publish.csproj" Condition="'$(SdkToolsSupported)' == 'true'" Category="clr" />

<ProjectToBuild Include="$(ToolsProjectRoot)ilasm\src\ilasm\ilasm.csproj" Condition="'$(SdkToolsSupported)' == 'true'" Category="clr" />
<ProjectToBuild Include="$(ToolsProjectRoot)ildasm\src\ildasm\ildasm.csproj" Condition="'$(SdkToolsSupported)' == 'true'" Category="clr" />

<ProjectToBuild Include="$(CoreClrProjectRoot)tools\aot\ILCompiler\ILCompiler.csproj" Condition="'$(SdkToolsSupported)' == 'true'" Category="clr" />
<ProjectToBuild Include="$(CoreClrProjectRoot)tools\aot\crossgen2\crossgen2.csproj" Condition="'$(SdkToolsSupported)' == 'true'" Category="clr" />
Expand Down Expand Up @@ -555,6 +557,8 @@
<ItemGroup Condition="$(_subset.Contains('+tools.ilasm+'))">
<ProjectToBuild Include="$(ToolsProjectRoot)ilasm\src\ILAssembler\ILAssembler.csproj" Category="tools" />
<ProjectToBuild Include="$(ToolsProjectRoot)ilasm\tests\ILAssembler.Tests\ILAssembler.Tests.csproj" Category="tools" Test="true" />
<ProjectToBuild Include="$(ToolsProjectRoot)ildasm\src\ILDisassembler\ILDisassembler.csproj" Category="tools" />
<ProjectToBuild Include="$(ToolsProjectRoot)ildasm\tests\ILDisassembler.Tests\ILDisassembler.Tests.csproj" Category="tools" Test="true" />
</ItemGroup>

<ItemGroup Condition="$(_subset.Contains('+clr.nativecorelib+'))">
Expand Down
1 change: 1 addition & 0 deletions eng/pipelines/coreclr/ilasm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pr:
- src/coreclr/ilasm/*
- src/coreclr/ildasm/*
- src/tools/ilasm/*
- src/tools/ildasm/*

schedules:
- cron: "0 19 * * 6"
Expand Down
7 changes: 3 additions & 4 deletions src/tests/Common/CoreRootArtifacts.targets
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,9 @@
<!-- XUnit runner harness assemblies that we don't want to mix in with the framework in Core_Root -->
<RunTimeArtifactsIncludeFolders Include="xunit/" TargetDir="xunit/" />

<!-- Managed ilasm for testing -->
<RunTimeArtifactsIncludeFolders Include="managed-ilasm/" TargetDir="managed-ilasm/">
<IncludeSubFolders>True</IncludeSubFolders>
</RunTimeArtifactsIncludeFolders>
<!-- Managed ilasm/ildasm for testing -->
<RunTimeArtifactsIncludeFolders Include="managed-ilasm/" TargetDir="managed-ilasm/" />
<RunTimeArtifactsIncludeFolders Include="managed-ildasm/" TargetDir="managed-ildasm/" />
</ItemGroup>

<!-- Glob CoreCLR artifacts into _CoreRootArtifactSource items -->
Expand Down
5 changes: 5 additions & 0 deletions src/tools/ildasm/ildasm.slnx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<Solution>
<Project Path="src/ildasm/ildasm.csproj" />
<Project Path="src/ILDisassembler/ILDisassembler.csproj" />
<Project Path="tests/ILDisassembler.Tests/ILDisassembler.Tests.csproj" />
</Solution>
154 changes: 154 additions & 0 deletions src/tools/ildasm/src/ILDisassembler/Disassembler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.IO;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;

namespace ILDisassembler;

/// <summary>
/// Main entry point for IL disassembly.
/// Reads a PE file and outputs IL assembly syntax.
/// </summary>
public sealed class Disassembler : IDisposable
{
private readonly PEReader _peReader;
private readonly MetadataReader _metadataReader;
private readonly string _filePath;
private readonly Options _options;

public Disassembler(string filePath, Options? options = null)
{
_filePath = filePath;
// TODO: Wire up options to control disassembly behavior (ShowBytes, ShowTokens, etc.)
_options = options ?? new Options();
var stream = File.OpenRead(filePath);
// PEReader takes ownership of the stream and will dispose it when PEReader is disposed
_peReader = new PEReader(stream);

if (!_peReader.HasMetadata)
{
throw new InvalidOperationException("PE file does not contain metadata");
}

_metadataReader = _peReader.GetMetadataReader();
}

public Disassembler(Stream stream, Options? options = null)
{
_filePath = "<stream>";
_options = options ?? new Options();
_peReader = new PEReader(stream);

if (!_peReader.HasMetadata)
{
throw new InvalidOperationException("PE file does not contain metadata");
}

_metadataReader = _peReader.GetMetadataReader();
}

/// <summary>
/// Disassembles the PE file to the specified output.
/// </summary>
public void Disassemble(TextWriter output)
{
var writer = new ILWriter(output);

// Write header comment
writer.WriteComment($".NET IL Disassembler. Version {typeof(Disassembler).Assembly.GetName().Version}");
writer.WriteLine();
writer.WriteComment($"Metadata version: {_metadataReader.MetadataVersion}");

// TODO: Support --headers option to output PE headers (.imagebase, .file alignment, .stackreserve, .corflags, .subsystem)
// TODO: Support --html option to wrap output in HTML tags
// TODO: Support --rtf option to wrap output in RTF format

// Disassemble in order:
// 1. Assembly extern references
WriteAssemblyRefs(writer);

// 2. Assembly definition
WriteAssemblyDef(writer);

// 3. Module definition
WriteModuleDef(writer);

// TODO: Output manifest resources (.mresource)
// TODO: Output file references (.file)
// TODO: Output exported types (.class extern)

// 4. Type definitions (classes, interfaces, etc.)
WriteTypeDefs(writer);
}

private void WriteAssemblyRefs(ILWriter writer)
{
foreach (var handle in _metadataReader.AssemblyReferences)
{
var assemblyRef = _metadataReader.GetAssemblyReference(handle);
var name = _metadataReader.GetString(assemblyRef.Name);
var version = assemblyRef.Version;

writer.WriteLine($".assembly extern {name}");
writer.WriteLine("{");
// TODO: Output .publickeytoken if present
// TODO: Output .culture if present
// TODO: Output .hash if present
writer.WriteLine($" .ver {version.Major}:{version.Minor}:{version.Build}:{version.Revision}");
writer.WriteLine("}");
}
}

private void WriteAssemblyDef(ILWriter writer)
{
if (!_metadataReader.IsAssembly)
{
return;
}

var assemblyDef = _metadataReader.GetAssemblyDefinition();
var name = _metadataReader.GetString(assemblyDef.Name);
var version = assemblyDef.Version;

writer.WriteLine($".assembly {name}");
writer.WriteLine("{");
// TODO: Output .publickey if present
// TODO: Output .culture if present
// TODO: Output .hash algorithm if present
// TODO: Output custom attributes on assembly
writer.WriteLine($" .ver {version.Major}:{version.Minor}:{version.Build}:{version.Revision}");
writer.WriteLine("}");
}

private void WriteModuleDef(ILWriter writer)
{
var moduleDef = _metadataReader.GetModuleDefinition();
var name = _metadataReader.GetString(moduleDef.Name);

writer.WriteLine($".module {name}");
writer.WriteComment($"MVID: {{{_metadataReader.GetGuid(moduleDef.Mvid)}}}");
// TODO: Output custom attributes on module
}

private void WriteTypeDefs(ILWriter writer)
{
// TODO: Support --typelist option to output full type list for round-trip
// TODO: Support --classlist option to output class list
// TODO: Support --visibility/--pubonly options to filter types
// TODO: Support --item option to disassemble specific item only
foreach (var handle in _metadataReader.TypeDefinitions)
{
var typeDef = _metadataReader.GetTypeDefinition(handle);
var typeWriter = new TypeDisassembler(_metadataReader, _peReader, typeDef);
typeWriter.Write(writer);
}
}

public void Dispose()
{
_peReader.Dispose();
}
}
16 changes: 16 additions & 0 deletions src/tools/ildasm/src/ILDisassembler/ILDisassembler.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>$(NetCoreAppToolCurrent)</TargetFramework>
<EnableDefaultCompileItems>true</EnableDefaultCompileItems>
<Nullable>enable</Nullable>
<IsAotCompatible>true</IsAotCompatible>
<IsShipping>false</IsShipping>
</PropertyGroup>

<!--
This library uses System.Reflection.Metadata and System.Reflection.PortableExecutable
which are inbox in the target framework, no additional packages needed.
-->

</Project>
55 changes: 55 additions & 0 deletions src/tools/ildasm/src/ILDisassembler/ILWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.IO;

namespace ILDisassembler;

/// <summary>
/// Helper class for writing IL assembly output with proper formatting.
/// </summary>
internal sealed class ILWriter
{
private readonly TextWriter _output;
private int _indentLevel;
private const string IndentString = " ";

public ILWriter(TextWriter output)
{
_output = output;
}

public void Indent() => _indentLevel++;

public void Dedent() => _indentLevel = _indentLevel > 0 ? _indentLevel - 1 : 0;

public void WriteLine()
{
_output.WriteLine();
}

public void WriteLine(string line)
{
WriteIndent();
_output.WriteLine(line);
}

public void Write(string text)
{
_output.Write(text);
}

public void WriteComment(string comment)
{
WriteIndent();
_output.WriteLine($"// {comment}");
}

public void WriteIndent()
{
for (int i = 0; i < _indentLevel; i++)
{
_output.Write(IndentString);
}
}
}
Loading
Loading