Skip to content

Commit

Permalink
Add ILStrip task to trim method bodies (dotnet#57359)
Browse files Browse the repository at this point in the history
This is used for iOS when assemblies are FullAOT compiled since the method body IL is unnecessary there.
  • Loading branch information
akoeplinger authored Aug 13, 2021
1 parent 3042bd7 commit 7706e34
Show file tree
Hide file tree
Showing 8 changed files with 325 additions and 1 deletion.
4 changes: 4 additions & 0 deletions eng/Version.Details.xml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@
<Uri>https://github.com/dotnet/runtime-assets</Uri>
<Sha>cc70e46d909f35a576abe245219c1df31160bbd6</Sha>
</Dependency>
<Dependency Name="Microsoft.DotNet.CilStrip.Sources" Version="6.0.0-beta.21411.3">
<Uri>https://github.com/dotnet/runtime-assets</Uri>
<Sha>cc70e46d909f35a576abe245219c1df31160bbd6</Sha>
</Dependency>
<Dependency Name="runtime.linux-arm64.Microsoft.NETCore.Runtime.Mono.LLVM.Sdk" Version="11.1.0-alpha.1.21409.1">
<Uri>https://github.com/dotnet/llvm-project</Uri>
<Sha>30296e71234ffb3eb8da789d36adf4db146e6602</Sha>
Expand Down
1 change: 1 addition & 0 deletions eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@
<SystemRuntimeTimeZoneDataVersion>6.0.0-beta.21411.3</SystemRuntimeTimeZoneDataVersion>
<SystemSecurityCryptographyX509CertificatesTestDataVersion>6.0.0-beta.21411.3</SystemSecurityCryptographyX509CertificatesTestDataVersion>
<SystemWindowsExtensionsTestDataVersion>6.0.0-beta.21411.3</SystemWindowsExtensionsTestDataVersion>
<MicrosoftDotNetCilStripSourcesVersion>6.0.0-beta.21411.3</MicrosoftDotNetCilStripSourcesVersion>
<!-- dotnet-optimization dependencies -->
<optimizationwindows_ntx64MIBCRuntimeVersion>1.0.0-prerelease.21411.3</optimizationwindows_ntx64MIBCRuntimeVersion>
<optimizationwindows_ntx86MIBCRuntimeVersion>1.0.0-prerelease.21411.3</optimizationwindows_ntx86MIBCRuntimeVersion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="$(RepoTasksDir)ILStripTask\ILStrip.csproj" />
<ProjectReference Include="$(RepoTasksDir)RuntimeConfigParser\RuntimeConfigParser.csproj" />
<ProjectReference Include="$(RepoTasksDir)JsonToItemsTaskFactory\JsonToItemsTaskFactory.csproj" />
</ItemGroup>
Expand All @@ -14,6 +15,7 @@
<PackageFile Include="Sdk\Sdk.props" TargetPath="Sdk" />
<PackageFile Include="Sdk\Sdk.targets" TargetPath="Sdk" />
<PackageFile Include="build\$(MSBuildProjectName).props" TargetPath="build" />
<PackageFile Include="Sdk\ILStripTask.props" TargetPath="Sdk" />
<PackageFile Include="Sdk\RuntimeConfigParserTask.props" TargetPath="Sdk" />
<PackageFile Include="Sdk\RuntimeComponentManifest.props" TargetPath="Sdk" />
<PackageFile Include="Sdk\RuntimeComponentManifest.targets" TargetPath="Sdk" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<Project>
<PropertyGroup>
<ILStripTasksAssemblyPath Condition="'$(MSBuildRuntimeType)' == 'Core'">$(MSBuildThisFileDirectory)..\tasks\net6.0\ILStrip.dll</ILStripTasksAssemblyPath>
<ILStripTasksAssemblyPath Condition="'$(MSBuildRuntimeType)' != 'Core'">$(MSBuildThisFileDirectory)..\tasks\net472\ILStrip.dll</ILStripTasksAssemblyPath>
</PropertyGroup>
<UsingTask TaskName="ILStrip" AssemblyFile="$(ILStripTasksAssemblyPath)" />
</Project>
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<Project>
<Import Project="$(MSBuildThisFileDirectory)\ILStripTask.props" />
<Import Project="$(MSBuildThisFileDirectory)\RuntimeConfigParserTask.props" />
<Import Project="$(MSBuildThisFileDirectory)\RuntimeComponentManifest.props" />
</Project>
277 changes: 277 additions & 0 deletions src/tasks/ILStripTask/ILStrip.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
// 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.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Mono.Cecil;
using Mono.Cecil.Binary;
using Mono.Cecil.Cil;
using Mono.Cecil.Metadata;

public class ILStrip : Microsoft.Build.Utilities.Task
{
/// <summary>
/// Assemblies to be stripped.
/// The assemblies will be modified in place if OutputPath metadata is not set.
/// </summary>
[Required]
public ITaskItem[] Assemblies { get; set; } = Array.Empty<ITaskItem>();

/// <summary>
/// Disable parallel stripping
/// </summary>
public bool DisableParallelStripping { get; set; }

public override bool Execute()
{
if (Assemblies.Length == 0)
{
throw new ArgumentException($"'{nameof(Assemblies)}' is required.", nameof(Assemblies));
}

if (DisableParallelStripping)
{
foreach (var assemblyItem in Assemblies)
{
if (!StripAssembly(assemblyItem))
return !Log.HasLoggedErrors;
}
}
else
{
Parallel.ForEach(Assemblies,
new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount },
assemblyItem => StripAssembly(assemblyItem));
}

return !Log.HasLoggedErrors;
}

private bool StripAssembly(ITaskItem assemblyItem)
{
string assemblyFile = assemblyItem.ItemSpec;
var outputPath = assemblyItem.GetMetadata("OutputPath");
if (String.IsNullOrWhiteSpace(outputPath))
{
outputPath = assemblyFile;
Log.LogMessage(MessageImportance.Low, $"[ILStrip] {assemblyFile}");
}
else
{
Log.LogMessage(MessageImportance.Low, $"[ILStrip] {assemblyFile} to {outputPath}");
}

try
{
AssemblyDefinition assembly = AssemblyFactory.GetAssembly(assemblyFile);
AssemblyStripper.StripAssembly(assembly, outputPath);
}
catch (Exception ex)
{
Log.LogMessage(MessageImportance.Low, ex.ToString());
Log.LogError($"ILStrip failed for {assemblyFile}: {ex.Message}");
return false;
}

return true;
}

private class AssemblyStripper
{
AssemblyDefinition assembly;
BinaryWriter writer;

Image original;
Image stripped;

ReflectionWriter reflection_writer;
MetadataWriter metadata_writer;

TablesHeap original_tables;
TablesHeap stripped_tables;

AssemblyStripper(AssemblyDefinition assembly, BinaryWriter writer)
{
this.assembly = assembly;
this.writer = writer;
}

void Strip()
{
FullLoad();
ClearMethodBodies();
CopyOriginalImage();
PatchMethods();
PatchFields();
PatchResources();
Write();
}

void FullLoad()
{
assembly.MainModule.FullLoad();
}

void ClearMethodBodies()
{
foreach (TypeDefinition type in assembly.MainModule.Types)
{
ClearMethodBodies(type.Constructors);
ClearMethodBodies(type.Methods);
}
}

static void ClearMethodBodies(ICollection methods)
{
foreach (MethodDefinition method in methods)
{
if (!method.HasBody)
continue;

MethodBody body = new MethodBody(method);
body.CilWorker.Emit(OpCodes.Ret);

method.Body = body;
}
}

void CopyOriginalImage()
{
original = assembly.MainModule.Image;
stripped = Image.CreateImage();

stripped.Accept(new CopyImageVisitor(original));

assembly.MainModule.Image = stripped;

original_tables = original.MetadataRoot.Streams.TablesHeap;
stripped_tables = stripped.MetadataRoot.Streams.TablesHeap;

TableCollection tables = original_tables.Tables;
foreach (IMetadataTable table in tables)
stripped_tables.Tables.Add(table);

stripped_tables.Valid = original_tables.Valid;
stripped_tables.Sorted = original_tables.Sorted;

reflection_writer = new ReflectionWriter(assembly.MainModule);
reflection_writer.StructureWriter = new StructureWriter(assembly, writer);
reflection_writer.CodeWriter.Stripped = true;

metadata_writer = reflection_writer.MetadataWriter;

PatchHeap(metadata_writer.StringWriter, original.MetadataRoot.Streams.StringsHeap);
PatchHeap(metadata_writer.GuidWriter, original.MetadataRoot.Streams.GuidHeap);
PatchHeap(metadata_writer.UserStringWriter, original.MetadataRoot.Streams.UserStringsHeap);
PatchHeap(metadata_writer.BlobWriter, original.MetadataRoot.Streams.BlobHeap);

if (assembly.EntryPoint != null)
metadata_writer.EntryPointToken = assembly.EntryPoint.MetadataToken.ToUInt();
}

static void PatchHeap(MemoryBinaryWriter heap_writer, MetadataHeap heap)
{
if (heap == null)
return;

heap_writer.BaseStream.Position = 0;
heap_writer.Write(heap.Data);
}

void PatchMethods()
{
MethodTable methodTable = (MethodTable)stripped_tables[MethodTable.RId];
if (methodTable == null)
return;

RVA method_rva = RVA.Zero;

for (int i = 0; i < methodTable.Rows.Count; i++)
{
MethodRow methodRow = methodTable[i];

methodRow.ImplFlags |= MethodImplAttributes.NoInlining;

MetadataToken methodToken = MetadataToken.FromMetadataRow(TokenType.Method, i);

MethodDefinition method = (MethodDefinition)assembly.MainModule.LookupByToken(methodToken);

if (method.HasBody)
{
method_rva = method_rva != RVA.Zero
? method_rva
: reflection_writer.CodeWriter.WriteMethodBody(method);

methodRow.RVA = method_rva;
}
else
methodRow.RVA = RVA.Zero;
}
}

void PatchFields()
{
FieldRVATable fieldRvaTable = (FieldRVATable)stripped_tables[FieldRVATable.RId];
if (fieldRvaTable == null)
return;

for (int i = 0; i < fieldRvaTable.Rows.Count; i++)
{
FieldRVARow fieldRvaRow = fieldRvaTable[i];

MetadataToken fieldToken = new MetadataToken(TokenType.Field, fieldRvaRow.Field);

FieldDefinition field = (FieldDefinition)assembly.MainModule.LookupByToken(fieldToken);

fieldRvaRow.RVA = metadata_writer.GetDataCursor();
metadata_writer.AddData(field.InitialValue.Length + 3 & (~3));
metadata_writer.AddFieldInitData(field.InitialValue);
}
}

void PatchResources()
{
ManifestResourceTable resourceTable = (ManifestResourceTable)stripped_tables[ManifestResourceTable.RId];
if (resourceTable == null)
return;

for (int i = 0; i < resourceTable.Rows.Count; i++)
{
ManifestResourceRow resourceRow = resourceTable[i];

if (resourceRow.Implementation.RID != 0)
continue;

foreach (Resource resource in assembly.MainModule.Resources)
{
EmbeddedResource er = resource as EmbeddedResource;
if (er == null)
continue;

if (resource.Name != original.MetadataRoot.Streams.StringsHeap[resourceRow.Name])
continue;

resourceRow.Offset = metadata_writer.AddResource(er.Data);
}
}
}

void Write()
{
stripped.MetadataRoot.Accept(metadata_writer);
}

public static void StripAssembly(AssemblyDefinition assembly, string file)
{
using (FileStream fs = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))
{
new AssemblyStripper(assembly, new BinaryWriter(fs)).Strip();
}
}
}
}
32 changes: 32 additions & 0 deletions src/tasks/ILStripTask/ILStrip.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(TargetFrameworkForNETCoreTasks);$(TargetFrameworkForNETFrameworkTasks)</TargetFrameworks>
<OutputType>Library</OutputType>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<NoWarn>$(NoWarn),CA1050,CS0618,CS0649,CS8604,CS8602,CS8632,SYSLIB0003</NoWarn>
<RunAnalyzers>false</RunAnalyzers>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build" Version="$(RefOnlyMicrosoftBuildVersion)" />
<PackageReference Include="Microsoft.Build.Framework" Version="$(RefOnlyMicrosoftBuildFrameworkVersion)" />
<PackageReference Include="Microsoft.Build.Tasks.Core" Version="$(RefOnlyMicrosoftBuildTasksCoreVersion)" />
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="$(RefOnlyMicrosoftBuildUtilitiesCoreVersion)" />
<PackageReference Include="Microsoft.DotNet.CilStrip.Sources" Version="$(MicrosoftDotNetCilStripSourcesVersion)" />
</ItemGroup>
<ItemGroup>
<Compile Include="ILStrip.cs" />
<Compile Include="..\Common\Utils.cs" />
<Compile Include="$(RepoRoot)src\libraries\System.Private.CoreLib\src\System\Diagnostics\CodeAnalysis\NullableAttributes.cs" Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'" />
</ItemGroup>

