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 ILLink option to preserve symbol paths #103295

Merged
merged 5 commits into from
Jun 17, 2024
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
1 change: 1 addition & 0 deletions eng/illink.targets
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@
<!-- trim the target assembly -->
<ILLinkArgs>$(ILLinkArgs) --action link $(TargetName)</ILLinkArgs>
<ILLinkArgs Condition="'$(ILLinkRewritePDBs)' == 'true' and Exists('$(ILLinkTrimAssemblySymbols)')">$(ILLinkArgs) -b true</ILLinkArgs>
<ILLinkArgs Condition="'$(ILLinkRewritePDBs)' == 'true' and Exists('$(ILLinkTrimAssemblySymbols)') and '$(DeterministicSourcePaths)' == 'true'">$(ILLinkArgs) --preserve-symbol-paths</ILLinkArgs>
<!-- pass the non-embedded descriptors xml file on the command line -->
<ILLinkArgs Condition="'$(ILLinkDescriptorsLibraryBuildXml)' != ''">$(ILLinkArgs) -x "$(ILLinkDescriptorsLibraryBuildXml)"</ILLinkArgs>
<ILLinkArgs Condition="'$(ILLinkSubstitutionsLibraryBuildXml)' != ''">$(ILLinkArgs) --substitutions "$(ILLinkSubstitutionsLibraryBuildXml)"</ILLinkArgs>
Expand Down
10 changes: 10 additions & 0 deletions src/tools/illink/src/ILLink.Tasks/LinkTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,13 @@ public class ILLink : ToolTask
public bool RemoveSymbols { set => _removeSymbols = value; }
bool? _removeSymbols;

/// <summary>
/// Preserve original path to debug symbols from each assembly's debug header.
/// Maps to '--preserve-symbol-paths' if true.
/// Default if not specified is to write out the full path to the pdb in the debug header.
/// </summary>
public bool PreserveSymbolPaths { get; set; }

/// <summary>
/// Sets the default action for trimmable assemblies.
/// Maps to '--trim-mode'
Expand Down Expand Up @@ -474,6 +481,9 @@ protected override string GenerateResponseFileCommands ()
if (_removeSymbols == false)
args.AppendLine ("-b");

if (PreserveSymbolPaths)
args.AppendLine ("--preserve-symbol-paths");

