From 409f1bb7182b6dd33d6dd60be25cd8c21e1b72d9 Mon Sep 17 00:00:00 2001 From: Stefan Winkler Date: Mon, 4 Apr 2016 21:31:05 +0200 Subject: [PATCH 1/3] Extract the pdb path Generally the path to the PDB file is embedded in the unit tests executable. Try to extract it and use it if present. --- .../DiaResolver.Tests/DiaResolverTests.cs | 29 ++- GoogleTestAdapter/DiaResolver/DiaResolver.cs | 205 +++++++++++++++++- GoogleTestAdapter/DiaResolver/IDiaResolver.cs | 1 + 3 files changed, 232 insertions(+), 3 deletions(-) diff --git a/GoogleTestAdapter/DiaResolver.Tests/DiaResolverTests.cs b/GoogleTestAdapter/DiaResolver.Tests/DiaResolverTests.cs index b1ccc7396..eaa535937 100644 --- a/GoogleTestAdapter/DiaResolver.Tests/DiaResolverTests.cs +++ b/GoogleTestAdapter/DiaResolver.Tests/DiaResolverTests.cs @@ -41,12 +41,39 @@ public void GetFunctions_X64_NonMatchingFilter_NoResults() DoResolveTest(X64ExternallyLinkedTests, "ThisFunctionDoesNotExist", 0, 0); } + [TestMethod] + public void ExtractPDB_X64() + { + IDiaResolver resolver = DefaultDiaResolverFactory.Instance.Create(X64ExternallyLinkedTests, ""); + string pdb = resolver.ExtractPdbPath(X64StaticallyLinkedTests); + string executableName = System.IO.Path.GetFileNameWithoutExtension(X64StaticallyLinkedTests); + string pdbName = System.IO.Path.GetFileNameWithoutExtension(pdb); + + Assert.AreEqual(executableName, pdbName); + + resolver.Dispose(); + } + + [TestMethod] + public void ExtractPDB_X86() + { + IDiaResolver resolver = DefaultDiaResolverFactory.Instance.Create(X86ExternallyLinkedTests, ""); + string pdb = resolver.ExtractPdbPath(X86StaticallyLinkedTests); + string executableName = System.IO.Path.GetFileNameWithoutExtension(X86StaticallyLinkedTests); + string pdbName = System.IO.Path.GetFileNameWithoutExtension(pdb); + + Assert.AreEqual(executableName, pdbName); + + resolver.Dispose(); + } + + private void DoResolveTest(string executable, string filter, int expectedLocations, int expectedErrorMessages, bool disposeResolver = true) { List locations = new List(); List errorMessages = new List(); - IDiaResolver resolver = DefaultDiaResolverFactory.Instance.Create(executable, ""); + IDiaResolver resolver = DefaultDiaResolverFactory.Instance.Create(executable, ""); locations.AddRange(resolver.GetFunctions(filter)); errorMessages.AddRange(resolver.ErrorMessages); diff --git a/GoogleTestAdapter/DiaResolver/DiaResolver.cs b/GoogleTestAdapter/DiaResolver/DiaResolver.cs index 04a3c84de..2df266aa9 100644 --- a/GoogleTestAdapter/DiaResolver/DiaResolver.cs +++ b/GoogleTestAdapter/DiaResolver/DiaResolver.cs @@ -106,9 +106,210 @@ public IEnumerable GetFunctions(string symbolFilterString) } + private class SectionHeader + { + public long VirtualAddress; + public long PointerToRawData; + public long SizeOfRawData; + } + + private long getRealAddress(long VirtualAdress, SectionHeader[] sections) + { + for (int i = 0; i < sections.Length; i++) + { + if (VirtualAdress > sections[i].VirtualAddress && + VirtualAdress < sections[i].VirtualAddress + sections[i].SizeOfRawData) + { + return VirtualAdress - sections[i].VirtualAddress + sections[i].PointerToRawData; + } + } + + return 0; + } + + private int readDWORD(FileStream stream) + { + byte[] buffer = new byte[4]; + + if (stream.Read(buffer, 0, 4) != 4) + return 0; + + // seems to be network (big endian) byte order + return (buffer[3] << 24) + (buffer[2] << 16) + + (buffer[1] << 8) + buffer[0]; + } + + private int readWORD(FileStream stream) + { + byte[] buffer = new byte[2]; + + if (stream.Read(buffer, 0, 2) != 2) + return 0; + + // seems to be network (big endian) byte order + return (buffer[1] << 8) + buffer[0]; + } + + // Most windows executables contain the path to their PDB + // in the header. This should be the most stable way to + // determine the location and the name of the PDB. + // + // This is inspired by + // https://deplinenoise.wordpress.com/2013/06/14/getting-your-pdb-name-from-a-running-executable-windows/ + // + // In contrast to this blog, the virtual addresses must + // be converted to an offset in the executable and 32bit and + // 64bit executables must be treated diffently + public string ExtractPdbPath(string binary) + { + const int offsetof_NumberOfSections = 2; + const int sizeof_IMAGE_NT_HEADERS32 = 248; + const int sizeof_IMAGE_NT_HEADERS64 = 264; + int sizeof_IMAGE_NT_HEADERS = sizeof_IMAGE_NT_HEADERS32; + const int sizeof_IMAGE_SECTION_HEADER = 40; + + const int offsetof_DataDirectory32 = 96; + const int offsetof_DataDirectory64 = 112; + int offsetof_DataDirectory = offsetof_DataDirectory32; + + const int sizeof_IMAGE_DATA_DIRECTORY = 8; + + FileStream executable = File.Open(binary, FileMode.Open); + + // the offset to the PE is at offeset 60 (0x3c) and contains + // four bytes + executable.Seek(60, SeekOrigin.Begin); + + byte[] buffer = new byte[8]; + + long peOffset = readDWORD(executable); + if (peOffset == 0) + return null; + + executable.Seek(peOffset, SeekOrigin.Begin); + + // read the next four bytes. They must be 'PE\0\0' + if (executable.Read(buffer, 0, 4) != 4) + return null; + + if (buffer[0] != 'P' || buffer[1] != 'E' || + buffer[2] != 0 || buffer[3] != 0) + return null; + + // get the magic in the optional header to determine whether + // the binary is 32 bit or 64 bit. + // get the magic + executable.Seek(peOffset + 4 + 20, SeekOrigin.Begin); + + long magic = readWORD(executable); + if (magic == 0) + return null; + + // magic can be used to test if the executable is + // a 32bit binary or a 64bit binary + if (magic == 0x20b) + { + sizeof_IMAGE_NT_HEADERS = sizeof_IMAGE_NT_HEADERS64; + offsetof_DataDirectory = offsetof_DataDirectory64; + } + + // get the number of sections in this executable, + // to map virtual addresses to offsets in the binary + // the table of sections is required. + executable.Seek(peOffset + 4 + offsetof_NumberOfSections, + SeekOrigin.Begin); + + int numberOfSections = readWORD(executable); + if (numberOfSections == 0) + return null; + + SectionHeader[] sections = new SectionHeader[numberOfSections]; + for (int i = 0; i < numberOfSections; i++) + { + sections[i] = new SectionHeader(); + byte[] shBuffer = new byte[sizeof_IMAGE_SECTION_HEADER]; + executable.Seek(peOffset + sizeof_IMAGE_NT_HEADERS + + i * sizeof_IMAGE_SECTION_HEADER, SeekOrigin.Begin); + + if (executable.Read(shBuffer,0,40) != 40) + return null; + + // offsetof VirtualAdress: 12; + sections[i].VirtualAddress = (shBuffer[15] << 24) + + (shBuffer[14] << 16) +(shBuffer[13] << 8) + shBuffer[12]; + // offsetof SizeOfRawData: 16; + sections[i].SizeOfRawData = (shBuffer[19] << 24) + + (shBuffer[18] << 16) + (shBuffer[17] << 8) + shBuffer[16]; + // offsetof PointerToRawData: 20; + sections[i].PointerToRawData = (shBuffer[23] << 24) + + (shBuffer[22] << 16) + (shBuffer[21] << 8) + shBuffer[20]; + + } + + // get the size of the optional header + executable.Seek(peOffset + 4 + 16, SeekOrigin.Begin); + if (executable.Read(buffer, 0, 2) != 2) + return null; + + long sizeOfOptionalHeader = (buffer[1] << 8) + buffer[0]; + + if (sizeOfOptionalHeader == 0) + return null; + + // the DEBUG_DATA_DIRECTORY is at index 7 in the + // DataDirectory member of the optional header + int offset = offsetof_DataDirectory + 6 * sizeof_IMAGE_DATA_DIRECTORY; + + executable.Seek(peOffset + 4 + 20 + offset, SeekOrigin.Begin); + if (executable.Read(buffer, 0, 8) != 8) + return null; + + // get the size and the address of the DEBUG_DATA_DIRECTORY + long tableSize = (buffer[7] << 24) + (buffer[6] << 16) + + (buffer[5] << 8) + buffer[4]; + long tableOffset = (buffer[3] << 24) + (buffer[2] << 16) + + (buffer[1] << 8) + buffer[0]; + + tableOffset = getRealAddress(tableOffset, sections); + + // get the AddressOfRawData member of the + // DEBUG_DATA_DIRECTORY + executable.Seek(tableOffset + 20, SeekOrigin.Begin); + + long AddressOfRawData = readDWORD(executable); + if (AddressOfRawData == 0) + return null; + + AddressOfRawData = getRealAddress(AddressOfRawData, sections); + + // the pointer to the pdb path starts at offset 24 + + string pdbPath = ""; + + executable.Seek(AddressOfRawData + 24, SeekOrigin.Begin); + if (executable.Read(buffer, 0, 1) != 1) + return null; + + while (buffer[0] != 0) + { + pdbPath += Convert.ToChar(buffer[0]); + + if (executable.Read(buffer, 0, 1) != 1) + return null; + } + + executable.Close(); + + return pdbPath; + } + private string FindPdbFile(string binary, string pathExtension) { - string pdb = Path.ChangeExtension(binary, ".pdb"); + string pdb = ExtractPdbPath(binary); + if (pdb != null && File.Exists(pdb)) + return pdb; + + pdb = Path.ChangeExtension(binary, ".pdb"); if (File.Exists(pdb)) return pdb; @@ -180,4 +381,4 @@ private IDiaEnumLineNumbers GetLineNumbers(uint addressSection, uint addressOffs } -} \ No newline at end of file +} diff --git a/GoogleTestAdapter/DiaResolver/IDiaResolver.cs b/GoogleTestAdapter/DiaResolver/IDiaResolver.cs index 1a9d798c0..4cfde095b 100644 --- a/GoogleTestAdapter/DiaResolver/IDiaResolver.cs +++ b/GoogleTestAdapter/DiaResolver/IDiaResolver.cs @@ -7,5 +7,6 @@ public interface IDiaResolver : IDisposable { List ErrorMessages { get; } IEnumerable GetFunctions(string symbolFilterString); + string ExtractPdbPath(string binary); } } \ No newline at end of file From ef1249ce2d77df9399b9a6a6b1b6582deb903297 Mon Sep 17 00:00:00 2001 From: Stefan Winkler Date: Sat, 16 Apr 2016 19:31:25 +0200 Subject: [PATCH 2/3] Move pdb path extraction to NativeMethods.cs --- .../DiaResolver.Tests/DiaResolverTests.cs | 14 +- GoogleTestAdapter/DiaResolver/DiaResolver.cs | 202 +------- GoogleTestAdapter/DiaResolver/IDiaResolver.cs | 1 - .../DiaResolver/NativeMethods.cs | 471 ++++++++++++++++++ 4 files changed, 480 insertions(+), 208 deletions(-) diff --git a/GoogleTestAdapter/DiaResolver.Tests/DiaResolverTests.cs b/GoogleTestAdapter/DiaResolver.Tests/DiaResolverTests.cs index eaa535937..a45658e5a 100644 --- a/GoogleTestAdapter/DiaResolver.Tests/DiaResolverTests.cs +++ b/GoogleTestAdapter/DiaResolver.Tests/DiaResolverTests.cs @@ -44,27 +44,25 @@ public void GetFunctions_X64_NonMatchingFilter_NoResults() [TestMethod] public void ExtractPDB_X64() { - IDiaResolver resolver = DefaultDiaResolverFactory.Instance.Create(X64ExternallyLinkedTests, ""); - string pdb = resolver.ExtractPdbPath(X64StaticallyLinkedTests); + NativeMethods.PDBPathExtractor extractor = new NativeMethods.PDBPathExtractor(X64StaticallyLinkedTests, new List()); + + string pdb = extractor.pdbPath; string executableName = System.IO.Path.GetFileNameWithoutExtension(X64StaticallyLinkedTests); string pdbName = System.IO.Path.GetFileNameWithoutExtension(pdb); Assert.AreEqual(executableName, pdbName); - - resolver.Dispose(); } [TestMethod] public void ExtractPDB_X86() { - IDiaResolver resolver = DefaultDiaResolverFactory.Instance.Create(X86ExternallyLinkedTests, ""); - string pdb = resolver.ExtractPdbPath(X86StaticallyLinkedTests); + NativeMethods.PDBPathExtractor extractor = new NativeMethods.PDBPathExtractor(X86StaticallyLinkedTests, new List()); + + string pdb = extractor.pdbPath; string executableName = System.IO.Path.GetFileNameWithoutExtension(X86StaticallyLinkedTests); string pdbName = System.IO.Path.GetFileNameWithoutExtension(pdb); Assert.AreEqual(executableName, pdbName); - - resolver.Dispose(); } diff --git a/GoogleTestAdapter/DiaResolver/DiaResolver.cs b/GoogleTestAdapter/DiaResolver/DiaResolver.cs index 2df266aa9..7be543942 100644 --- a/GoogleTestAdapter/DiaResolver/DiaResolver.cs +++ b/GoogleTestAdapter/DiaResolver/DiaResolver.cs @@ -105,207 +105,11 @@ public IEnumerable GetFunctions(string symbolFilterString) return GetSymbolNamesAndAddresses(diaSymbols).Select(ToSourceFileLocation); } - - private class SectionHeader - { - public long VirtualAddress; - public long PointerToRawData; - public long SizeOfRawData; - } - - private long getRealAddress(long VirtualAdress, SectionHeader[] sections) - { - for (int i = 0; i < sections.Length; i++) - { - if (VirtualAdress > sections[i].VirtualAddress && - VirtualAdress < sections[i].VirtualAddress + sections[i].SizeOfRawData) - { - return VirtualAdress - sections[i].VirtualAddress + sections[i].PointerToRawData; - } - } - - return 0; - } - - private int readDWORD(FileStream stream) - { - byte[] buffer = new byte[4]; - - if (stream.Read(buffer, 0, 4) != 4) - return 0; - - // seems to be network (big endian) byte order - return (buffer[3] << 24) + (buffer[2] << 16) + - (buffer[1] << 8) + buffer[0]; - } - - private int readWORD(FileStream stream) - { - byte[] buffer = new byte[2]; - - if (stream.Read(buffer, 0, 2) != 2) - return 0; - - // seems to be network (big endian) byte order - return (buffer[1] << 8) + buffer[0]; - } - - // Most windows executables contain the path to their PDB - // in the header. This should be the most stable way to - // determine the location and the name of the PDB. - // - // This is inspired by - // https://deplinenoise.wordpress.com/2013/06/14/getting-your-pdb-name-from-a-running-executable-windows/ - // - // In contrast to this blog, the virtual addresses must - // be converted to an offset in the executable and 32bit and - // 64bit executables must be treated diffently - public string ExtractPdbPath(string binary) - { - const int offsetof_NumberOfSections = 2; - const int sizeof_IMAGE_NT_HEADERS32 = 248; - const int sizeof_IMAGE_NT_HEADERS64 = 264; - int sizeof_IMAGE_NT_HEADERS = sizeof_IMAGE_NT_HEADERS32; - const int sizeof_IMAGE_SECTION_HEADER = 40; - - const int offsetof_DataDirectory32 = 96; - const int offsetof_DataDirectory64 = 112; - int offsetof_DataDirectory = offsetof_DataDirectory32; - - const int sizeof_IMAGE_DATA_DIRECTORY = 8; - - FileStream executable = File.Open(binary, FileMode.Open); - - // the offset to the PE is at offeset 60 (0x3c) and contains - // four bytes - executable.Seek(60, SeekOrigin.Begin); - - byte[] buffer = new byte[8]; - - long peOffset = readDWORD(executable); - if (peOffset == 0) - return null; - - executable.Seek(peOffset, SeekOrigin.Begin); - - // read the next four bytes. They must be 'PE\0\0' - if (executable.Read(buffer, 0, 4) != 4) - return null; - - if (buffer[0] != 'P' || buffer[1] != 'E' || - buffer[2] != 0 || buffer[3] != 0) - return null; - - // get the magic in the optional header to determine whether - // the binary is 32 bit or 64 bit. - // get the magic - executable.Seek(peOffset + 4 + 20, SeekOrigin.Begin); - - long magic = readWORD(executable); - if (magic == 0) - return null; - - // magic can be used to test if the executable is - // a 32bit binary or a 64bit binary - if (magic == 0x20b) - { - sizeof_IMAGE_NT_HEADERS = sizeof_IMAGE_NT_HEADERS64; - offsetof_DataDirectory = offsetof_DataDirectory64; - } - - // get the number of sections in this executable, - // to map virtual addresses to offsets in the binary - // the table of sections is required. - executable.Seek(peOffset + 4 + offsetof_NumberOfSections, - SeekOrigin.Begin); - - int numberOfSections = readWORD(executable); - if (numberOfSections == 0) - return null; - - SectionHeader[] sections = new SectionHeader[numberOfSections]; - for (int i = 0; i < numberOfSections; i++) - { - sections[i] = new SectionHeader(); - byte[] shBuffer = new byte[sizeof_IMAGE_SECTION_HEADER]; - executable.Seek(peOffset + sizeof_IMAGE_NT_HEADERS + - i * sizeof_IMAGE_SECTION_HEADER, SeekOrigin.Begin); - - if (executable.Read(shBuffer,0,40) != 40) - return null; - - // offsetof VirtualAdress: 12; - sections[i].VirtualAddress = (shBuffer[15] << 24) + - (shBuffer[14] << 16) +(shBuffer[13] << 8) + shBuffer[12]; - // offsetof SizeOfRawData: 16; - sections[i].SizeOfRawData = (shBuffer[19] << 24) + - (shBuffer[18] << 16) + (shBuffer[17] << 8) + shBuffer[16]; - // offsetof PointerToRawData: 20; - sections[i].PointerToRawData = (shBuffer[23] << 24) + - (shBuffer[22] << 16) + (shBuffer[21] << 8) + shBuffer[20]; - - } - - // get the size of the optional header - executable.Seek(peOffset + 4 + 16, SeekOrigin.Begin); - if (executable.Read(buffer, 0, 2) != 2) - return null; - - long sizeOfOptionalHeader = (buffer[1] << 8) + buffer[0]; - - if (sizeOfOptionalHeader == 0) - return null; - - // the DEBUG_DATA_DIRECTORY is at index 7 in the - // DataDirectory member of the optional header - int offset = offsetof_DataDirectory + 6 * sizeof_IMAGE_DATA_DIRECTORY; - - executable.Seek(peOffset + 4 + 20 + offset, SeekOrigin.Begin); - if (executable.Read(buffer, 0, 8) != 8) - return null; - - // get the size and the address of the DEBUG_DATA_DIRECTORY - long tableSize = (buffer[7] << 24) + (buffer[6] << 16) + - (buffer[5] << 8) + buffer[4]; - long tableOffset = (buffer[3] << 24) + (buffer[2] << 16) + - (buffer[1] << 8) + buffer[0]; - - tableOffset = getRealAddress(tableOffset, sections); - - // get the AddressOfRawData member of the - // DEBUG_DATA_DIRECTORY - executable.Seek(tableOffset + 20, SeekOrigin.Begin); - - long AddressOfRawData = readDWORD(executable); - if (AddressOfRawData == 0) - return null; - - AddressOfRawData = getRealAddress(AddressOfRawData, sections); - - // the pointer to the pdb path starts at offset 24 - - string pdbPath = ""; - - executable.Seek(AddressOfRawData + 24, SeekOrigin.Begin); - if (executable.Read(buffer, 0, 1) != 1) - return null; - - while (buffer[0] != 0) - { - pdbPath += Convert.ToChar(buffer[0]); - - if (executable.Read(buffer, 0, 1) != 1) - return null; - } - - executable.Close(); - - return pdbPath; - } - private string FindPdbFile(string binary, string pathExtension) { - string pdb = ExtractPdbPath(binary); + NativeMethods.PDBPathExtractor pdbExtractor = + new NativeMethods.PDBPathExtractor(binary, new List()); + string pdb = pdbExtractor.pdbPath; if (pdb != null && File.Exists(pdb)) return pdb; diff --git a/GoogleTestAdapter/DiaResolver/IDiaResolver.cs b/GoogleTestAdapter/DiaResolver/IDiaResolver.cs index 4cfde095b..1a9d798c0 100644 --- a/GoogleTestAdapter/DiaResolver/IDiaResolver.cs +++ b/GoogleTestAdapter/DiaResolver/IDiaResolver.cs @@ -7,6 +7,5 @@ public interface IDiaResolver : IDisposable { List ErrorMessages { get; } IEnumerable GetFunctions(string symbolFilterString); - string ExtractPdbPath(string binary); } } \ No newline at end of file diff --git a/GoogleTestAdapter/DiaResolver/NativeMethods.cs b/GoogleTestAdapter/DiaResolver/NativeMethods.cs index 0eebb27ba..59f63eece 100644 --- a/GoogleTestAdapter/DiaResolver/NativeMethods.cs +++ b/GoogleTestAdapter/DiaResolver/NativeMethods.cs @@ -48,6 +48,366 @@ struct IMAGE_IMPORT_DESCRIPTOR public uint FirstThunk; } + [StructLayout(LayoutKind.Explicit)] + // ReSharper disable once InconsistentNaming + struct IMAGE_DOS_HEADER + { // DOS .EXE header + [FieldOffset(0)] + public ushort e_magic; // Magic number + + [FieldOffset(2)] + public ushort e_cblp; // public bytes on last page of file + + [FieldOffset(4)] + public ushort e_cp; // Pages in file + + [FieldOffset(6)] + public ushort e_crlc; // Relocations + + [FieldOffset(8)] + public ushort e_cparhdr; // Size of header in paragraphs + + [FieldOffset(10)] + public ushort e_minalloc; // Minimum extra paragraphs needed + + [FieldOffset(12)] + public ushort e_maxalloc; // Maximum extra paragraphs needed + + [FieldOffset(14)] + public ushort e_ss; // Initial (relative) SS value + + [FieldOffset(16)] + public ushort e_sp; // Initial SP value + + [FieldOffset(18)] + public ushort e_csum; // Checksum + + [FieldOffset(20)] + public ushort e_ip; // Initial IP value + + [FieldOffset(22)] + public ushort e_cs; // Initial (relative) CS value + + [FieldOffset(24)] + public ushort e_lfarlc; // File address of relocation table + + [FieldOffset(26)] + public ushort e_ovno; // Overlay number + + [FieldOffset(28)] + public ushort e_res; // 4 Reserved public ushorts + + [FieldOffset(36)] + public ushort e_oemid; // OEM identifier (for e_oeminfo) + + [FieldOffset(38)] + public ushort e_oeminfo; // OEM information; e_oemid specific + + [FieldOffset(40)] + public ushort e_res2; // Reserved public ushorts + + [FieldOffset(60)] + public int e_lfanew; // File address of new exe header + } + + [StructLayout(LayoutKind.Explicit)] + // ReSharper disable once InconsistentNaming + struct IMAGE_DATA_DIRECTORY + { + [FieldOffset(0)] + public uint VirtualAddress; + + [FieldOffset(4)] + public uint Size; + } + + + [StructLayout(LayoutKind.Explicit)] + // ReSharper disable once InconsistentNaming + struct IMAGE_OPTIONAL_HEADER32 + { + // + // Standard fields. + // + [FieldOffset(0)] + public ushort Magic; + + [FieldOffset(2)] + public byte MajorLinkerVersion; + + [FieldOffset(3)] + public byte MinorLinkerVersion; + + [FieldOffset(4)] + public uint SizeOfCode; + + [FieldOffset(8)] + public uint SizeOfInitializedData; + + [FieldOffset(12)] + public uint SizeOfUninitializedData; + + [FieldOffset(16)] + public uint AddressOfEntryPoint; + + [FieldOffset(20)] + public uint BaseOfCode; + + [FieldOffset(24)] + public uint BaseOfData; + + // + // NT additional fields. + // + + [FieldOffset(28)] + public uint ImageBase; + + [FieldOffset(32)] + public uint SectionAlignment; + + [FieldOffset(36)] + public uint FileAlignment; + + [FieldOffset(40)] + public ushort MajorOperatingSystemVersion; + + [FieldOffset(42)] + public ushort MinorOperatingSystemVersion; + + [FieldOffset(44)] + public ushort MajorImageVersion; + + [FieldOffset(46)] + public ushort MinorImageVersion; + + [FieldOffset(48)] + public ushort MajorSubsystemVersion; + + [FieldOffset(50)] + public ushort MinorSubsystemVersion; + + [FieldOffset(52)] + public uint Win32VersionValue; + + [FieldOffset(56)] + public uint SizeOfImage; + + [FieldOffset(60)] + public uint SizeOfHeaders; + + [FieldOffset(64)] + public uint CheckSum; + + [FieldOffset(68)] + public ushort Subsystem; + + [FieldOffset(70)] + public ushort DllCharacteristics; + + [FieldOffset(72)] + public uint SizeOfStackReserve; + + [FieldOffset(76)] + public uint SizeOfStackCommit; + + [FieldOffset(80)] + public uint SizeOfHeapReserve; + + [FieldOffset(84)] + public uint SizeOfHeapCommit; + + [FieldOffset(88)] + public uint LoaderFlags; + + [FieldOffset(92)] + public uint NumberOfRvaAndSizes; + + [FieldOffset(96)] + public IMAGE_DATA_DIRECTORY DataDirectory; + + //IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; + } + + [StructLayout(LayoutKind.Explicit)] + // ReSharper disable once InconsistentNaming + struct IMAGE_OPTIONAL_HEADER64 + { + [FieldOffset(0)] + public ushort Magic; + + [FieldOffset(2)] + public byte MajorLinkerVersion; + + [FieldOffset(3)] + public byte MinorLinkerVersion; + + [FieldOffset(4)] + public uint SizeOfCode; + + [FieldOffset(8)] + public uint SizeOfInitializedData; + + [FieldOffset(12)] + public uint SizeOfUninitializedData; + + [FieldOffset(16)] + public uint AddressOfEntryPoint; + + [FieldOffset(20)] + public uint BaseOfCode; + + [FieldOffset(24)] + public UInt64 ImageBase; + + [FieldOffset(32)] + public uint SectionAlignment; + + [FieldOffset(36)] + public uint FileAlignment; + + [FieldOffset(40)] + public ushort MajorOperatingSystemVersion; + + [FieldOffset(42)] + public ushort MinorOperatingSystemVersion; + + [FieldOffset(44)] + public ushort MajorImageVersion; + + [FieldOffset(46)] + public ushort MinorImageVersion; + + [FieldOffset(48)] + public ushort MajorSubsystemVersion; + + [FieldOffset(50)] + public ushort MinorSubsystemVersion; + + [FieldOffset(52)] + public uint Win32VersionValue; + + [FieldOffset(56)] + public uint SizeOfImage; + + [FieldOffset(60)] + public uint SizeOfHeaders; + + [FieldOffset(64)] + public uint CheckSum; + + [FieldOffset(68)] + public ushort Subsystem; + + [FieldOffset(70)] + public ushort DllCharacteristics; + + [FieldOffset(72)] + public UInt64 SizeOfStackReserve; + + [FieldOffset(80)] + public UInt64 SizeOfStackCommit; + + [FieldOffset(88)] + public UInt64 SizeOfHeapReserve; + + [FieldOffset(96)] + public UInt64 SizeOfHeapCommit; + + [FieldOffset(104)] + public uint LoaderFlags; + + [FieldOffset(108)] + public uint NumberOfRvaAndSizes; + + [FieldOffset(112)] + public IMAGE_DATA_DIRECTORY DataDirectory; + // IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; + } + + + [StructLayout(LayoutKind.Explicit)] + // ReSharper disable once InconsistentNaming + struct IMAGE_DEBUG_DIRECTORY + { + [FieldOffset(0)] + public uint Characteristics; + + [FieldOffset(4)] + public uint TimeDateStamp; + + [FieldOffset(8)] + public ushort MajorVersion; + + [FieldOffset(10)] + public ushort MinorVersion; + + [FieldOffset(12)] + public uint Type; + + [FieldOffset(16)] + public uint SizeOfData; + + [FieldOffset(20)] + public uint AddressOfRawData; + + [FieldOffset(24)] + public uint PointerToRawData; + } + + [StructLayout(LayoutKind.Explicit)] + unsafe struct IMAGE_SECTION_HEADER + { + [FieldOffset(0)] + public fixed byte Name[8]; + + [FieldOffset(8)] + public uint VirtualSizePhysicalAddress; + + [FieldOffset(12)] + public uint VirtualAddress; + + [FieldOffset(16)] + public uint SizeOfRawData; + + [FieldOffset(20)] + public uint PointerToRawData; + + [FieldOffset(24)] + public uint PointerToRelocations; + + [FieldOffset(28)] + public uint PointerToLinenumbers; + + [FieldOffset(32)] + public ushort NumberOfRelocations; + + [FieldOffset(34)] + public ushort NumberOfLinenumbers; + + [FieldOffset(36)] + public uint Characteristics; + } + + [StructLayout(LayoutKind.Explicit)] + struct PdbInfo + { + [FieldOffset(0)] + public uint Signature; + + [FieldOffset(4)] + //public fixed byte Guid[16]; + // don't care about making this an array, as + // we don't need it anyway + public byte Guid; + + [FieldOffset(20)] + public uint Age; + + [FieldOffset(24)] + public byte PdbFileName; + } + enum NameSearchOptions : uint { NsNone = 0x0u, @@ -126,6 +486,117 @@ private string GetString(uint name) } + public class PDBPathExtractor + { + public string pdbPath = null; + private LOADED_IMAGE _loadedImage = new LOADED_IMAGE(); + + private class SectionHeader + { + public long VirtualAddress; + public long PointerToRawData; + public long SizeOfRawData; + } + + private long AddressToOffset(long VirtualAdress, SectionHeader[] sections) + { + for (int i = 0; i < sections.Length; i++) + { + if (VirtualAdress > sections[i].VirtualAddress && + VirtualAdress < sections[i].VirtualAddress + sections[i].SizeOfRawData) + { + return VirtualAdress - sections[i].VirtualAddress + sections[i].PointerToRawData; + } + } + + return 0; + } + + // Most windows executables contain the path to their PDB + // in the header. This should be the most stable way to + // determine the location and the name of the PDB. + // + // This is inspired by + // https://deplinenoise.wordpress.com/2013/06/14/getting-your-pdb-name-from-a-running-executable-windows/ + // + // In contrast to this blog, the virtual addresses must + // be converted to an offset in the executable and 32bit and + // 64bit executables must be treated diffently + public PDBPathExtractor(string executable, List errorMessages) + { + const int sizeof_IMAGE_NT_HEADERS32 = 248; + const int sizeof_IMAGE_NT_HEADERS64 = 264; + int sizeof_IMAGE_NT_HEADERS = sizeof_IMAGE_NT_HEADERS32; + + fixed (LOADED_IMAGE* fixedLoadedImage = &_loadedImage) + { + if (MapAndLoad(executable, null, fixedLoadedImage, true, true)) + { + IMAGE_DOS_HEADER* dosHeader = (IMAGE_DOS_HEADER*)fixedLoadedImage->MappedAddress; + IntPtr fileHeader = fixedLoadedImage->MappedAddress + dosHeader->e_lfanew + 4; + + // use the 32 Bit struct to get the magic and decide, whether + // the executable is 32 or 64 bit + IMAGE_OPTIONAL_HEADER32* optHeader32 = (IMAGE_OPTIONAL_HEADER32*)(fileHeader + 20); + + IMAGE_DATA_DIRECTORY* directory; + if (optHeader32->Magic != 0x20b) + { + // this is a 32 bit executable + // keep using optHeader 32 + // the debug directory is at index 6 + directory = &(optHeader32->DataDirectory) + 6; + } + else + { + // 64 bit executable, use the appropriate + // structds and offset + sizeof_IMAGE_NT_HEADERS = sizeof_IMAGE_NT_HEADERS64; + IMAGE_OPTIONAL_HEADER64* optHeader64 = (IMAGE_OPTIONAL_HEADER64*)(fileHeader + 20); + directory = &(optHeader64->DataDirectory) + 6; + } + + // get information on all sections. This is required to + // map addresses to correct offsets + uint numberOfSections = fixedLoadedImage->NumbOfSections; + SectionHeader[] sections = new SectionHeader[numberOfSections]; + IMAGE_SECTION_HEADER* secHeader = (IMAGE_SECTION_HEADER*)(fileHeader - 4 + sizeof_IMAGE_NT_HEADERS); + + for (int i = 0; i < numberOfSections; i++) + { + sections[i] = new SectionHeader(); + + sections[i].VirtualAddress = secHeader->VirtualAddress; + sections[i].SizeOfRawData = secHeader->SizeOfRawData; + sections[i].PointerToRawData = secHeader->PointerToRawData; + + secHeader++; + } + + int offset = (int)AddressToOffset((int)directory->VirtualAddress, sections); + + IMAGE_DEBUG_DIRECTORY* dbg_dir = (IMAGE_DEBUG_DIRECTORY*)((int)fixedLoadedImage->MappedAddress + (int)offset); + + offset = (int)AddressToOffset((int)dbg_dir->AddressOfRawData, sections); + PdbInfo* pdbInfo = (PdbInfo*)((int)fixedLoadedImage->MappedAddress + (int)offset); + + byte* path = &pdbInfo->PdbFileName; + + this.pdbPath = ""; + while (*path != 0) + { + this.pdbPath += Convert.ToChar(*path); + path++; + } + + if (!UnMapAndLoad(ref _loadedImage)) + { + errorMessages.Add("UnMapAndLoad failed!"); + } + } + } + } + } } } \ No newline at end of file From 5b1b074ed752a04ac0678d62bdd7e9c1a4d9ac34 Mon Sep 17 00:00:00 2001 From: Stefan Winkler Date: Sun, 17 Apr 2016 09:57:24 +0200 Subject: [PATCH 3/3] Add sanity checks in PDBPathExtractor Return if no debug informations are available. --- .../DiaResolver/NativeMethods.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/GoogleTestAdapter/DiaResolver/NativeMethods.cs b/GoogleTestAdapter/DiaResolver/NativeMethods.cs index 704b9447e..bc8b775c8 100644 --- a/GoogleTestAdapter/DiaResolver/NativeMethods.cs +++ b/GoogleTestAdapter/DiaResolver/NativeMethods.cs @@ -557,6 +557,16 @@ public PDBPathExtractor(string executable, List errorMessages) directory = &(optHeader64->DataDirectory) + 6; } + // return if no debugging information is available + if (directory->Size == 0) + { + if (!UnMapAndLoad(ref _loadedImage)) + { + errorMessages.Add("UnMapAndLoad failed!"); + } + return; + } + // get information on all sections. This is required to // map addresses to correct offsets uint numberOfSections = fixedLoadedImage->NumbOfSections; @@ -578,6 +588,16 @@ public PDBPathExtractor(string executable, List errorMessages) IMAGE_DEBUG_DIRECTORY* dbg_dir = (IMAGE_DEBUG_DIRECTORY*)((int)fixedLoadedImage->MappedAddress + (int)offset); + // return if no debugging information is available + if (dbg_dir->SizeOfData == 0) + { + if (!UnMapAndLoad(ref _loadedImage)) + { + errorMessages.Add("UnMapAndLoad failed!"); + } + return; + } + offset = (int)AddressToOffset((int)dbg_dir->AddressOfRawData, sections); PdbInfo* pdbInfo = (PdbInfo*)((int)fixedLoadedImage->MappedAddress + (int)offset);