<!-- GetFilesToPackage assists to place `ILStrip.dll` in a NuGet package in Microsoft.NET.Runtime.MonoTargets.Sdk.pkgproj for external use -->
<Target Name="GetFilesToPackage" Returns="@(FilesToPackage)">
<ItemGroup>
<_PublishFramework Remove="@(_PublishFramework)" />
<_PublishFramework Include="$(TargetFrameworks)" />

<FilesToPackage Include="$(OutputPath)%(_PublishFramework.Identity)\$(AssemblyName).dll" TargetPath="tasks\%(_PublishFramework.Identity)" />
</ItemGroup>
</Target>
</Project>
2 changes: 1 addition & 1 deletion src/tasks/RuntimeConfigParser/RuntimeConfigParser.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
<Compile Include="$(RepoRoot)src\libraries\System.Private.CoreLib\src\System\Diagnostics\CodeAnalysis\NullableAttributes.cs" Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'" />
</ItemGroup>

<!-- GetFilesToPackage assists to place `RuntimeConfigParser.dll` in a NuGet package in Microsoft.NET.Runtime.RuntimeConfigParser.Task.pkgproj for external use -->
<!-- GetFilesToPackage assists to place `RuntimeConfigParser.dll` in a NuGet package in Microsoft.NET.Runtime.MonoTargets.Sdk.pkgproj for external use -->
<Target Name="GetFilesToPackage" Returns="@(FilesToPackage)">
<ItemGroup>
<_PublishFramework Remove="@(_PublishFramework)" />
Expand Down

0 comments on commit 7706e34

Please sign in to comment.