Skip to content
Permalink
Browse files

Fix #1676: Add pdb support to powershell and command-line frontends

derived from work by @an-dr-eas-k in #1679
  • Loading branch information
siegfriedpammer committed Dec 10, 2019
1 parent 3f9e748 commit 597ee991a2b483621d08e721722d3cd7d2e26f8a
@@ -3,6 +3,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<IsPackable>true</IsPackable>
<PackAsTool>true</PackAsTool>
<AssemblyName>ilspycmd</AssemblyName>
@@ -12,7 +13,7 @@
<Copyright>Copyright 2011-2019 AlphaSierraPapa</Copyright>
<PackageProjectUrl>https://github.com/icsharpcode/ILSpy/</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageIcon>ILSpyCmdNuGetPackageIcon.png</PackageIcon>
<PackageIcon>ILSpyCmdNuGetPackageIcon.png</PackageIcon>
<RepositoryUrl>https://github.com/icsharpcode/ILSpy/</RepositoryUrl>
<Company />
<AssemblyVersion>6.0.0.0</AssemblyVersion>
@@ -26,6 +27,12 @@
<WarningsAsErrors>NU1605</WarningsAsErrors>
</PropertyGroup>

<ItemGroup>
<Compile Include="..\ICSharpCode.Decompiler.PdbProvider.Cecil\MonoCecilDebugInfoProvider.cs" Link="MonoCecilDebugInfoProvider.cs" />
<Compile Include="..\ILSpy\DebugInfo\PortableDebugInfoProvider.cs" Link="PortableDebugInfoProvider.cs" />
<Compile Include="..\ILSpy\DebugInfo\DebugInfoUtils.cs" Link="DebugInfoUtils.cs" />
</ItemGroup>

<ItemGroup>
<None Include="ILSpyCmdNuGetPackageIcon.png" Pack="true" PackagePath="\" />
</ItemGroup>
@@ -45,6 +52,7 @@
<PackageReference Include="System.Runtime.Handles" Version="4.3.0" />
<PackageReference Include="System.Runtime.InteropServices" Version="4.3.0" />
<PackageReference Include="System.Text.Encoding.Extensions" Version="4.3.0" />
<PackageReference Include="Mono.Cecil" Version="0.10.3" />
</ItemGroup>

</Project>
@@ -12,6 +12,7 @@
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using ICSharpCode.Decompiler.DebugInfo;
using ICSharpCode.Decompiler.PdbProvider;
// ReSharper disable All

namespace ICSharpCode.Decompiler.Console
@@ -45,9 +46,13 @@ class ILSpyCmdProgram
[Option("-il|--ilcode", "Show IL code.", CommandOptionType.NoValue)]
public bool ShowILCodeFlag { get; }

[Option("-d|--debuginfo", "Generate PDB.", CommandOptionType.NoValue)]
[Option("-genpdb", "Generate PDB.", CommandOptionType.NoValue)]
public bool CreateDebugInfoFlag { get; }

[FileExistsOrNull]
[Option("-usepdb", "Use PDB.", CommandOptionType.SingleOrNoValue)]
public (bool IsSet, string Value) InputPDBFile { get; }

[Option("-l|--list <entity-type(s)>", "Lists all entities of the specified type(s). Valid types: c(lass), i(interface), s(truct), d(elegate), e(num)", CommandOptionType.MultipleValue)]
public string[] EntityTypes { get; } = new string[0];

@@ -142,7 +147,9 @@ CSharpDecompiler GetDecompiler(string assemblyFileName)
foreach (var path in ReferencePaths) {
resolver.AddSearchDirectory(path);
}
return new CSharpDecompiler(assemblyFileName, resolver, GetSettings());
return new CSharpDecompiler(assemblyFileName, resolver, GetSettings()) {
DebugInfoProvider = TryLoadPDB(module)
};
}

int ListContent(string assemblyFileName, TextWriter output, ISet<TypeKind> kinds)
@@ -175,6 +182,7 @@ int DecompileAsProject(string assemblyFileName, string outputDirectory)
resolver.AddSearchDirectory(path);
}
decompiler.AssemblyResolver = resolver;
decompiler.DebugInfoProvider = TryLoadPDB(module);
decompiler.DecompileProject(module, outputDirectory);
return 0;
}
@@ -211,5 +219,16 @@ int GeneratePdbForAssembly(string assemblyFileName, string pdbFileName, CommandL

return 0;
}

