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
13 changes: 13 additions & 0 deletions eng/Subsets.props
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@
<SubsetName Include="Tools.CdacDumpTests" OnDemand="true" Description="Dump-based integration tests for the diagnostic data contract reader." />
<SubsetName Include="Tools.CdacStressTests" OnDemand="true" Description="Stress integration tests for the diagnostic data contract reader." />
<SubsetName Include="Tools.ILAsm" OnDemand="true" Description="Build and package the ilasm tool." />
<SubsetName Include="Tools.ILDAsm" OnDemand="true" Description="Build and package the managed ildasm tool." />

<!-- Host -->
<SubsetName Include="Host" Description="The .NET hosts, packages, hosting libraries, and tests. Equivalent to: $(DefaultHostSubsets)" />
Expand Down Expand Up @@ -622,6 +623,18 @@
Category="clr" />
</ItemGroup>

<!-- Not part of clr.tools or clr.packages: Microsoft.NETCore.ILDAsm still ships the native ildasm until the managed one reaches parity. -->
<ItemGroup Condition="$(_subset.Contains('+tools.ildasm+'))">
<ProjectToBuild Include="$(ToolsProjectRoot)ildasm\src\ILDisassembler\ILDisassembler.csproj" Category="tools" />
<ProjectToBuild Include="$(ToolsProjectRoot)ildasm\tests\ILDisassembler.Tests\ILDisassembler.Tests.csproj" Category="tools" Test="true" />
<ProjectToBuild Include="$(ToolsProjectRoot)ildasm\src\ildasm\ildasm.csproj" Category="tools" />
<ProjectToBuild Include="$(ToolsProjectRoot)ildasm\src\ildasm\ildasm_inbuild.csproj"
Condition="'$(BuildHostILTools)' == 'true' and '$(TargetRid)' != '$(NETCoreSdkRuntimeIdentifier)'"
Category="tools" />
<ProjectToBuild Include="$(CoreClrProjectRoot).nuget\Microsoft.NETCore.ILDAsm\Microsoft.NETCore.ILDAsm.Package.csproj"
Category="tools" />
</ItemGroup>

<ItemGroup Condition="$(_subset.Contains('+clr.nativecorelib+'))">
<ProjectToBuild Include="$(CoreClrProjectRoot)crossgen-corelib.proj" Category="clr" />
</ItemGroup>
Expand Down
4 changes: 3 additions & 1 deletion eng/pipelines/coreclr/ilasm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ pr:
- src/coreclr/ilasm/*
- src/coreclr/ildasm/*
- src/tools/ilasm/*
- src/tools/ildasm/*
- src/coreclr/.nuget/Microsoft.NETCore.ILAsm/*
- src/coreclr/.nuget/Microsoft.NETCore.ILDAsm/*
- src/coreclr/.nuget/package-rids.props
- src/tasks/installer.tasks/*
- eng/NativeAotToolPackage.targets
Expand Down Expand Up @@ -57,7 +59,7 @@ extends:
- windows_x64
jobParameters:
nameSuffix: ILAsm
buildArgs: -s tools.ilasm -c $(_BuildConfig) -pack -test
buildArgs: -s tools.ilasm+tools.ildasm -c $(_BuildConfig) -pack -test
timeoutInMinutes: 120
enablePublishTestResults: true
testResultsFormat: 'xunit'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<Project>
<PropertyGroup>
<UsePkgProjTargets>false</UsePkgProjTargets>
</PropertyGroup>
<Import Project="Sdk.props" Sdk="Microsoft.Build.NoTargets" />

<PropertyGroup Condition="'$(BuildIdentityPackage)' == ''">
<BuildIdentityPackage>true</BuildIdentityPackage>
<BuildIdentityPackage Condition="'$(BuildingAnOfficialBuildLeg)' == 'true' and ('$(OS)' != 'Windows_NT' or '$(TargetArchitecture)' != 'x64')">false</BuildIdentityPackage>
</PropertyGroup>

<PropertyGroup>
<IsPackable>true</IsPackable>
<PackageId>Microsoft.NETCore.ILDAsm</PackageId>
<PackageDescription>The .NET IL Disassembler.</PackageDescription>
<IncludeBuildOutput>false</IncludeBuildOutput>
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
<BeforePack>$(BeforePack);GenerateILDAsmRuntimeDependencies</BeforePack>
</PropertyGroup>

<Import Project="..\package-rids.props" />

<ItemGroup>
<None Include="$(IntermediateOutputPath)runtime.json" Pack="true" PackagePath="/" />
</ItemGroup>

<UsingTask TaskName="GenerateRuntimeDependencies"
AssemblyFile="$(InstallerTasksAssemblyPath)"
Runtime="NET"
TaskFactory="TaskHostFactory" />

<Target Name="GenerateILDAsmRuntimeDependencies">
<MakeDir Directories="$(IntermediateOutputPath)" />
<GenerateRuntimeDependencies RuntimeIdentifiers="@(BuildRID)"
PackageId="$(PackageId)"
PackageVersion="$(PackageVersion)"
OutputPath="$(IntermediateOutputPath)runtime.json" />
</Target>

<Import Project="Sdk.targets" Sdk="Microsoft.Build.NoTargets" />
</Project>
1 change: 1 addition & 0 deletions src/coreclr/.nuget/Microsoft.NETCore.ILDAsm/PACKAGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The .NET IL Disassembler.
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();
}
}
11 changes: 11 additions & 0 deletions src/tools/ildasm/src/ILDisassembler/ILDisassembler.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">

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

</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