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