IDebugInfoProvider TryLoadPDB(PEFile module)
{
if (InputPDBFile.IsSet) {
if (InputPDBFile.Value == null)
return DebugInfoUtils.LoadSymbols(module);
return DebugInfoUtils.FromFile(module, InputPDBFile.Value);
}

return null;
}
}
}
@@ -1,19 +1,38 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.IO;

namespace ICSharpCode.Decompiler.Console
{
[AttributeUsage(AttributeTargets.Class)]
public class ProjectOptionRequiresOutputDirectoryValidationAttribute : ValidationAttribute
public sealed class ProjectOptionRequiresOutputDirectoryValidationAttribute : ValidationAttribute
{
public ProjectOptionRequiresOutputDirectoryValidationAttribute()
{
}

protected override ValidationResult IsValid(object value, ValidationContext context)
{
if (value is ILSpyCmdProgram obj) {
if (obj.CreateCompilableProjectFlag && String.IsNullOrEmpty(obj.OutputDirectory)) {
if (obj.CreateCompilableProjectFlag && string.IsNullOrEmpty(obj.OutputDirectory)) {
return new ValidationResult("--project cannot be used unless --outputdir is also specified");
}
}
return ValidationResult.Success;
}
}

[AttributeUsage(AttributeTargets.Property)]
public sealed class FileExistsOrNullAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext context)
{
var s = value as string;
if (string.IsNullOrEmpty(s))
return ValidationResult.Success;
if (File.Exists(s))
return ValidationResult.Success;
return new ValidationResult($"File '{s}' does not exist!");
}
}
}
@@ -30,7 +30,7 @@
using Mono.Cecil.Pdb;
using SRM = System.Reflection.Metadata;