if (CustomSteps != null) {
foreach (var customStep in CustomSteps) {
args.Append ("--custom-step ");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ Copyright (c) .NET Foundation. All rights reserved.
TrimMode="$(TrimMode)"
DefaultAction="$(_TrimmerDefaultAction)"
RemoveSymbols="$(TrimmerRemoveSymbols)"
PreserveSymbolPaths="$(_TrimmerPreserveSymbolPaths)"
FeatureSettings="@(_TrimmerFeatureSettings)"
CustomData="@(_TrimmerCustomData)"

Expand Down Expand Up @@ -237,6 +238,11 @@ Copyright (c) .NET Foundation. All rights reserved.
<TrimmerRemoveSymbols Condition=" '$(DebuggerSupport)' != 'false' ">false</TrimmerRemoveSymbols>
</PropertyGroup>

<PropertyGroup>
<_TrimmerPreserveSymbolPaths Condition=" '$(_TrimmerPreserveSymbolPaths)' == '' and '$(DeterministicSourcePaths)' == 'true' ">true</_TrimmerPreserveSymbolPaths>
<_TrimmerPreserveSymbolPaths Condition=" '$(_TrimmerPreserveSymbolPaths)' == '' ">false</_TrimmerPreserveSymbolPaths>
</PropertyGroup>

<!-- Set IsTrimmable for any assemblies that already have customized TrimMode. -->
<ItemGroup>
<ManagedAssemblyToLink Condition=" '%(ManagedAssemblyToLink.TrimMode)' != '' ">
Expand Down
14 changes: 13 additions & 1 deletion src/tools/illink/src/linker/CompatibilitySuppressions.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<!-- https://learn.microsoft.com/dotnet/fundamentals/package-validation/diagnostic-ids -->
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Suppression>
Expand Down Expand Up @@ -1503,12 +1503,24 @@
<Left>ref/net9.0/illink.dll</Left>
<Right>lib/net9.0/illink.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Mono.Linker.LinkContext.get_PreserveSymbolPaths</Target>
<Left>ref/net9.0/illink.dll</Left>
<Right>lib/net9.0/illink.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Mono.Linker.LinkContext.set_KeepComInterfaces(System.Boolean)</Target>
<Left>ref/net9.0/illink.dll</Left>
<Right>lib/net9.0/illink.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Mono.Linker.LinkContext.set_PreserveSymbolPaths(System.Boolean)</Target>
<Left>ref/net9.0/illink.dll</Left>
<Right>lib/net9.0/illink.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Mono.Linker.LinkContext</Target>
Expand Down
1 change: 1 addition & 0 deletions src/tools/illink/src/linker/Linker.Steps/OutputStep.cs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ WriterParameters SaveSymbols (AssemblyDefinition assembly)
return parameters;

parameters.WriteSymbols = true;
parameters.SymbolWriterProvider = new CustomSymbolWriterProvider (Context.PreserveSymbolPaths);
return parameters;
}

Expand Down
120 changes: 120 additions & 0 deletions src/tools/illink/src/linker/Linker/CustomSymbolWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.IO;
using System.Text;

using Mono.Cecil;
using Mono.Cecil.Cil;

namespace Mono.Linker
{
internal sealed class CustomSymbolWriterProvider : ISymbolWriterProvider
{
readonly DefaultSymbolWriterProvider _defaultProvider = new DefaultSymbolWriterProvider ();
readonly bool _preserveSymbolPaths;

public CustomSymbolWriterProvider (bool preserveSymbolPaths) => this._preserveSymbolPaths = preserveSymbolPaths;

public ISymbolWriter GetSymbolWriter (ModuleDefinition module, string fileName)
=> new CustomSymbolWriter (_defaultProvider.GetSymbolWriter (module, fileName), module, _preserveSymbolPaths);

public ISymbolWriter GetSymbolWriter (ModuleDefinition module, Stream symbolStream)
=> new CustomSymbolWriter (_defaultProvider.GetSymbolWriter (module, symbolStream), module, _preserveSymbolPaths);
}

internal sealed class CustomSymbolWriter : ISymbolWriter
{
// ASCII "RSDS": https://github.com/dotnet/runtime/blob/main/docs/design/specs/PE-COFF.md#codeview-debug-directory-entry-type-2
const int CodeViewSignature = 0x53445352;

readonly ISymbolWriter _symbolWriter;
readonly ModuleDefinition _module;
readonly bool _preserveSymbolPaths;

internal CustomSymbolWriter (ISymbolWriter defaultWriter, ModuleDefinition module, bool preserveSymbolPaths)
{
_symbolWriter = defaultWriter;
_module = module;
_preserveSymbolPaths = preserveSymbolPaths;
}

public ImageDebugHeader GetDebugHeader ()
{
var header = _symbolWriter.GetDebugHeader ();
if (!_preserveSymbolPaths)
return header;

if (!header.HasEntries)
return header;

for (int i = 0; i < header.Entries.Length; i++) {
header.Entries [i] = ProcessEntry (header.Entries [i]);
}

return header;
}

ImageDebugHeaderEntry ProcessEntry (ImageDebugHeaderEntry entry)
{
if (entry.Directory.Type != ImageDebugType.CodeView)
return entry;

var reader = new BinaryReader (new MemoryStream (entry.Data));
var newDataStream = new MemoryStream ();
var writer = new BinaryWriter (newDataStream);

var sig = reader.ReadUInt32 ();
if (sig != CodeViewSignature)
return entry;

writer.Write (sig);
writer.Write (reader.ReadBytes (16)); // MVID
writer.Write (reader.ReadUInt32 ()); // Age

writer.Write (Encoding.UTF8.GetBytes (GetOriginalPdbPath ()));
writer.Write ((byte) 0);

var newData = newDataStream.ToArray ();

var directory = entry.Directory;
directory.SizeOfData = newData.Length;

return new ImageDebugHeaderEntry (directory, newData);
}

string GetOriginalPdbPath ()
{
if (!_module.HasDebugHeader)
return string.Empty;

var debugHeader = _module.GetDebugHeader ();
foreach (var entry in debugHeader.Entries) {
if (entry.Directory.Type != ImageDebugType.CodeView)
continue;

var reader = new BinaryReader (new MemoryStream (entry.Data));
var sig = reader.ReadUInt32 ();
if (sig != CodeViewSignature)
return string.Empty;

var stream = reader.BaseStream;
stream.Seek (16 + 4, SeekOrigin.Current); // MVID and Age
// Pdb path is NUL-terminated path at offset 24.
// https://github.com/dotnet/runtime/blob/main/docs/design/specs/PE-COFF.md#codeview-debug-directory-entry-type-2
return Encoding.UTF8.GetString (
reader.ReadBytes ((int) (stream.Length - stream.Position - 1))); // remaining length - ending \0
}

return string.Empty;
}

public ISymbolReaderProvider GetReaderProvider () => _symbolWriter.GetReaderProvider ();

public void Write (MethodDebugInformation info) => _symbolWriter.Write (info);

public void Write () => _symbolWriter.Write ();

public void Dispose () => _symbolWriter.Dispose ();
}
}
19 changes: 13 additions & 6 deletions src/tools/illink/src/linker/Linker/Driver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,12 @@ protected int SetupContext (ILogger? customLogger = null)
continue;
}

