diff --git a/BummerExe/App.config b/BummerExe/App.config new file mode 100644 index 00000000..193aecc6 --- /dev/null +++ b/BummerExe/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/BummerExe/BummerExe.csproj b/BummerExe/BummerExe.csproj new file mode 100644 index 00000000..42c7eb2f --- /dev/null +++ b/BummerExe/BummerExe.csproj @@ -0,0 +1,66 @@ + + + + + Debug + AnyCPU + {19C457D5-6780-4480-94CB-ED7E055402B8} + Exe + Bummer + Bummer + v4.8 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\FluentCommandLineParser.1.4.3\lib\net35\FluentCommandLineParser.dll + + + ..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll + + + + + + + + + + + + + + + + + + + + + {721fdfc9-6da9-4159-a54e-e448dfb9097d} + BummerLib + + + + \ No newline at end of file diff --git a/BummerExe/Program.cs b/BummerExe/Program.cs new file mode 100644 index 00000000..6a997e29 --- /dev/null +++ b/BummerExe/Program.cs @@ -0,0 +1,89 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) CQSE GmbH +// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using Fclp; +using Newtonsoft.Json; + +namespace Cqse.ConQAT.Dotnet.Bummer +{ + /// + /// Entry point class to the binary-source mapper. + /// + internal class Program + { + + /// + /// The entry point of the program, where the program control + /// starts and ends. + /// + /// The command-line arguments. + public static int Main(string[] args) + { + var commandLineParser = new FluentCommandLineParser(); + + var filenames = new List(); + var assemblyNames = new List(); + + commandLineParser.Setup>('f', "files") + .Callback(items => filenames = items) + .WithDescription("Path(s) to symbol file(s) that should be analyzed.") + .Required(); + commandLineParser.Setup>('a', "assemblyNames") + .Callback(items => assemblyNames = items) + .WithDescription("Assembly name(s) to be used in the AssemblyMethodMapping(s). Must have the same order and number of elements as the files parameter.") + .Required(); + + commandLineParser.SetupHelp("?", "help") + .Callback(text => Console.WriteLine(text)); + + var commandLineParseResult = commandLineParser.Parse(args); + + if (!filenames.Count.Equals(assemblyNames.Count)) + { + Console.WriteLine("The parameters and had different lengths. Aborting analysis."); + return -1; + } + if (!commandLineParseResult.HasErrors) + { + List methodMappings = Bummer.GetMethodMappings(filenames, assemblyNames); + OutputMethodMappings(methodMappings); + } + else + { + Console.WriteLine(commandLineParseResult.ErrorText); + commandLineParser.HelpOption.ShowHelp(commandLineParser.Options); + } + return 0; + } + + /// + /// Prints the specified list of method mappings on the console as JSON. + /// + /// The method mappings to save. + private static void OutputMethodMappings(List methodMappings) + { + var settings = new JsonSerializerSettings(); + settings.Formatting = Formatting.None; + var serializer = JsonSerializer.Create(settings); + serializer.Serialize(Console.Out, methodMappings); + } + } +} diff --git a/BummerExe/Properties/AssemblyInfo.cs b/BummerExe/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..e30427b6 --- /dev/null +++ b/BummerExe/Properties/AssemblyInfo.cs @@ -0,0 +1,7 @@ +using System.Reflection; + +[assembly: AssemblyTitle("Bummer")] +[assembly: AssemblyProduct("Bummer")] +[assembly: AssemblyCompany("CQSE GmbH")] +[assembly: AssemblyCopyright("Copyright © CQSE GmbH")] +[assembly: AssemblyDescription("Creates mappings between binary and source file methods based on .pdb or .mdb symbol files.")] \ No newline at end of file diff --git a/BummerExe/packages.config b/BummerExe/packages.config new file mode 100644 index 00000000..fa3415e1 --- /dev/null +++ b/BummerExe/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/BummerLib/Bummer.cs b/BummerLib/Bummer.cs new file mode 100644 index 00000000..10087dc2 --- /dev/null +++ b/BummerLib/Bummer.cs @@ -0,0 +1,53 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) CQSE GmbH +// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Cqse.ConQAT.Dotnet.Bummer +{ + /// + /// Entry point class to the binary-source mapper. + /// + public class Bummer + { + /// + /// Gets the method mappings for the specified list of file names. + /// + /// The list of symbol filenames to analyze. + public static List GetMethodMappings(List filenames, List assemblyNames) + { + var methodMappings = new List(); + var methodMapper = new MethodMapper(); + foreach (var pair in filenames.Zip(assemblyNames, (filename, assemblyName) => new { filename = filename, assemblyName = assemblyName })) + { + if (!File.Exists(pair.filename)) + { + Console.WriteLine("File does not exist: " + pair.filename); + continue; + } + + methodMappings.Add(methodMapper.GetMethodMappings(pair.filename, pair.assemblyName)); + } + return methodMappings; + } + } +} diff --git a/BummerLib/BummerLib.csproj b/BummerLib/BummerLib.csproj new file mode 100644 index 00000000..a0b8f298 --- /dev/null +++ b/BummerLib/BummerLib.csproj @@ -0,0 +1,100 @@ + + + + + Debug + AnyCPU + {721FDFC9-6DA9-4159-A54E-E448DFB9097D} + Library + Properties + Bummer + BummerLib + v4.8 + 512 + true + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Mono.Cecil.0.10.1\lib\net40\Mono.Cecil.dll + + + ..\packages\Mono.Cecil.0.10.1\lib\net40\Mono.Cecil.Mdb.dll + + + ..\packages\Mono.Cecil.0.10.1\lib\net40\Mono.Cecil.Pdb.dll + + + ..\packages\Mono.Cecil.0.10.1\lib\net40\Mono.Cecil.Rocks.dll + + + + ..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll + + + ..\packages\System.Collections.Immutable.5.0.0\lib\net461\System.Collections.Immutable.dll + + + + ..\packages\System.Memory.4.5.4\lib\net461\System.Memory.dll + + + + ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll + + + ..\packages\System.Reflection.Metadata.5.0.0\lib\net461\System.Reflection.Metadata.dll + + + ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.3\lib\net461\System.Runtime.CompilerServices.Unsafe.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + \ No newline at end of file diff --git a/BummerLib/MethodMapper/IMethodMapper.cs b/BummerLib/MethodMapper/IMethodMapper.cs new file mode 100644 index 00000000..2349da9d --- /dev/null +++ b/BummerLib/MethodMapper/IMethodMapper.cs @@ -0,0 +1,48 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) CQSE GmbH +// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//------------------------------------------------------------------------------ + +namespace Cqse.ConQAT.Dotnet.Bummer +{ + /// + /// Specifies the functionality of classes that can interpret symbol files + /// and provide a mapping between source code and binary code + /// based on the contained methods. + /// + public interface IMethodMapper + { + /// + /// When implemented in a subclass, determines whether this instance + /// can interpret the specified symbol file. + /// + /// true if this instance can interpret the specified + /// symbol file; false, otherwise. + /// Path to a symbol file. + bool CanInterpretSymbolFile(string pathToSymbolFile); + + /// + /// When implemented in a subclass, gets the method mappings that allow + /// to match binary methods to source methods, by interpreting + /// the specified symbol file. + /// + /// The method mappings for the specified symbol file. + /// Path to the symbol file to interpret. + /// Assembly name to use for the generated mappings (usually the filename without extension). + AssemblyMethodMappings GetMethodMappings(string pathToSymbolFile, string assemblyName); + } +} diff --git a/BummerLib/MethodMapper/Mdb/MdbMethodMapper.cs b/BummerLib/MethodMapper/Mdb/MdbMethodMapper.cs new file mode 100644 index 00000000..ae58d5ec --- /dev/null +++ b/BummerLib/MethodMapper/Mdb/MdbMethodMapper.cs @@ -0,0 +1,76 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) CQSE GmbH +// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//------------------------------------------------------------------------------ + +using System.Collections.Generic; +using Mono.CompilerServices.SymbolWriter; + +namespace Cqse.ConQAT.Dotnet.Bummer +{ + /// + /// Method mapper for Mono symbol files (.mdb). + /// + public class MdbMethodMapper : MethodMapperBase + { + /// + /// File extension of Mdb files. + /// + private const string MdbExtension = ".mdb"; + + /// + protected override string GetSupportedExtension() => MdbExtension; + + /// + protected override IEnumerable ReadMethodMappings(string pathToSymbolFile) + { + var symbolFile = MonoSymbolFile.ReadSymbolFile(pathToSymbolFile); + + foreach (MethodEntry methodEntry in symbolFile.Methods) + { + yield return this.GetMapping(methodEntry); + } + } + + /// + /// Provides the method mapping for the specified MDB method. + /// + /// The MDB MethodEntry to get the mapping for. + private MethodMapping GetMapping(MethodEntry mdbMethod) + { + var methodMapping = new MethodMapping() + { + MethodToken = (uint)mdbMethod.Token, + SourceFile = mdbMethod.CompileUnit.SourceFile.FileName + }; + + LineNumberTable lineNumberTable = mdbMethod.GetLineNumberTable(); + if (lineNumberTable != null) + { + LineNumberEntry[] lines = lineNumberTable.LineNumbers; + + if (lines != null && lines.Length > 0) + { + methodMapping.StartLine = (uint)lines[0].Row; + methodMapping.EndLine = (uint)lines[lines.Length - 1].Row; + } + } + + return methodMapping; + } + } +} diff --git a/BummerLib/MethodMapper/MethodMapper.cs b/BummerLib/MethodMapper/MethodMapper.cs new file mode 100644 index 00000000..40a7fe6b --- /dev/null +++ b/BummerLib/MethodMapper/MethodMapper.cs @@ -0,0 +1,87 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) CQSE GmbH +// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//------------------------------------------------------------------------------ + +using System; + +namespace Cqse.ConQAT.Dotnet.Bummer +{ + /// + /// + /// Given a debug symbol file, + /// provides a mapping from methods in IL assembly to source code lines. + /// + /// + /// This class is a bridge to different concrete implementations + /// of method mappers. + /// Currently, it supports Mono debug files (.mdb) and + /// Microsoft .NET symbol files (.pdb). + /// + /// + public class MethodMapper : IMethodMapper + { + /// + /// Holds an instance of + /// . + /// + /// The MDB method mapper. + private MdbMethodMapper MdbMethodMapper { get; } = new MdbMethodMapper(); + + /// + /// Holds an instance of + /// . + /// + /// The PDB method mapper. + private PdbMethodMapper PdbMethodMapper { get; } = new PdbMethodMapper(); + + /// + /// Determines whether the specified symbol file can be interpreted. + /// + /// true, if any of the concrete method mappers can + /// interpret the specified symbol file; + /// false, otherwise. + /// Path to a symbol file. + public bool CanInterpretSymbolFile(string pathToSymbolFile) + => MdbMethodMapper.CanInterpretSymbolFile(pathToSymbolFile) + || PdbMethodMapper.CanInterpretSymbolFile(pathToSymbolFile); + + /// + /// Gets the method mappings that allow to match binary methods to + /// source methods, by forwarding the specified symbol file to + /// a suitable method mapper. + /// + /// The method mappings for the specified symbol file. + /// Path to the symbol file to interpret. + public AssemblyMethodMappings GetMethodMappings(string pathToSymbolFile, string assemblyName) + { + if (this.MdbMethodMapper.CanInterpretSymbolFile(pathToSymbolFile)) + { + return this.MdbMethodMapper.GetMethodMappings(pathToSymbolFile, assemblyName); + } + + if (this.PdbMethodMapper.CanInterpretSymbolFile(pathToSymbolFile)) + { + return this.PdbMethodMapper.GetMethodMappings(pathToSymbolFile, assemblyName); + } + + throw new ArgumentException( + "Symbol file " + pathToSymbolFile + " not supported.", + "pathToSymbolFile"); + } + } +} diff --git a/BummerLib/MethodMapper/MethodMapperBase.cs b/BummerLib/MethodMapper/MethodMapperBase.cs new file mode 100644 index 00000000..88a4a3e3 --- /dev/null +++ b/BummerLib/MethodMapper/MethodMapperBase.cs @@ -0,0 +1,70 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) CQSE GmbH +// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//------------------------------------------------------------------------------ + +using System.Collections.Generic; +using System.IO; + +namespace Cqse.ConQAT.Dotnet.Bummer +{ + /// + /// Base class for concrete method mappers. + /// Breaks down support for specific symbol files to + /// the corresponding extension. + /// Handles setting assembly name and symbol file name for subclasses. + /// + public abstract class MethodMapperBase : IMethodMapper + { + /// + public bool CanInterpretSymbolFile(string pathToSymbolFile) + { + return Path.GetExtension(pathToSymbolFile).ToLower() + .Equals(this.GetSupportedExtension().ToLower()); + } + + /// + public AssemblyMethodMappings GetMethodMappings(string pathToSymbolFile, string assemblyName) + { + var methodMappings = new AssemblyMethodMappings() + { + AssemblyName = assemblyName, + SymbolFileName = pathToSymbolFile + }; + + methodMappings.MethodMappings.AddRange( + this.ReadMethodMappings(pathToSymbolFile)); + return methodMappings; + } + + /// + /// When implemented in a subclass, gets the file extension + /// supported by the concrete method mapper. + /// + /// The file extension supported by the concrete + /// method mapper. + protected abstract string GetSupportedExtension(); + + /// + /// When implemented in a subclass, reads the method mappings + /// from the specified symbol file. + /// + /// The list of method mappings. + /// Path to symbol file. + protected abstract IEnumerable ReadMethodMappings(string pathToSymbolFile); + } +} diff --git a/BummerLib/MethodMapper/Pdb/PdbFile.cs b/BummerLib/MethodMapper/Pdb/PdbFile.cs new file mode 100644 index 00000000..0a42b676 --- /dev/null +++ b/BummerLib/MethodMapper/Pdb/PdbFile.cs @@ -0,0 +1,272 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) CQSE GmbH +// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//------------------------------------------------------------------------------ + +namespace Cqse.ConQAT.Dotnet.Bummer +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Reflection; + using System.Linq; + + /// + /// + /// Adapter for access to Microsoft.Cci.Pdb.PdbFiles. + /// Uses reflection to read Pdb files and load the enclosed functions. + /// Wraps these functions in a corresponding local wrapper class + /// . + /// + /// + /// Wrapping access to the MS class is necessary, because the methods + /// we need to use to interpret Pdb files are marked as internal within the + /// MS assembly. Hence they cannot be accessed using Mono Cecil directly. + /// + /// + public static class PdbFile + { + /// + /// Marks lines that are "hidden" from the compiler. These lines should be ignored when caluclating + /// the start and end line of a function. + /// + public const uint CompilerHiddenLine = 16707566; + + /// + /// Namespace of all Pdb related classes within the Microsoft Pdb assembly. + /// + private const string PdbNamespace = "Microsoft.Cci.Pdb"; + + /// + /// Name of the PdbFile type in the MS assembly. + /// + private const string PdbFileTypeName = PdbNamespace + ".PdbFile"; + + /// + /// Name of the PdbFunction type in the MS assembly. + /// + private const string PdbFunctionTypeName = PdbNamespace + ".PdbFunction"; + + /// + /// Name of the PdbLines type in the MS assembly. + /// + private const string PdbLinesTypeName = PdbNamespace + ".PdbLines"; + + /// + /// Name of the PdbSource type in the MS assembly. + /// + private const string PdbSourceTypeName = PdbNamespace + ".PdbSource"; + + /// + /// Name of the PdbLine type in the MS assembly. + /// + private const string PdbLineTypeName = PdbNamespace + ".PdbLine"; + + /// + /// Name of the LoadFunctions methods of the MS PdbFile type. + /// + private const string PdbFileLoadFunctionsMethodName = "LoadFunctions"; + + /// + /// The name of the token field of the MS PdbFunction type. + /// + private const string PdbFunctionTokenFieldName = "token"; + + /// + /// The name of the lines field of the MS PdbFunction type. + /// + private const string PdbFunctionLinesFieldName = "lines"; + + /// + /// The name of the file field of the MS PdbLines type. + /// + private const string PdbLinesFileFieldName = "file"; + + /// + /// The name of the name field of the MS PdbSource type. + /// + private const string PdbSourceNameFieldName = "name"; + + /// + /// The name of the lines field of the MS PdbLines type. + /// + private const string PdbLinesLinesFieldName = "lines"; + + /// + /// The name of the lineBegin field of the MS PdbLine type. + /// + private const string PdbLineLineBeginFieldName = "lineBegin"; + + /// + /// The name of the lineEnd field of the MS PdbLine type. + /// + private const string PdbLineLineEndFieldName = "lineEnd"; + + /// + /// Mono Cecil Pdb assembly. + /// + private static readonly Assembly MonoCecilPdb = typeof(Mono.Cecil.Pdb.PdbReaderProvider).Assembly; + + /// + /// MS PdbFile type as gathered via reflection. + /// + private static readonly Type PdbFileType = MonoCecilPdb.GetType(PdbFileTypeName); + + /// + /// Method info for the LoadFunctions methods of MS PdbFile type + /// as gathered via reflection. + /// + private static readonly MethodInfo PdbFileLoadFunctionsMethodInfo = + PdbFileType.GetMethod(PdbFileLoadFunctionsMethodName, BindingFlags.Static | BindingFlags.NonPublic); + + /// + /// MS PdbFunction type as gathered via reflection. + /// + private static readonly Type PdbFunctionType = MonoCecilPdb.GetType(PdbFunctionTypeName); + + /// + /// MS PdbLines type as gathered via reflection. + /// + private static readonly Type PdbLinesType = MonoCecilPdb.GetType(PdbLinesTypeName); + + /// + /// MS PdbSource type as gathered via reflection. + /// + private static readonly Type PdbSourceType = MonoCecilPdb.GetType(PdbSourceTypeName); + + /// + /// MS PdbLine type as gathered via reflection. + /// + private static readonly Type PdbLineType = MonoCecilPdb.GetType(PdbLineTypeName); + + /// + /// Binding flags for internal types, methods, or fields. + /// + private static readonly BindingFlags BindingFlagsInternal = + BindingFlags.NonPublic | BindingFlags.Instance; + + /// + /// Loads the Pdb functions contained in the specified stream to a Pdb file. + /// + /// The Pdb functions contained in the specified stream. + /// Stream pointing to a Pdb file. + public static IEnumerable LoadPdbFunctions(Stream pdbStream) + { + Dictionary tokenToSourceMapping = null; + string sourceServerData = null; + const int age = 0; + Guid guid = Guid.Empty; + + object loadedPdbFunctions = PdbFileLoadFunctionsMethodInfo + .Invoke(null, new object[] { pdbStream, tokenToSourceMapping, sourceServerData, age, guid }); + + foreach (object pdbFunction in loadedPdbFunctions as Array) + { + yield return WrapPdbFunction(pdbFunction); + } + } + + /// + /// Wraps the specified MS PdbFunction object into an object of type + /// . + /// + private static PdbFunction WrapPdbFunction(object pdbFunction) + { + var wrappedPdbFunction = new PdbFunction(); + wrappedPdbFunction.Token = + (uint)GetField(pdbFunction, PdbFunctionType, PdbFunctionTokenFieldName); + + var pdbFunctionLines = + GetField(pdbFunction, PdbFunctionType, PdbFunctionLinesFieldName); + if (pdbFunctionLines != null) + { + AddPdbFunctionLines(wrappedPdbFunction, pdbFunctionLines); + } + + return wrappedPdbFunction; + } + + /// + /// Adds the specified PdbFunction lines to the provided function wrapper. + /// + /// The wrapped PdbFunction, the lines + /// should be added to. + /// A PdbLines object containing the + /// source lines of a PdbFunction. + private static void AddPdbFunctionLines(PdbFunction wrappedPdbFunction, object pdbFunctionLines) + { + if (pdbFunctionLines == null || (pdbFunctionLines as Array).Length == 0) + { + return; + } + + object lines = (pdbFunctionLines as Array).GetValue(0); + + var pdbFunctionLinesFile = + GetField(lines, PdbLinesType, PdbLinesFileFieldName); + var pdbFunctionLinesFileName = + GetField(pdbFunctionLinesFile, PdbSourceType, PdbSourceNameFieldName) as string; + wrappedPdbFunction.SourceFilename = pdbFunctionLinesFileName; + + var pdbFunctionLinesLineArray = + GetField(lines, PdbLinesType, PdbLinesLinesFieldName) as Array; + if (pdbFunctionLinesLineArray?.Length > 0) + { + var lineNumbers = FilterAndSortLines(pdbFunctionLinesLineArray); + + if (lineNumbers.Count == 0) + { + wrappedPdbFunction.StartLine = CompilerHiddenLine; + wrappedPdbFunction.EndLine = CompilerHiddenLine; + } + else + { + wrappedPdbFunction.StartLine = lineNumbers.First(); + wrappedPdbFunction.EndLine = lineNumbers.Last(); + } + } + } + + /// + /// Returs the field of the given name from the given object of the given type using reflection. + /// + private static object GetField(object obj, Type type, string fieldName) + { + return type + .GetField(fieldName, BindingFlagsInternal) + .GetValue(obj); + } + + /// + /// Returns the line numbers in the given line array sorted and with compiler hidden lines removed. + /// There are cases where only the first or last few lines of a method are compiler-hidden. + /// + /// The array of PdbLine objects + private static List FilterAndSortLines(Array pdbFunctionLinesLineArray) + { + List lineNumbers = new List(); + foreach (object line in pdbFunctionLinesLineArray) + { + lineNumbers.Add((uint)GetField(line, PdbLineType, PdbLineLineBeginFieldName)); + lineNumbers.Add((uint)GetField(line, PdbLineType, PdbLineLineEndFieldName)); + } + lineNumbers = lineNumbers.FindAll(line => line != CompilerHiddenLine); + lineNumbers.Sort(); + return lineNumbers; + } + } +} diff --git a/BummerLib/MethodMapper/Pdb/PdbFunction.cs b/BummerLib/MethodMapper/Pdb/PdbFunction.cs new file mode 100644 index 00000000..d858ae10 --- /dev/null +++ b/BummerLib/MethodMapper/Pdb/PdbFunction.cs @@ -0,0 +1,67 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) CQSE GmbH +// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//------------------------------------------------------------------------------ + +namespace Cqse.ConQAT.Dotnet.Bummer +{ + /// + /// Wraps a Microsoft.Cci.Pdb.PdbFunction. + /// + public class PdbFunction + { + /// + /// Gets or sets the IL function token. + /// + public uint Token { get; set; } + + /// + /// Gets or sets the filename of the source file + /// where the function is defined. + /// + public string SourceFilename { get; set; } + + /// + /// Gets or sets the start line of the function + /// within its containing file. + /// + public uint StartLine { get; set; } + + /// + /// Gets or sets the end line of the function + /// within its containing file. + /// + public uint EndLine { get; set; } + + /// + public override bool Equals(object obj) + { + PdbFunction other = obj as PdbFunction; + if (other == null) { + return false; + } + + return Token == other.Token && SourceFilename == other.SourceFilename && StartLine == other.StartLine && EndLine == other.EndLine; + } + + /// + public override int GetHashCode() + { + return new { Token, SourceFilename, StartLine, EndLine }.GetHashCode(); + } + } +} diff --git a/BummerLib/MethodMapper/Pdb/PdbMethodMapper.cs b/BummerLib/MethodMapper/Pdb/PdbMethodMapper.cs new file mode 100644 index 00000000..e9f8cb30 --- /dev/null +++ b/BummerLib/MethodMapper/Pdb/PdbMethodMapper.cs @@ -0,0 +1,151 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) CQSE GmbH +// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; + +namespace Cqse.ConQAT.Dotnet.Bummer +{ + /// + /// Method mapper for Microsoft .NET symbol files (.pdb). + /// + public class PdbMethodMapper : MethodMapperBase + { + /// + /// File extension of Pdb files. + /// + private const string PdbExtension = ".pdb"; + + /// + /// First bytes (as string) of a portable PDB. + /// + private const string PortablePdbMagic = "BSJB"; + + /// + /// First bytes (as string) of a native PDB (that is supported by Cecil). + /// + private const string NativePdbMagic = "Microsoft C/C++ MSF 7.00"; + + /// + /// The different string representations of magic bytes of known PDB types. + /// + private static readonly string[] MagicTypes = { NativePdbMagic, PortablePdbMagic }; + + /// + protected override string GetSupportedExtension() => PdbExtension; + + /// + protected override IEnumerable ReadMethodMappings(string pathToSymbolFile) + { + string pdbType = DetectPdbType(pathToSymbolFile); + using (Stream fileStream = File.OpenRead(pathToSymbolFile)) + { + switch (pdbType) + { + case NativePdbMagic: + return this.ReadNativePdbMethodMappings(fileStream).ToList(); + case PortablePdbMagic: + return this.ReadPortablePdbMappings(fileStream).ToList(); + default: + throw new ArgumentException($"PDB file {pathToSymbolFile} not supported. Unknown binary header.", nameof(pathToSymbolFile)); + } + + } + } + + /// + /// Reads method mappings for portable PDB files. + /// + internal IEnumerable ReadPortablePdbMappings(Stream fileStream) + { + MetadataReader metadataReader = MetadataReaderProvider.FromPortablePdbStream(fileStream, MetadataStreamOptions.PrefetchMetadata).GetMetadataReader(MetadataReaderOptions.ApplyWindowsRuntimeProjections); + foreach (MethodDebugInformationHandle methodHandle in metadataReader.MethodDebugInformation) + { + MethodDebugInformation debugInfo = metadataReader.GetMethodDebugInformation(methodHandle); + var sequencePoints = debugInfo.GetSequencePoints().Where(point => !point.IsHidden).ToList(); + if (sequencePoints.Count == 0) + { + // If there are no visible lines we cannot map the method to source code, skip. + continue; + } + + DocumentHandle docHandle = sequencePoints.First().Document; + if (docHandle.IsNil) + { + docHandle = debugInfo.Document; + } + + Document doc = metadataReader.GetDocument(docHandle); + + yield return new MethodMapping + { + MethodToken = (uint) MetadataTokens.GetToken(methodHandle.ToDefinitionHandle()), + SourceFile = metadataReader.GetString(doc.Name), + StartLine = (uint)sequencePoints.First().StartLine, + EndLine = (uint)sequencePoints.Last().EndLine, + }; + } + } + + /// + /// Reads method mappings for traditional (native) PDB files. + /// + internal IEnumerable ReadNativePdbMethodMappings(Stream fileStream) + { + IEnumerable pdbFunctions = PdbFile.LoadPdbFunctions(fileStream); + foreach (PdbFunction pdbFunction in pdbFunctions) + { + yield return new MethodMapping + { + MethodToken = pdbFunction.Token, + SourceFile = pdbFunction.SourceFilename, + StartLine = pdbFunction.StartLine, + EndLine = pdbFunction.EndLine + }; + } + } + + /// + /// Returns the magic string that denotes the type of PDB that the file represents, or null for an unknown type. + /// + private static string DetectPdbType(string pdbFile) + { + + byte[] magicBytes = new byte[MagicTypes.Max(magic => magic.Length)]; + using (FileStream pdbStream = File.OpenRead(pdbFile)) + { + pdbStream.Read(magicBytes, 0, magicBytes.Length); + } + + foreach (string magicString in MagicTypes) + { + if (magicString.Zip(magicBytes, (magicChar, magicByte) => magicChar == magicByte).All(result => result)) + { + return magicString; + } + } + + return null; + } + } +} diff --git a/BummerLib/MethodMapping/AssemblyMethodMappings.cs b/BummerLib/MethodMapping/AssemblyMethodMappings.cs new file mode 100644 index 00000000..a72ec870 --- /dev/null +++ b/BummerLib/MethodMapping/AssemblyMethodMappings.cs @@ -0,0 +1,56 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) CQSE GmbH +// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//------------------------------------------------------------------------------ + +namespace Cqse.ConQAT.Dotnet.Bummer +{ + using System.Collections.Generic; + + /// + /// Holds mappings for methods within one assembly. + /// + public class AssemblyMethodMappings + { + /// + /// Gets the method mappings. + /// + /// The method mappings. + public List MethodMappings { get; } = new List(); + + /// + /// Gets or sets the name of the corresponding assembly. + /// + /// The name of the corresponding assembly. + public string AssemblyName { get; set; } + + /// + /// Gets or sets the name of the corresponding symbol file. + /// + /// The name of the corresponding symbol file. + public string SymbolFileName { get; set; } + + /// + /// Initializes a new instance of the + /// class. + /// + public AssemblyMethodMappings() + { + this.MethodMappings = new List(); + } + } +} diff --git a/BummerLib/MethodMapping/MethodMapping.cs b/BummerLib/MethodMapping/MethodMapping.cs new file mode 100644 index 00000000..c7ebda9d --- /dev/null +++ b/BummerLib/MethodMapping/MethodMapping.cs @@ -0,0 +1,57 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) CQSE GmbH +// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//------------------------------------------------------------------------------ + +namespace Cqse.ConQAT.Dotnet.Bummer +{ + /// + /// Stores the mapping of an IL method within an assembly to source code + /// lines in a specific source file. + /// + public class MethodMapping + { + /// + /// Gets or sets the method token, which was given to the method + /// by the compiler. + /// + /// The IL method token. This token identifies the method + /// within the assembly. + public uint MethodToken { get; set; } + + /// + /// Gets or sets the source file where the method is included. + /// + /// The source file where the method is included. May be null if the method is compiler-generated. + /// In that case, the start and end line are not valid. + public string SourceFile { get; set; } + + /// + /// Gets or sets the start line of the method in the source code. + /// + /// The 1-based, inclusive start line of the source code that corresponds + /// to the method. + public uint StartLine { get; set; } + + /// + /// Gets or sets the end line of the method in the source code. + /// + /// The 1-based, inclusive end line of the source code that corresponds + /// to the method. + public uint EndLine { get; set; } + } +} diff --git a/BummerLib/Properties/AssemblyInfo.cs b/BummerLib/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..7ee27c35 --- /dev/null +++ b/BummerLib/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyProduct("CQSE .NET Profiler Bummer")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyCompany("CQSE GmbH")] +[assembly: AssemblyCopyright("Copyright © CQSE GmbH")] +[assembly: AssemblyDescription("Creates mappings between binary and source file methods based on .pdb or .mdb symbol files.")] +[assembly: InternalsVisibleTo("Bummer_Test")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("721fdfc9-6da9-4159-a54e-e448dfb9097d")] diff --git a/BummerLib/packages.config b/BummerLib/packages.config new file mode 100644 index 00000000..cfe7c066 --- /dev/null +++ b/BummerLib/packages.config @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/BummerLib_Test/BummerLib_Test.csproj b/BummerLib_Test/BummerLib_Test.csproj new file mode 100644 index 00000000..088d4e98 --- /dev/null +++ b/BummerLib_Test/BummerLib_Test.csproj @@ -0,0 +1,82 @@ + + + + + + + Debug + AnyCPU + {DD23760A-176C-4151-9E71-7F3642FFFACE} + Library + Properties + Bummer_Test + Bummer_Test + v4.8 + 512 + true + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\NUnit.3.13.2\lib\net45\nunit.framework.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + {721fdfc9-6da9-4159-a54e-e448dfb9097d} + BummerLib + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/BummerLib_Test/PdbFileTest.cs b/BummerLib_Test/PdbFileTest.cs new file mode 100644 index 00000000..f1602f19 --- /dev/null +++ b/BummerLib_Test/PdbFileTest.cs @@ -0,0 +1,155 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) CQSE GmbH +// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//------------------------------------------------------------------------------ + +using NUnit.Framework; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Cqse.ConQAT.Dotnet.Bummer +{ + /// + /// Tests for . + /// + public class PdbFileTest : TestBase + { + /// + /// Most basic smoke test. + /// Makes sure that reading the pdb and loading the enclosed functions + /// does not throw an exception. + /// This means that accessing the needed method in the Mono assembly via + /// reflection is still possible. + /// + [Test] + public void SmokeTestReflection() + { + this.LoadPdbFunctionsFromTestFile("Sandbox.pdb"); + } + + /// + /// Makes sure that loading PdbFunctions from the Sandbox Pdb works + /// and provides the correct results. + /// + [Test] + public void TestSandbox() + { + List functions = this.LoadPdbFunctionsFromTestFile("Sandbox.pdb"); + Assert.AreEqual(2, functions.Count); + Assert.Contains(new PdbFunction() + { + StartLine = 12, + EndLine = 15, + Token = 100663297, + SourceFilename = "c:\\cqse\\src\\conqat-engine\\testgap\\eu.cqse.conqat.engine.testgap\\test-data\\sandbox\\08\\Sandbox\\Sandbox\\Program.cs" + }, functions); + Assert.Contains(new PdbFunction() + { + StartLine = 19, + EndLine = 26, + Token = 100663298, + SourceFilename = "c:\\cqse\\src\\conqat-engine\\testgap\\eu.cqse.conqat.engine.testgap\\test-data\\sandbox\\08\\Sandbox\\Sandbox\\Program.cs" + }, functions); + } + + /// + /// Makes sure that loading PdbFunctions from the Library A Pdb works + /// and provides the correct results. + /// + [Test] + public void TestLibraryA() + { + List functions = this.LoadPdbFunctionsFromTestFile("LibraryARenamedInVersion8.pdb"); + Assert.AreEqual(4, functions.Count); + Assert.Contains(new PdbFunction() + { + StartLine = 11, + EndLine = 17, + Token = 100663297, + SourceFilename = "c:\\cqse\\src\\conqat-engine\\testgap\\eu.cqse.conqat.engine.testgap\\test-data\\sandbox\\08\\Sandbox\\LibraryA\\ClassARenamedInVersion7.cs" + }, functions); + Assert.Contains(new PdbFunction() + { + StartLine = 21, + EndLine = 23, + Token = 100663298, + SourceFilename = "c:\\cqse\\src\\conqat-engine\\testgap\\eu.cqse.conqat.engine.testgap\\test-data\\sandbox\\08\\Sandbox\\LibraryA\\ClassARenamedInVersion7.cs" + }, functions); + + Assert.Contains(new PdbFunction() + { + StartLine = 13, + EndLine = 17, + Token = 100663300, + SourceFilename = "c:\\cqse\\src\\conqat-engine\\testgap\\eu.cqse.conqat.engine.testgap\\test-data\\sandbox\\08\\Sandbox\\LibraryA\\ClassB.cs" + }, functions); + Assert.Contains(new PdbFunction() + { + StartLine = 20, + EndLine = 23, + Token = 100663301, + SourceFilename = "c:\\cqse\\src\\conqat-engine\\testgap\\eu.cqse.conqat.engine.testgap\\test-data\\sandbox\\08\\Sandbox\\LibraryA\\ClassB.cs" + }, functions); + } + + /// + /// Makes sure that loading PdbFunctions from the 'UnusedLibrary' Pdb + /// works and provides the correct results. + /// + [Test] + public void TestUnusedLibrary() + { + List functions = this.LoadPdbFunctionsFromTestFile("UnusedLibraryRenamedInVersion8.pdb"); + Assert.AreEqual(3, functions.Count); + Assert.Contains(new PdbFunction() + { + StartLine = 20, + EndLine = 27, + Token = 100663297, + SourceFilename = "c:\\cqse\\src\\conqat-engine\\testgap\\eu.cqse.conqat.engine.testgap\\test-data\\sandbox\\08\\Sandbox\\UnusedLibrary\\SecondUnusedclassRenamedInVersion7.cs" + }, functions); + Assert.Contains(new PdbFunction() + { + StartLine = 30, + EndLine = 38, + Token = 100663298, + SourceFilename = "c:\\cqse\\src\\conqat-engine\\testgap\\eu.cqse.conqat.engine.testgap\\test-data\\sandbox\\08\\Sandbox\\UnusedLibrary\\SecondUnusedclassRenamedInVersion7.cs" + }, functions); + + Assert.Contains(new PdbFunction() + { + StartLine = 13, + EndLine = 18, + Token = 100663300, + SourceFilename = "c:\\cqse\\src\\conqat-engine\\testgap\\eu.cqse.conqat.engine.testgap\\test-data\\sandbox\\08\\Sandbox\\UnusedLibrary\\UnusedClass.cs" + }, functions); + } + + /// + /// Loads the Pdb functions from the specified test file. + /// + private List LoadPdbFunctionsFromTestFile(string testFileName) + { + FileInfo sandboxPdb = this.GetGlobalTestFile(testFileName); + using (FileStream pdbStream = sandboxPdb.OpenRead()) + { + return PdbFile.LoadPdbFunctions(pdbStream).ToList(); + } + } + } +} diff --git a/BummerLib_Test/PdbMethodMapperTest.cs b/BummerLib_Test/PdbMethodMapperTest.cs new file mode 100644 index 00000000..3bbbdbb3 --- /dev/null +++ b/BummerLib_Test/PdbMethodMapperTest.cs @@ -0,0 +1,86 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) CQSE GmbH +// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//------------------------------------------------------------------------------ + + +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Cqse.ConQAT.Dotnet.Bummer +{ + /// + /// Tests for the . + /// + public class PdbMethodMapperTest : TestBase + { + /// + /// Tests reading a legacy PDB file is throwing an exception. + /// + [Test] + public void TestReadingLegacyPdbWillThrowAnException() + { + string pdb = this.GetGlobalTestFile("LegacyPdb.pdb").FullName; + Assert.Throws(() => new PdbMethodMapper().GetMethodMappings(pdb, "LegacyPdb"), + $"PDB file {pdb} not supported. Unknown binary header."); + } + + /// + /// Tests reading a legacy PDB file is throwing an exception. + /// + [Test] + public void TestReadingPortablePdbWillNotThrowAnException() + { + string pdb = this.GetGlobalTestFile("Newtonsoft.Json.pdb").FullName; + Assert.DoesNotThrow(() => new PdbMethodMapper().GetMethodMappings(pdb, "Newtonsoft.Json")); + } + + /// + /// Smoke test that verifies that reading the sandbox pdb will not throw an exception. + /// + [Test] + public void TestReadingSandboxPdb() + { + string pdb = this.GetGlobalTestFile("Sandbox.pdb").FullName; + Assert.DoesNotThrow(() => new PdbMethodMapper().GetMethodMappings(pdb, "Sandbox")); + } + + /// + /// Tests reading a portable PDB file. + /// + [Test] + public void TestReadPortablePdbMappings() + { + using (Stream pdb = File.OpenRead(this.GetGlobalTestFile("Newtonsoft.Json.pdb").FullName)) + { + var mapper = new PdbMethodMapper(); + // Corresponds to constructor: https://github.com/JamesNK/Newtonsoft.Json/blob/12.0.1/Src/Newtonsoft.Json/JsonException.cs#L55 + List mappings = mapper.ReadPortablePdbMappings(pdb).Where(m => m.SourceFile.Contains("JsonException.cs") && m.StartLine == 56).ToList(); + Assert.That(mappings, Has.Count.EqualTo(1)); + + MethodMapping mapping = mappings.First(); + Assert.That(mapping.StartLine, Is.EqualTo(56)); + Assert.That(mapping.EndLine, Is.EqualTo(58)); + Assert.That(mapping.SourceFile, Is.EqualTo("/_/Src/Newtonsoft.Json/JsonException.cs")); + Assert.That(mapping.MethodToken, Is.EqualTo(100663435)); + } + } + } +} diff --git a/BummerLib_Test/TestBase.cs b/BummerLib_Test/TestBase.cs new file mode 100644 index 00000000..f7933823 --- /dev/null +++ b/BummerLib_Test/TestBase.cs @@ -0,0 +1,39 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) CQSE GmbH +// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//------------------------------------------------------------------------------ + +using NUnit.Framework; +using System.IO; + + +namespace Cqse.ConQAT.Dotnet.Bummer +{ + /// + /// Test base class for Bummer tests + /// + public class TestBase + { + /// + /// Returns the FileInfo for a resource file in the TestData folder. + /// + protected FileInfo GetGlobalTestFile(string resource) { + string path = Path.Combine(TestContext.CurrentContext.TestDirectory, "../..", "TestData", resource); + return new FileInfo(path); + } + } +} diff --git a/BummerLib_Test/TestData/LegacyPdb.pdb b/BummerLib_Test/TestData/LegacyPdb.pdb new file mode 100644 index 00000000..963d871a Binary files /dev/null and b/BummerLib_Test/TestData/LegacyPdb.pdb differ diff --git a/BummerLib_Test/TestData/LibraryARenamedInVersion8.pdb b/BummerLib_Test/TestData/LibraryARenamedInVersion8.pdb new file mode 100644 index 00000000..613249ca Binary files /dev/null and b/BummerLib_Test/TestData/LibraryARenamedInVersion8.pdb differ diff --git a/BummerLib_Test/TestData/Newtonsoft.Json.pdb b/BummerLib_Test/TestData/Newtonsoft.Json.pdb new file mode 100644 index 00000000..0cfd8934 Binary files /dev/null and b/BummerLib_Test/TestData/Newtonsoft.Json.pdb differ diff --git a/BummerLib_Test/TestData/Sandbox.pdb b/BummerLib_Test/TestData/Sandbox.pdb new file mode 100644 index 00000000..e7d480c3 Binary files /dev/null and b/BummerLib_Test/TestData/Sandbox.pdb differ diff --git a/BummerLib_Test/TestData/UnusedLibraryRenamedInVersion8.pdb b/BummerLib_Test/TestData/UnusedLibraryRenamedInVersion8.pdb new file mode 100644 index 00000000..820ee33e Binary files /dev/null and b/BummerLib_Test/TestData/UnusedLibraryRenamedInVersion8.pdb differ diff --git a/BummerLib_Test/packages.config b/BummerLib_Test/packages.config new file mode 100644 index 00000000..7147696f --- /dev/null +++ b/BummerLib_Test/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Cqse.Teamscale.Profiler.Dotnet.sln b/Cqse.Teamscale.Profiler.Dotnet.sln index ef305ccc..6fa5eef2 100644 --- a/Cqse.Teamscale.Profiler.Dotnet.sln +++ b/Cqse.Teamscale.Profiler.Dotnet.sln @@ -21,7 +21,13 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Profiler_Cpp_Test", "Profil EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DumpPdb", "DumpPdb\DumpPdb.csproj", "{BF294B13-B2BB-43B4-9F83-3AA0BE0DC448}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample_Debugging_App", "Sample_Debugging_App\Sample_Debugging_App.csproj", "{084AB86B-40EA-40B1-9B30-43D2619DEF21}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample_Debugging_App", "Sample_Debugging_App\Sample_Debugging_App.csproj", "{084AB86B-40EA-40B1-9B30-43D2619DEF21}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BummerLib", "BummerLib\BummerLib.csproj", "{721FDFC9-6DA9-4159-A54E-E448DFB9097D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BummerLib_Test", "BummerLib_Test\BummerLib_Test.csproj", "{DD23760A-176C-4151-9E71-7F3642FFFACE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BummerExe", "BummerExe\BummerExe.csproj", "{19C457D5-6780-4480-94CB-ED7E055402B8}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -115,6 +121,42 @@ Global {084AB86B-40EA-40B1-9B30-43D2619DEF21}.Release|Win32.Build.0 = Release|Any CPU {084AB86B-40EA-40B1-9B30-43D2619DEF21}.Release|x64.ActiveCfg = Release|Any CPU {084AB86B-40EA-40B1-9B30-43D2619DEF21}.Release|x64.Build.0 = Release|Any CPU + {721FDFC9-6DA9-4159-A54E-E448DFB9097D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {721FDFC9-6DA9-4159-A54E-E448DFB9097D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {721FDFC9-6DA9-4159-A54E-E448DFB9097D}.Debug|Win32.ActiveCfg = Debug|Any CPU + {721FDFC9-6DA9-4159-A54E-E448DFB9097D}.Debug|Win32.Build.0 = Debug|Any CPU + {721FDFC9-6DA9-4159-A54E-E448DFB9097D}.Debug|x64.ActiveCfg = Debug|Any CPU + {721FDFC9-6DA9-4159-A54E-E448DFB9097D}.Debug|x64.Build.0 = Debug|Any CPU + {721FDFC9-6DA9-4159-A54E-E448DFB9097D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {721FDFC9-6DA9-4159-A54E-E448DFB9097D}.Release|Any CPU.Build.0 = Release|Any CPU + {721FDFC9-6DA9-4159-A54E-E448DFB9097D}.Release|Win32.ActiveCfg = Release|Any CPU + {721FDFC9-6DA9-4159-A54E-E448DFB9097D}.Release|Win32.Build.0 = Release|Any CPU + {721FDFC9-6DA9-4159-A54E-E448DFB9097D}.Release|x64.ActiveCfg = Release|Any CPU + {721FDFC9-6DA9-4159-A54E-E448DFB9097D}.Release|x64.Build.0 = Release|Any CPU + {DD23760A-176C-4151-9E71-7F3642FFFACE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DD23760A-176C-4151-9E71-7F3642FFFACE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DD23760A-176C-4151-9E71-7F3642FFFACE}.Debug|Win32.ActiveCfg = Debug|Any CPU + {DD23760A-176C-4151-9E71-7F3642FFFACE}.Debug|Win32.Build.0 = Debug|Any CPU + {DD23760A-176C-4151-9E71-7F3642FFFACE}.Debug|x64.ActiveCfg = Debug|Any CPU + {DD23760A-176C-4151-9E71-7F3642FFFACE}.Debug|x64.Build.0 = Debug|Any CPU + {DD23760A-176C-4151-9E71-7F3642FFFACE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DD23760A-176C-4151-9E71-7F3642FFFACE}.Release|Any CPU.Build.0 = Release|Any CPU + {DD23760A-176C-4151-9E71-7F3642FFFACE}.Release|Win32.ActiveCfg = Release|Any CPU + {DD23760A-176C-4151-9E71-7F3642FFFACE}.Release|Win32.Build.0 = Release|Any CPU + {DD23760A-176C-4151-9E71-7F3642FFFACE}.Release|x64.ActiveCfg = Release|Any CPU + {DD23760A-176C-4151-9E71-7F3642FFFACE}.Release|x64.Build.0 = Release|Any CPU + {19C457D5-6780-4480-94CB-ED7E055402B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {19C457D5-6780-4480-94CB-ED7E055402B8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {19C457D5-6780-4480-94CB-ED7E055402B8}.Debug|Win32.ActiveCfg = Debug|Any CPU + {19C457D5-6780-4480-94CB-ED7E055402B8}.Debug|Win32.Build.0 = Debug|Any CPU + {19C457D5-6780-4480-94CB-ED7E055402B8}.Debug|x64.ActiveCfg = Debug|Any CPU + {19C457D5-6780-4480-94CB-ED7E055402B8}.Debug|x64.Build.0 = Debug|Any CPU + {19C457D5-6780-4480-94CB-ED7E055402B8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {19C457D5-6780-4480-94CB-ED7E055402B8}.Release|Any CPU.Build.0 = Release|Any CPU + {19C457D5-6780-4480-94CB-ED7E055402B8}.Release|Win32.ActiveCfg = Release|Any CPU + {19C457D5-6780-4480-94CB-ED7E055402B8}.Release|Win32.Build.0 = Release|Any CPU + {19C457D5-6780-4480-94CB-ED7E055402B8}.Release|x64.ActiveCfg = Release|Any CPU + {19C457D5-6780-4480-94CB-ED7E055402B8}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/DumpPdb/DumpPdb.csproj b/DumpPdb/DumpPdb.csproj index 39ac4a3e..decf47be 100644 --- a/DumpPdb/DumpPdb.csproj +++ b/DumpPdb/DumpPdb.csproj @@ -33,9 +33,6 @@ 4 - - ..\packages\Cqse.Bummer.1.0.3\lib\netstandard2.0\bummer.dll - ..\packages\Mono.Cecil.0.10.1\lib\net40\Mono.Cecil.dll @@ -65,5 +62,11 @@ + + + {721fdfc9-6da9-4159-a54e-e448dfb9097d} + BummerLib + + \ No newline at end of file diff --git a/DumpPdb/packages.config b/DumpPdb/packages.config index ebf21730..a7c60968 100644 --- a/DumpPdb/packages.config +++ b/DumpPdb/packages.config @@ -1,5 +1,4 @@  - \ No newline at end of file