Skip to content

Commit

Permalink
Add ILLink option to preserve symbol paths (#103295)
Browse files Browse the repository at this point in the history
This adds ILLink support for `--preserve-symbol-paths`, which is
set when `DeterministicSourcePaths` is true.  This setting will
preserve the original pdb path from the inputs by using a custom
symbol writer (see
dotnet/cecil#183 (comment)).
  • Loading branch information
sbomer authored Jun 17, 2024
1 parent 768ec1e commit e679b59
Show file tree
Hide file tree
Showing 10 changed files with 188 additions and 7 deletions.
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

0 comments on commit e679b59

Please sign in to comment.