namespace ICSharpCode.Decompiler.PdbProvider.Cecil
namespace ICSharpCode.Decompiler.PdbProvider
{
public class MonoCecilDebugInfoProvider : IDebugInfoProvider
{
@@ -1,8 +1,12 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Management.Automation;
using System.Reflection.PortableExecutable;
using System.Text;
using ICSharpCode.Decompiler.CSharp;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.PdbProvider;

namespace ICSharpCode.Decompiler.PowerShell
{
@@ -24,18 +28,25 @@ public class GetDecompilerCmdlet : PSCmdlet
[Parameter(HelpMessage = "Remove dead code")]
public bool RemoveDeadCode { get; set; }

[Parameter(HelpMessage = "Use PDB")]
public string PDBFilePath { get; set; }

protected override void ProcessRecord()
{
string path = GetUnresolvedProviderPathFromPSPath(LiteralPath);

try {
var module = new PEFile(LiteralPath, new FileStream(LiteralPath, FileMode.Open, FileAccess.Read), PEStreamOptions.Default);
var debugInfo = DebugInfoUtils.FromFile(module, PDBFilePath);
var decompiler = new CSharpDecompiler(path, new DecompilerSettings(LanguageVersion) {
ThrowOnAssemblyResolveErrors = false,
RemoveDeadCode = RemoveDeadCode,
RemoveDeadStores = RemoveDeadStores
RemoveDeadStores = RemoveDeadStores,
UseDebugSymbols = debugInfo != null,
ShowDebugInfo = debugInfo != null,
});
decompiler.DebugInfoProvider = debugInfo;
WriteObject(decompiler);

} catch (Exception e) {
WriteVerbose(e.ToString());
WriteError(new ErrorRecord(e, ErrorIds.AssemblyLoadFailed, ErrorCategory.OperationStopped, null));
@@ -2,13 +2,21 @@

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<RootNamespace>ICSharpCode.Decompiler.PowerShell</RootNamespace>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="PowerShellStandard.Library" Version="5.1.0" />
<PackageReference Include="ICSharpCode.Decompiler" Version="6.0.0.5420-preview1" />
<PackageReference Include="Mono.Cecil" Version="0.10.3" />
</ItemGroup>

<ItemGroup>
<Compile Include="..\ICSharpCode.Decompiler.PdbProvider.Cecil\MonoCecilDebugInfoProvider.cs" Link="MonoCecilDebugInfoProvider.cs" />
<Compile Include="..\ILSpy\DebugInfo\PortableDebugInfoProvider.cs" Link="PortableDebugInfoProvider.cs" />
<Compile Include="..\ILSpy\DebugInfo\DebugInfoUtils.cs" Link="DebugInfoUtils.cs" />
</ItemGroup>

</Project>
@@ -0,0 +1,113 @@
// Copyright (c) 2018 Siegfried Pammer
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

using System;
using System.IO;
using System.Linq;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using System.Runtime.InteropServices;
using ICSharpCode.Decompiler.DebugInfo;
using ICSharpCode.Decompiler.Metadata;

namespace ICSharpCode.Decompiler.PdbProvider
{
static class DebugInfoUtils
{
public static IDebugInfoProvider LoadSymbols(PEFile module)
{
try {
var reader = module.Reader;
// try to open portable pdb file/embedded pdb info:
if (TryOpenPortablePdb(module, out var provider, out var pdbFileName)) {
return new PortableDebugInfoProvider(pdbFileName, provider);
} else {
// search for pdb in same directory as dll
pdbFileName = Path.Combine(Path.GetDirectoryName(module.FileName), Path.GetFileNameWithoutExtension(module.FileName) + ".pdb");
if (File.Exists(pdbFileName)) {
return new MonoCecilDebugInfoProvider(module, pdbFileName);
}
}
} catch (Exception ex) when (ex is BadImageFormatException || ex is COMException) {
// Ignore PDB load errors
}
return null;
}

public static IDebugInfoProvider FromFile(PEFile module, string pdbFileName)
{
if (string.IsNullOrEmpty(pdbFileName))
return null;

Stream stream = OpenStream(pdbFileName);
if (stream == null)
return null;

if (stream.Read(buffer, 0, buffer.Length) == LegacyPDBPrefix.Length
&& System.Text.Encoding.ASCII.GetString(buffer) == LegacyPDBPrefix) {
stream.Position = 0;
return new MonoCecilDebugInfoProvider(module, pdbFileName);
} else {
stream.Position = 0;
var provider = MetadataReaderProvider.FromPortablePdbStream(stream);
return new PortableDebugInfoProvider(pdbFileName, provider);
}
}

const string LegacyPDBPrefix = "Microsoft C/C++ MSF 7.00";
static readonly byte[] buffer = new byte[LegacyPDBPrefix.Length];

static bool TryOpenPortablePdb(PEFile module, out MetadataReaderProvider provider, out string pdbFileName)
{
provider = null;
pdbFileName = null;
var reader = module.Reader;
foreach (var entry in reader.ReadDebugDirectory()) {
if (entry.IsPortableCodeView) {
return reader.TryOpenAssociatedPortablePdb(module.FileName, OpenStream, out provider, out pdbFileName);
}
if (entry.Type == DebugDirectoryEntryType.CodeView) {
string pdbDirectory = Path.GetDirectoryName(module.FileName);
pdbFileName = Path.Combine(pdbDirectory, Path.GetFileNameWithoutExtension(module.FileName) + ".pdb");
if (File.Exists(pdbFileName)) {
Stream stream = OpenStream(pdbFileName);
if (stream.Read(buffer, 0, buffer.Length) == LegacyPDBPrefix.Length
&& System.Text.Encoding.ASCII.GetString(buffer) == LegacyPDBPrefix) {
return false;
}
stream.Position = 0;
provider = MetadataReaderProvider.FromPortablePdbStream(stream);
return true;
}
}
}
return false;
}

static Stream OpenStream(string fileName)
{
if (!File.Exists(fileName))
return null;
var memory = new MemoryStream();
using (var stream = File.OpenRead(fileName))
stream.CopyTo(memory);
memory.Position = 0;
return memory;
}
}
}
@@ -20,9 +20,8 @@
using System.Collections.Generic;
using System.Reflection.Metadata;
using ICSharpCode.Decompiler.DebugInfo;
using ICSharpCode.Decompiler.Metadata;

namespace ICSharpCode.ILSpy.DebugInfo
namespace ICSharpCode.Decompiler.PdbProvider
{
class PortableDebugInfoProvider : IDebugInfoProvider
{
@@ -135,6 +135,7 @@
<Compile Include="CreateListDialog.xaml.cs">
<DependentUpon>CreateListDialog.xaml</DependentUpon>
</Compile>
<Compile Include="DebugInfo\DebugInfoUtils.cs" />
<Compile Include="DebugInfo\PortableDebugInfoProvider.cs" />
<Compile Include="DebugSteps.xaml.cs">
<DependentUpon>DebugSteps.xaml</DependentUpon>

0 comments on commit 597ee99

Please sign in to comment.
You can’t perform that action at this time.