diff --git a/ICSharpCode.Decompiler.Console/ICSharpCode.Decompiler.Console.csproj b/ICSharpCode.Decompiler.Console/ICSharpCode.Decompiler.Console.csproj index 0b542b97c..8db01c4d5 100644 --- a/ICSharpCode.Decompiler.Console/ICSharpCode.Decompiler.Console.csproj +++ b/ICSharpCode.Decompiler.Console/ICSharpCode.Decompiler.Console.csproj @@ -3,6 +3,7 @@ Exe netcoreapp2.1 + true true true ilspycmd @@ -12,7 +13,7 @@ Copyright 2011-2019 AlphaSierraPapa https://github.com/icsharpcode/ILSpy/ MIT - ILSpyCmdNuGetPackageIcon.png + ILSpyCmdNuGetPackageIcon.png https://github.com/icsharpcode/ILSpy/ 6.0.0.0 @@ -26,6 +27,12 @@ NU1605 + + + + + + @@ -45,6 +52,7 @@ + diff --git a/ICSharpCode.Decompiler.Console/IlspyCmdProgram.cs b/ICSharpCode.Decompiler.Console/IlspyCmdProgram.cs index 02fdcdf23..3821f042f 100644 --- a/ICSharpCode.Decompiler.Console/IlspyCmdProgram.cs +++ b/ICSharpCode.Decompiler.Console/IlspyCmdProgram.cs @@ -12,6 +12,7 @@ using System.Threading; 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 @@ Remarks: [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 ", "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 @@ Remarks: 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 kinds) @@ -175,6 +182,7 @@ Remarks: resolver.AddSearchDirectory(path); } decompiler.AssemblyResolver = resolver; + decompiler.DebugInfoProvider = TryLoadPDB(module); decompiler.DecompileProject(module, outputDirectory); return 0; } @@ -211,5 +219,16 @@ Remarks: 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; + } } } diff --git a/ICSharpCode.Decompiler.Console/ValidationAttributes.cs b/ICSharpCode.Decompiler.Console/ValidationAttributes.cs index 802382bbf..a593b1674 100644 --- a/ICSharpCode.Decompiler.Console/ValidationAttributes.cs +++ b/ICSharpCode.Decompiler.Console/ValidationAttributes.cs @@ -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!"); + } + } } diff --git a/ICSharpCode.Decompiler.PdbProvider.Cecil/MonoCecilDebugInfoProvider.cs b/ICSharpCode.Decompiler.PdbProvider.Cecil/MonoCecilDebugInfoProvider.cs index 0b8f5a383..9f79d0c73 100644 --- a/ICSharpCode.Decompiler.PdbProvider.Cecil/MonoCecilDebugInfoProvider.cs +++ b/ICSharpCode.Decompiler.PdbProvider.Cecil/MonoCecilDebugInfoProvider.cs @@ -30,7 +30,7 @@ using Mono.Cecil; using Mono.Cecil.Pdb; using SRM = System.Reflection.Metadata; -namespace ICSharpCode.Decompiler.PdbProvider.Cecil +namespace ICSharpCode.Decompiler.PdbProvider { public class MonoCecilDebugInfoProvider : IDebugInfoProvider { diff --git a/ICSharpCode.Decompiler.PowerShell/GetDecompilerCmdlet.cs b/ICSharpCode.Decompiler.PowerShell/GetDecompilerCmdlet.cs index a268c3c42..30616b253 100644 --- a/ICSharpCode.Decompiler.PowerShell/GetDecompilerCmdlet.cs +++ b/ICSharpCode.Decompiler.PowerShell/GetDecompilerCmdlet.cs @@ -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 @@ namespace ICSharpCode.Decompiler.PowerShell [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)); diff --git a/ICSharpCode.Decompiler.PowerShell/ICSharpCode.Decompiler.PowerShell.csproj b/ICSharpCode.Decompiler.PowerShell/ICSharpCode.Decompiler.PowerShell.csproj index f976d5fd5..d092cc8f9 100644 --- a/ICSharpCode.Decompiler.PowerShell/ICSharpCode.Decompiler.PowerShell.csproj +++ b/ICSharpCode.Decompiler.PowerShell/ICSharpCode.Decompiler.PowerShell.csproj @@ -2,6 +2,7 @@ netstandard2.0 + true true ICSharpCode.Decompiler.PowerShell @@ -9,6 +10,13 @@ + + + + + + + diff --git a/ILSpy/DebugInfo/DebugInfoUtils.cs b/ILSpy/DebugInfo/DebugInfoUtils.cs new file mode 100644 index 000000000..f52a790d7 --- /dev/null +++ b/ILSpy/DebugInfo/DebugInfoUtils.cs @@ -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; + } + } +} diff --git a/ILSpy/DebugInfo/PortableDebugInfoProvider.cs b/ILSpy/DebugInfo/PortableDebugInfoProvider.cs index c76cc2977..b811ce681 100644 --- a/ILSpy/DebugInfo/PortableDebugInfoProvider.cs +++ b/ILSpy/DebugInfo/PortableDebugInfoProvider.cs @@ -20,9 +20,8 @@ using System; 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 { diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj index a785aad71..163bdc228 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -135,6 +135,7 @@ CreateListDialog.xaml + DebugSteps.xaml diff --git a/ILSpy/LoadedAssembly.cs b/ILSpy/LoadedAssembly.cs index 78b3e1854..46fe60622 100644 --- a/ILSpy/LoadedAssembly.cs +++ b/ILSpy/LoadedAssembly.cs @@ -28,10 +28,9 @@ using System.Threading; using System.Threading.Tasks; using ICSharpCode.Decompiler.DebugInfo; using ICSharpCode.Decompiler.Metadata; -using ICSharpCode.Decompiler.PdbProvider.Cecil; +using ICSharpCode.Decompiler.PdbProvider; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem.Implementation; -using ICSharpCode.ILSpy.DebugInfo; using ICSharpCode.ILSpy.Options; namespace ICSharpCode.ILSpy @@ -167,7 +166,7 @@ namespace ICSharpCode.ILSpy if (DecompilerSettingsPanel.CurrentDecompilerSettings.UseDebugSymbols) { try { - LoadSymbols(module); + debugInfoProvider = DebugInfoUtils.LoadSymbols(module); } catch (IOException) { } catch (UnauthorizedAccessException) { } catch (InvalidOperationException) { @@ -180,70 +179,6 @@ namespace ICSharpCode.ILSpy return module; } - void 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)) { - debugInfoProvider = new PortableDebugInfoProvider(pdbFileName, provider); - } else { - // search for pdb in same directory as dll - string pdbDirectory = Path.GetDirectoryName(fileName); - pdbFileName = Path.Combine(pdbDirectory, Path.GetFileNameWithoutExtension(fileName) + ".pdb"); - if (File.Exists(pdbFileName)) { - debugInfoProvider = new MonoCecilDebugInfoProvider(module, pdbFileName); - return; - } - - // TODO: use symbol cache, get symbols from microsoft - } - } catch (Exception ex) when (ex is BadImageFormatException || ex is COMException) { - // Ignore PDB load errors - } - } - - const string LegacyPDBPrefix = "Microsoft C/C++ MSF 7.00"; - byte[] buffer = new byte[LegacyPDBPrefix.Length]; - - 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(fileName, OpenStream, out provider, out pdbFileName); - } - if (entry.Type == DebugDirectoryEntryType.CodeView) { - string pdbDirectory = Path.GetDirectoryName(fileName); - pdbFileName = Path.Combine(pdbDirectory, Path.GetFileNameWithoutExtension(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; - } - - 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; - } - [ThreadStatic] static int assemblyLoadDisableCount;