case "--preserve-symbol-paths":
if (!GetBoolParam (token, l => context.PreserveSymbolPaths = l))
return -1;

continue;

case "--version":
Version ();
return 1;
Expand Down Expand Up @@ -1333,12 +1339,13 @@ static void Usage ()

Console.WriteLine ();
Console.WriteLine ("Options");
Console.WriteLine (" -d PATH Specify additional directory to search in for assembly references");
Console.WriteLine (" -reference FILE Specify additional file location used to resolve assembly references");
Console.WriteLine (" -b Update debug symbols for all modified files. Defaults to false");
Console.WriteLine (" -out PATH Specify the output directory. Defaults to 'output'");
Console.WriteLine (" -h Lists all {0} options", _linker);
Console.WriteLine (" @FILE Read response file for more options");
Console.WriteLine (" -d PATH Specify additional directory to search in for assembly references");
Console.WriteLine (" -reference FILE Specify additional file location used to resolve assembly references");
Console.WriteLine (" -b Update debug symbols for all modified files. Defaults to false");
Console.WriteLine (" --preserve-symbol-paths Preserve debug header paths to pdb files. Defaults to false");
Console.WriteLine (" -out PATH Specify the output directory. Defaults to 'output'");
Console.WriteLine (" -h Lists all {0} options", _linker);
Console.WriteLine (" @FILE Read response file for more options");

Console.WriteLine ();
Console.WriteLine ("Actions");
Expand Down
2 changes: 2 additions & 0 deletions src/tools/illink/src/linker/Linker/LinkContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ public Pipeline Pipeline {

public bool LinkSymbols { get; set; }

public bool PreserveSymbolPaths { get; set; }

public bool KeepComInterfaces { get; set; }

public bool KeepMembersForDebugger { get; set; } = true;
Expand Down
21 changes: 21 additions & 0 deletions src/tools/illink/test/ILLink.Tasks.Tests/ILLink.Tasks.Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,27 @@ public void TestRemoveSymbolsDefault ()
}
}

[Theory]
[InlineData (true)]
[InlineData (false)]
public void TestPreserveSymbolPaths (bool preserveSymbolPaths)
{
var task = new MockTask () {
PreserveSymbolPaths = preserveSymbolPaths
};
using (var driver = task.CreateDriver ()) {
Assert.Equal (preserveSymbolPaths, driver.Context.PreserveSymbolPaths);
}
}

[Fact]
public void TestPreserveSymbolPathsDefault ()
{
var task = new MockTask ();
using (var driver = task.CreateDriver ()) {
Assert.False (driver.Context.PreserveSymbolPaths);
}
}

[Fact]
public void TestKeepCustomMetadata ()
Expand Down
1 change: 1 addition & 0 deletions src/tools/illink/test/ILLink.Tasks.Tests/Mock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public void SetOptimization (string optimization, bool enabled)
static readonly string[] nonOptimizationBooleanProperties = new string[] {
"DumpDependencies",
"RemoveSymbols",
"PreserveSymbolPaths",
"TreatWarningsAsErrors",
"SingleWarn"
};
Expand Down
Loading