Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ILStrip task to trim method bodies #57359

Merged
merged 1 commit into from
Aug 13, 2021
Merged
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/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