Inedo Community Forums Forums
    • Recent
    • Tags
    • Popular
    • Login

    Welcome to the Inedo Forums! Check out the Forums Guide for help getting started.

    If you are experiencing any issues with the forum software, please visit the Contact Form on our website and let us know!

    Symbol Server id issue

    Scheduled Pinned Locked Moved Support
    17 Posts 3 Posters 84 Views
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • I Offline
      it_9582
      last edited by

      Hi @atripp,

      yes, this is correct. Windbg wants to download the symbol with ID 0511335... but ProGet shows me Symbol 58544d5... for the pdb in my uploaded nupkg.

      I have now dumped now the pdb file header which shows me that our pdb is a Windows PDB (Full) not a PortablePdb. Could it be possible that the MetadataReader is impossible to read the Windows PDB Files, because it reads the metadata defined by ECMA 335 instead of MSF?

      Why the Download of the 58544d5... Symbol file is impossible although it is listed, remains a mystery to me too. Is it possible to get more debug informations from the System to analyse it deeper?

      Best regards

      atrippA 1 Reply Last reply Reply Quote 0
      • atrippA Offline
        atripp inedo-engineer @it_9582
        last edited by

        Hi @it_9582 ,

        I'm not really sure the differences between the files to be honest. But that could be possible?

        I know some of the PDB code is really ancient (like 30+ years) and it uses old .exe files that no other program can read. I'm not sure if that is true.

        This is the code we use, by the way:

        var provider = MetadataReaderProvider.FromPortablePdbStream(source, MetadataStreamOptions.LeaveOpen);
        var reader = provider.GetMetadataReader();
        if (reader.MetadataKind != MetadataKind.Ecma335)
            return null;
        reader.DebugMetadataHeader.Id //<-- that is how we get the PDB ID
        

        If that function is returning the wrong Id, then I don't know if we can solve the problem. It doesn't seem very feasible to write our own PDB parsing, and I don't think there's another way to do it...

        Thanks,
        Alana

        1 Reply Last reply Reply Quote 0
        • I Offline
          it_9582
          last edited by

          Hi @atripp,
          thanks for the informationsand the Code snippet. We have tested the code but with our PDB File we get the following exception: System.BadImageFormatException: Invalid COR20 header signature on the call of GetMetadataReader(). Could this the reason for the issue?

          A college of me has implemented a sample C# readout of the GUID and AGE for the old Windows PDB Full format. Would it be a possibility to you, to add this code which allows to readout the needed information and publish the pdb correctly on the nuget symbol server feed? I could oversend you the implementation.

          As a side note. We have successfully used a Asset-Feed to fake a Symbol-Server:

          • Create the needed Folder-Structure on the Asset-Feed
            • <pdb-filename>/<PDB-GUID><PDB-AGE>
          • Store the PDB into this Folder-Structure
            • <pdb-filename>/<PDB-GUID><PDB-AGE>/<pdb-file>

          This was a working workaround, but it is not really nice to handle this construct, instead of bundling a nuget-Package with the pdb.

          Best regards

          atrippA 1 Reply Last reply Reply Quote 0
          • atrippA Offline
            atripp inedo-engineer @it_9582
            last edited by

            Hi @it_9582 ,

            If there's an error reading the file using GetMetadataReader, then we load it using the MicrosoftPdbFile class that we wrote. So, I'm guessing that's what's causing the wrong information?

            Anyway, let me share you the full code for the PortablePdbFile class. I summarized it before, but you can see the full context of what we're doing any why.

            using System.Collections.Immutable;
            using System.IO;
            using System.Reflection.Metadata;
            
            namespace Inedo.ProGet.Symbols;
            
            public sealed class PortablePdbFile : IPdbFile
            {
                private readonly MetadataReader metadataReader;
            
                private PortablePdbFile(MetadataReader metadataReader) => this.metadataReader = metadataReader;
            
                // visual studio always treats this value like a guid, despite the portable pdb spec
                public ImmutableArray<byte> Id => this.metadataReader.DebugMetadataHeader.Id.RemoveRange(16, 4);
            
                // not really age, but actually last 4 bytes of id - ignored by visual studio
                uint IPdbFile.Age => BitConverter.ToUInt32(this.metadataReader.DebugMetadataHeader.Id.ToArray(), 16);
                bool IPdbFile.IsPortable => true;
            
                public IEnumerable<string> GetSourceFileNames()
                {
                    foreach (var docHandle in this.metadataReader.Documents)
                    {
                        if (!docHandle.IsNil)
                        {
                            var doc = this.metadataReader.GetDocument(docHandle);
                            yield return this.metadataReader.GetString(doc.Name);
                        }
                    }
                }
            
                public static PortablePdbFile Load(Stream source)
                {
                    if (source == null)
                        throw new ArgumentNullException(nameof(source));
            
                    try
                    {
                        var provider = MetadataReaderProvider.FromPortablePdbStream(source, MetadataStreamOptions.LeaveOpen);
                        var reader = provider.GetMetadataReader();
                        if (reader.MetadataKind != MetadataKind.Ecma335)
                            return null;
            
                        return new PortablePdbFile(reader);
                    }
                    catch
                    {
                        return null;
                    }
                }
            
                void IDisposable.Dispose()
                {
                }
            }
            
            1 Reply Last reply Reply Quote 0
            • I Offline
              it_9582
              last edited by

              Hi @atripp,
              that makes sense. Than in our situation the the PortablePdbFile load will fail and your MicrosoftPdbFile class will load the informations, but that seems to be wrong, because the ID not match.

              Are there any possibilities to fix this possible failed readout in the MicrosoftPdbFile class?

              Best regards

              1 Reply Last reply Reply Quote 0
              • atrippA Offline
                atripp inedo-engineer
                last edited by

                Hi @it_9582 ,

                It's certainly possible; there's a few hundred lines of code that make up the MicrosoftPdbFile class, so I don't know which parts to share with you. Of course I'm happy to share it all if you'd like.

                Since you mentioned your colleague was able to read the file, perhaps you can share what you did, and I can see how it compares to our code?

                Thanks,
                Alana

                1 Reply Last reply Reply Quote 0
                • I Offline
                  it_9582
                  last edited by

                  Hi @atripp,
                  I understand that it will not be simple to find the possible issue in the huge MicrosoftPdbFile class. As first step it would be nice if you could compare the code of my colleague with your class and if this not help than you could possibly share the class code with me so that we could verify this.

                  The PdbFile class of my colleague is the following:

                  namespace Pdbreader;
                  
                  // Adapted and ported from https://github.com/symstore/symstore/blob/master/symstore/pdb.py
                  /*
                   * Copyright(c) 2019 Lennart Blanco
                   *
                   * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
                   * associated documentation files (the "Software"), to deal in the Software without restriction,
                   * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
                   * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
                   * subject to the following conditions:
                   *
                   * The above copyright notice and this permission notice shall be included in all copies or substantial
                   * portions of the Software.
                   *
                   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
                   * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
                   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
                   * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
                   * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
                   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
                   */
                  
                  using System.Buffers.Binary;
                  
                  public class PdbInvalidSignatureException : Exception
                  {
                      public PdbInvalidSignatureException() : base("Invalid PDB signature.") { }
                      public PdbInvalidSignatureException(string message) : base(message) { }
                      public PdbInvalidSignatureException(string message, Exception inner) : base(message, inner) { }
                  }
                  
                  /// <summary>
                  /// This class supports reading Windows PDB files and extracting the GUID and Age found
                  /// inside to create the GUID used by the Symbol server path.
                  /// </summary>
                  public class PdbFile
                  {
                      private static ReadOnlySpan<byte> ExpectedSignature =>
                      [
                          0x4D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, 0x74, 0x20, 0x43, 0x2F,
                          0x43, 0x2B, 0x2B, 0x20, 0x4D, 0x53, 0x46, 0x20, 0x37, 0x2E, 0x30, 0x30,
                          0x0D, 0x0A, 0x1A, 0x44, 0x53, 0x00, 0x00, 0x00
                      ];
                  
                      /// <summary>
                      /// Parses a PDB file at the given path, extracting its GUID and age.
                      /// </summary>
                      /// <param name="filepath">The full path to the <c>.pdb</c> file to read.</param>
                      /// <exception cref="PdbInvalidSignatureException">
                      /// Thrown if the file does not begin with the expected MSF 7.00 signature.
                      /// </exception>
                      public PdbFile(string filepath)
                      {
                          using var fs = new FileStream(filepath, FileMode.Open, FileAccess.Read);
                          using var reader = new BinaryReader(fs);
                  
                          Span<byte> sig = stackalloc byte[ExpectedSignature.Length];
                          var bytesRead = reader.Read(sig);
                          if (bytesRead < ExpectedSignature.Length || !sig.SequenceEqual(ExpectedSignature))
                          {
                              throw new PdbInvalidSignatureException();
                          }
                  
                          var pageSize    = reader.ReadInt32();
                          reader.ReadInt32(); // free page map index
                          reader.ReadInt32(); // pages used
                          var rootDirSize = reader.ReadInt32();
                          reader.ReadInt32(); // reserved
                  
                          var root = new Root(reader, pageSize, rootDirSize);
                  
                          // --- Read PDB stream (stream index 1) ---
                          var pdbStreamData = root.ReadStream(1);
                          using var pdbMs = new MemoryStream(pdbStreamData);
                          using var pdbBr = new BinaryReader(pdbMs);
                  
                          pdbBr.ReadInt32(); // Version
                          pdbBr.ReadInt32(); // Signature/Timestamp
                          var pdbAge    = pdbBr.ReadInt32();
                          var guidBytes = pdbBr.ReadBytes(16);
                          Guid = new Guid(guidBytes);
                  
                          // --- Read DBI stream (stream index 3) ---
                          var dbiStreamData = root.ReadStream(3);
                          if (dbiStreamData.Length >= 12)
                          {
                              using var dbiBr = new BinaryReader(new MemoryStream(dbiStreamData));
                              dbiBr.ReadInt32(); // Signature
                              dbiBr.ReadInt32(); // Version
                              Age = dbiBr.ReadInt32();
                          }
                          else
                          {
                              Age = pdbAge;
                          }
                      }
                  
                      /// <summary>
                      /// Get the Guid found in the PDB file
                      /// </summary>
                      public Guid Guid { get; }
                      
                      /// <summary>
                      /// The age of the PDB, used to match it against its corresponding binary.
                      /// Sourced from the DBI stream where available, falling back to the PDB info stream.
                      /// </summary>
                      public int Age   { get; }
                  
                      /// <summary>
                      /// Returns the symbol store lookup key for this PDB in the format <c>{GUID}{Age}</c>,
                      /// e.g. <c>3844DBD05CC449FEB97320D39D627B2F1</c>.
                      /// </summary>
                      public string ToStoreFormat() => $"{Guid:N}".ToUpperInvariant() + Age;
                  }
                  
                  internal class Root
                  {
                      private readonly BinaryReader _reader;
                      private readonly int _pageSize;
                      private readonly int _size;
                  
                      private uint[]? _rootPages;
                      private uint? _cachedNumStreams;
                      private int[]? _cachedStreamSizes;
                  
                      private const int HeaderSize = 32; // signature
                      private const int HeaderInts = 5;  // pageSize, fpmIdx, pagesUsed, rootDirSize, reserved
                  
                      public Root(BinaryReader reader, int pageSize, int size)
                      {
                          _reader   = reader;
                          _pageSize = pageSize;
                          _size     = size;
                      }
                  
                      private static int PageCount(int size, int pageSize) => (int)Math.Ceiling((double)size / pageSize);
                  
                      private uint[] GetRootPages()
                      {
                          if (_rootPages is not null) return _rootPages;
                  
                          var numPages = PageCount(_size, _pageSize);
                  
                          _reader.BaseStream.Seek(HeaderSize + 4 * HeaderInts, SeekOrigin.Begin);
                  
                          var numIndexPages = PageCount(numPages * 4, _pageSize);
                          var indexPages    = new uint[numIndexPages];
                          for (var i = 0; i < numIndexPages; i++)
                          {
                              indexPages[i] = _reader.ReadUInt32();
                          }
                  
                          var rootPageData = new byte[numIndexPages * _pageSize];
                          for (var i = 0; i < indexPages.Length; i++)
                          {
                              _reader.BaseStream.Seek(indexPages[i] * _pageSize, SeekOrigin.Begin);
                              var chunk = _reader.ReadBytes(_pageSize);
                              Buffer.BlockCopy(chunk, 0, rootPageData, i * _pageSize, chunk.Length);
                          }
                  
                          _rootPages = new uint[numPages];
                          using var br = new BinaryReader(new MemoryStream(rootPageData));
                          for (var i = 0; i < numPages; i++)
                          {
                              _rootPages[i] = br.ReadUInt32();
                          }
                  
                          return _rootPages;
                      }
                  
                      /// <summary>Reads <paramref name="length"/> bytes starting at <paramref name="start"/> within
                      /// the root directory stream (not an arbitrary stream).</summary>
                      private byte[] ReadFromRoot(int start, int length)
                      {
                          var pages      = GetRootPages();
                          var result     = new byte[length];
                          var destOffset = 0;
                          var startPage  = start / _pageSize;
                          var startByte  = start % _pageSize;
                  
                          _reader.BaseStream.Seek(pages[startPage] * _pageSize + startByte, SeekOrigin.Begin);
                  
                          var toRead = Math.Min(length, _pageSize - startByte);
                          Buffer.BlockCopy(_reader.ReadBytes(toRead), 0, result, destOffset, toRead);
                          destOffset += toRead;
                          length     -= toRead;
                          startPage++;
                  
                          while (length > 0)
                          {
                              _reader.BaseStream.Seek(pages[startPage] * _pageSize, SeekOrigin.Begin);
                              toRead = Math.Min(_pageSize, length);
                              Buffer.BlockCopy(_reader.ReadBytes(toRead), 0, result, destOffset, toRead);
                              destOffset += toRead;
                              length     -= toRead;
                              startPage++;
                          }
                  
                          return result;
                      }
                  
                      private uint GetNumStreams() =>
                          _cachedNumStreams ??= BinaryPrimitives.ReadUInt32LittleEndian(ReadFromRoot(0, 4));
                  
                      private int[] GetAllStreamSizes()
                      {
                          if (_cachedStreamSizes is not null) return _cachedStreamSizes;
                  
                          var count = (int)GetNumStreams();
                          var raw   = ReadFromRoot(4, count * 4);
                          _cachedStreamSizes = new int[count];
                          for (var i = 0; i < count; i++)
                          {
                              _cachedStreamSizes[i] = BinaryPrimitives.ReadInt32LittleEndian(raw.AsSpan(i * 4, 4));
                          }
                  
                          return _cachedStreamSizes;
                      }
                  
                      private uint[] GetStreamPages(int streamIndex)
                      {
                          var sizes     = GetAllStreamSizes();
                          var numStreams = (int)GetNumStreams();
                  
                          if (streamIndex >= numStreams)
                          {
                              throw new IndexOutOfRangeException($"Stream index {streamIndex} out of range (count: {numStreams}).");
                          }
                  
                          var pagesOffset = 4 + 4 * numStreams;
                          for (var i = 0; i < streamIndex; i++)
                          {
                              var sz = sizes[i];
                              if (sz > 0)
                              {
                                  pagesOffset += PageCount(sz, _pageSize) * 4;
                              }
                          }
                  
                          var streamSize = sizes[streamIndex];
                          if (streamSize <= 0) return []; // nil or empty stream
                  
                          var numPages = PageCount(streamSize, _pageSize);
                          var rawPages = ReadFromRoot(pagesOffset, 4 * numPages);
                          var pages    = new uint[numPages];
                          for (var i = 0; i < numPages; i++)
                          {
                              pages[i] = BinaryPrimitives.ReadUInt32LittleEndian(rawPages.AsSpan(i * 4, 4));
                          }
                  
                          return pages;
                      }
                  
                      /// <summary>Reads the full contents of a numbered MSF stream across all its pages.</summary>
                      public byte[] ReadStream(int streamIndex)
                      {
                          var sizes = GetAllStreamSizes();
                          if (streamIndex >= sizes.Length)
                          {
                              return [];
                          }
                  
                          var streamSize = sizes[streamIndex];
                          if (streamSize <= 0)
                          {
                              return [];
                          }
                  
                          var pages      = GetStreamPages(streamIndex);
                          var result     = new byte[streamSize];
                          var remaining  = streamSize;
                          var destOffset = 0;
                  
                          foreach (var page in pages)
                          {
                              _reader.BaseStream.Seek(page * _pageSize, SeekOrigin.Begin);
                              var toRead = Math.Min(_pageSize, remaining);
                              var chunk  = _reader.ReadBytes(toRead);
                              Buffer.BlockCopy(chunk, 0, result, destOffset, toRead);
                              destOffset += toRead;
                              remaining  -= toRead;
                          }
                  
                          return result;
                      }
                  }
                  

                  and he wrote this simple test program for this:

                  namespace Pdbreader;
                  
                  public static class PdbReader
                  {
                      public static void Main(string[] args)
                      {
                          if (args.Length != 1)
                          {
                              Console.WriteLine("Usage: PdbReader <pdb_file>");
                              return;
                          }
                  
                          try
                          {
                              var path = args[0];
                              var filename = Path.GetFileName(path);
                              var pdb = new PdbFile(path);
                              Console.WriteLine($"GUID: {pdb.ToStoreFormat()}");
                              Console.WriteLine($"Symstore path should be: <root>/{filename}/{pdb.ToStoreFormat()}/{filename}");
                  
                              // The following will not work with windows pdbs, only the portable ones
                              //var metaStr = PortableMetaData.GetGuidString(args[0]);
                              //Console.WriteLine($"Meta-GUID: {metaStr}");
                          }
                          catch (Exception e)
                          {
                              Console.WriteLine($"Exception: {e}");
                          }
                      }
                  }
                  

                  Best regards

                  atrippA 1 Reply Last reply Reply Quote 0
                  • atrippA Offline
                    atripp inedo-engineer @it_9582
                    last edited by

                    Hi @it_9582 ,

                    Sorry it looks like we're dealing with a lot more code than I expected we would. I really don't know what to look at, and neither your code or our code makes sense (it's been many many years since anyone edited it.

                    I'm not sure if it's helpful, but I'll share the code of our class. If you spot anything simple to change, we can explore it. Otherwise, I think the only way to move forward would be for you to share us some example nuget packages that we can set a debugger to.

                    Here's the MicrosoftPdbFile class, which i combined into one giant string here:

                    using System;
                    using System.Collections;
                    using System.Collections.Generic;
                    using System.Collections.Immutable;
                    using System.IO;
                    using System.Linq;
                    using System.Text;
                    
                    namespace Inedo.ProGet.Symbols;
                    
                    /// <summary>
                    /// Provides access to the data contained in a Microsoft PDB file.
                    /// </summary>
                    public sealed class MicrosoftPdbFile : IDisposable, IPdbFile
                    {
                        private RootIndex root;
                        private Dictionary<string, int> nameIndex;
                        private bool leaveStreamOpen;
                        private bool disposed;
                    
                        /// <summary>
                        /// Initializes a new instance of the <see cref="MicrosoftPdbFile"/> class.
                        /// </summary>
                        /// <param name="stream">Stream which is backed by a PDB file.</param>
                        /// <param name="leaveStreamOpen">Value indicating whether to leave the stream open after this instance is disposed.</param>
                        public MicrosoftPdbFile(Stream stream, bool leaveStreamOpen)
                        {
                            if (stream == null)
                                throw new ArgumentNullException(nameof(stream));
                    
                            this.leaveStreamOpen = leaveStreamOpen;
                            this.Initialize(stream);
                        }
                    
                        /// <summary>
                        /// Gets the PDB signature.
                        /// </summary>
                        public uint Signature { get; private set; }
                        /// <summary>
                        /// Gets the PDB age.
                        /// </summary>
                        public uint Age { get; private set; }
                        /// <summary>
                        /// Gets the PDB guid.
                        /// </summary>
                        public Guid Guid { get; private set; }
                    
                        ImmutableArray<byte> IPdbFile.Id => this.Guid.ToByteArray().ToImmutableArray();
                        bool IPdbFile.IsPortable => false;
                    
                        /// <summary>
                        /// Returns a stream backed by the data in a named PDB stream.
                        /// </summary>
                        /// <param name="streamName">Name of the PDB stream to open.</param>
                        /// <returns>Stream backed by the specified named stream.</returns>
                        public Stream OpenStream(string streamName)
                        {
                            if (streamName == null)
                                throw new ArgumentNullException(nameof(streamName));
                    
                            int? streamIndex = this.TryGetStream(streamName);
                            if (streamIndex == null)
                                throw new InvalidOperationException($"Stream {streamName} was not found.");
                    
                            return this.root.OpenRead((int)streamIndex);
                        }
                        /// <summary>
                        /// Returns an enumeration of all of the stream names in the PDB file.
                        /// </summary>
                        /// <returns>Enumeration of all stream names.</returns>
                        public IEnumerable<string> EnumerateStreams() => this.nameIndex.Keys;
                        /// <summary>
                        /// Returns an enumeration of all of the source file names in the PDB file.
                        /// </summary>
                        /// <returns>Enumeration of all of the source file names.</returns>
                        public IEnumerable<string> GetSourceFileNames()
                        {
                            var srcFileNames = this.EnumerateStreams()
                                .Where(s => s.StartsWith("/src/files/", StringComparison.OrdinalIgnoreCase))
                                .Select(s => s.Substring("/src/files/".Length))
                                .ToHashSet(StringComparer.OrdinalIgnoreCase);
                    
                            try
                            {
                                using (var namesStream = this.OpenStream("/names"))
                                using (var namesReader = new BinaryReader(namesStream))
                                {
                                    namesStream.Position = 8;
                                    int length = namesReader.ReadInt32();
                                    long endPos = length + 12;
                    
                                    while (namesStream.Position < endPos && namesStream.Position < namesStream.Length)
                                    {
                                        try
                                        {
                                            var name = ReadNullTerminatedString(namesReader);
                                            if (name.Length > 0 && Path.IsPathRooted(name))
                                                srcFileNames.Add(name);
                                        }
                                        catch
                                        {
                                            // Can't read name
                                        }
                                    }
                                }
                            }
                            catch
                            {
                                // Can't enumerate names stream
                            }
                    
                            return srcFileNames;
                        }
                    
                        /// <summary>
                        /// Closes the PDB file.
                        /// </summary>
                        public void Close()
                        {
                            if (!this.disposed)
                            {
                                this.root.Close(this.leaveStreamOpen);
                                this.disposed = true;
                            }
                        }
                        void IDisposable.Dispose() => this.Close();
                    
                        private void Initialize(Stream stream)
                        {
                            var fileSignature = new byte[0x20];
                            stream.Read(fileSignature, 0, fileSignature.Length);
                    
                            this.root = new RootIndex(stream);
                    
                            using (var sigStream = this.root.OpenRead(1))
                            using (var reader = new BinaryReader(sigStream))
                            {
                                uint version = reader.ReadUInt32();
                                this.Signature = reader.ReadUInt32();
                                this.Age = reader.ReadUInt32();
                                this.Guid = new Guid(reader.ReadBytes(16));
                    
                                this.nameIndex = ReadNameIndex(reader);
                            }
                        }
                        private int? TryGetStream(string name) => this.nameIndex.TryGetValue(name, out int index) ? (int?)index : null;
                    
                        private static Dictionary<string, int> ReadNameIndex(BinaryReader reader)
                        {
                            int stringOffset = reader.ReadInt32();
                    
                            var startOffset = reader.BaseStream.Position;
                            reader.BaseStream.Seek(stringOffset, SeekOrigin.Current);
                    
                            int count = reader.ReadInt32();
                            int hashTableSize = reader.ReadInt32();
                    
                            var present = new BitArray(reader.ReadBytes(reader.ReadInt32() * 4));
                            var deleted = new BitArray(reader.ReadBytes(reader.ReadInt32() * 4));
                            if (deleted.Cast<bool>().Any(b => b))
                                throw new InvalidDataException("PDB format not supported: deleted bits are not 0.");
                    
                            var nameIndex = new Dictionary<string, int>(hashTableSize + 100, StringComparer.OrdinalIgnoreCase);
                    
                            for (int i = 0; i < hashTableSize; i++)
                            {
                                if (i < present.Length && present[i])
                                {
                                    int ns = reader.ReadInt32();
                                    int ni = reader.ReadInt32();
                    
                                    var pos = reader.BaseStream.Position;
                                    reader.BaseStream.Position = startOffset + ns;
                                    var name = ReadNullTerminatedString(reader);
                                    reader.BaseStream.Position = pos;
                    
                                    nameIndex.Add(name, ni);
                                }
                            }
                    
                            return nameIndex;
                        }
                        private static string ReadNullTerminatedString(BinaryReader reader)
                        {
                            var data = new List<byte>();
                            var b = reader.ReadByte();
                            while (b != 0)
                            {
                                data.Add(b);
                                b = reader.ReadByte();
                            }
                    
                            return Encoding.UTF8.GetString(data.ToArray());
                        }
                    
                        private sealed class PagedFile : IDisposable
                        {
                            private LinkedList<CachedPage> pages = new LinkedList<CachedPage>();
                            private Stream baseStream;
                            private readonly object lockObject = new object();
                            private BitArray freePages;
                            private uint pageSize;
                            private uint pageCount;
                            private bool disposed;
                    
                            public PagedFile(Stream baseStream, uint pageSize, uint pageCount)
                            {
                                this.baseStream = baseStream;
                                this.pageSize = pageSize;
                                this.pageCount = pageCount;
                                this.CacheSize = 1000;
                            }
                    
                            public int CacheSize { get; }
                            public uint PageSize => this.pageSize;
                            public uint PageCount => this.pageCount;
                    
                            public void InitializeFreePageList(byte[] data)
                            {
                                this.freePages = new BitArray(data);
                            }
                            public byte[] GetFreePageList()
                            {
                                var data = new byte[this.freePages.Count / 8];
                                for (int i = 0; i < data.Length; i++)
                                {
                                    for (int j = 0; j < 8; j++)
                                    {
                                        if (this.freePages[(i * 8) + j])
                                            data[i] |= (byte)(1 << j);
                                    }
                                }
                    
                                return data;
                            }
                            public byte[] GetPage(uint pageIndex)
                            {
                                if (this.disposed)
                                    throw new ObjectDisposedException(nameof(PagedFile));
                                if (pageIndex >= this.pageCount)
                                    throw new ArgumentOutOfRangeException();
                    
                                lock (this.lockObject)
                                {
                                    var page = this.pages.FirstOrDefault(p => p.PageIndex == pageIndex);
                                    if (page != null)
                                    {
                                        this.pages.Remove(page);
                                    }
                                    else
                                    {
                                        var buffer = new byte[this.pageSize];
                                        this.baseStream.Position = this.pageSize * pageIndex;
                                        this.baseStream.Read(buffer, 0, buffer.Length);
                                        page = new CachedPage
                                        {
                                            PageIndex = pageIndex,
                                            PageData = buffer
                                        };
                                    }
                    
                                    while (this.pages.Count >= this.CacheSize)
                                    {
                                        this.pages.RemoveLast();
                                    }
                    
                                    this.pages.AddFirst(page);
                    
                                    return page.PageData;
                                }
                            }
                            public void Dispose()
                            {
                                this.baseStream.Dispose();
                                this.pages = null;
                                this.disposed = true;
                            }
                    
                            private sealed class CachedPage : IEquatable<CachedPage>
                            {
                                public uint PageIndex;
                                public byte[] PageData;
                    
                                public bool Equals(CachedPage other) => this.PageIndex == other.PageIndex && this.PageData == other.PageData;
                                public override bool Equals(object obj) => obj is CachedPage p ? this.Equals(p) : false;
                                public override int GetHashCode() => this.PageIndex.GetHashCode();
                            }
                        }
                        private sealed class PdbStream : Stream
                        {
                            private RootIndex root;
                            private StreamInfo streamInfo;
                            private uint position;
                    
                            public PdbStream(RootIndex root, StreamInfo streamInfo)
                            {
                                this.root = root;
                                this.streamInfo = streamInfo;
                            }
                    
                            public override bool CanRead => true;
                            public override bool CanSeek => true;
                            public override bool CanWrite => false;
                            public override long Length => this.streamInfo.Length;
                            public override long Position
                            {
                                get => this.position;
                                set => this.position = (uint)value;
                            }
                    
                            public override void Flush()
                            {
                            }
                            public override int Read(byte[] buffer, int offset, int count)
                            {
                                if (buffer == null)
                                    throw new ArgumentNullException(nameof(buffer));
                    
                                int bytesRemaining = Math.Min(count, (int)(this.Length - this.position));
                                int bytesRead = 0;
                    
                                while (bytesRemaining > 0)
                                {
                                    uint currentPage = this.position / this.root.Pages.PageSize;
                                    uint currentPageOffset = this.position % this.root.Pages.PageSize;
                    
                                    var page = this.root.Pages.GetPage(this.streamInfo.Pages[currentPage]);
                    
                                    int bytesToCopy = Math.Min(bytesRemaining, (int)(this.root.Pages.PageSize - currentPageOffset));
                    
                                    Array.Copy(page, currentPageOffset, buffer, offset + bytesRead, bytesToCopy);
                                    bytesRemaining -= bytesToCopy;
                                    this.position += (uint)bytesToCopy;
                                    bytesRead += bytesToCopy;
                                }
                    
                                return bytesRead;
                            }
                            public override int ReadByte()
                            {
                                if (this.position >= this.Length)
                                    return -1;
                    
                                uint currentPage = this.position / this.root.Pages.PageSize;
                                uint currentPageOffset = this.position % this.root.Pages.PageSize;
                    
                                var page = this.root.Pages.GetPage(this.streamInfo.Pages[currentPage]);
                                this.position++;
                    
                                return page[currentPageOffset];
                            }
                            public override long Seek(long offset, SeekOrigin origin)
                            {
                                switch (origin)
                                {
                                    case SeekOrigin.Begin:
                                        this.position = (uint)offset;
                                        break;
                    
                                    case SeekOrigin.Current:
                                        this.position = (uint)((long)this.position + offset);
                                        break;
                    
                                    case SeekOrigin.End:
                                        this.position = (uint)(this.Length + offset);
                                        break;
                                }
                    
                                return this.position;
                            }
                            public override void SetLength(long value) => throw new NotSupportedException();
                            public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
                            public override void WriteByte(byte value) => throw new NotSupportedException();
                        }
                        private sealed class RootIndex
                        {
                            private BinaryReader reader;
                            private List<StreamInfo> streams = new List<StreamInfo>();
                            private StreamInfo rootStreamInfo;
                            private StreamInfo rootPageListStreamInfo;
                            private uint freePageMapIndex;
                    
                            public RootIndex(Stream stream)
                            {
                                this.reader = new BinaryReader(stream);
                                this.Initialize();
                            }
                    
                            public PagedFile Pages { get; private set; }
                    
                            public Stream OpenRead(int streamIndex)
                            {
                                var streamInfo = this.streams[streamIndex];
                                return new PdbStream(this, streamInfo);
                            }
                            public void Close(bool leaveStreamOpen)
                            {
                                if (!leaveStreamOpen)
                                    this.reader.Dispose();
                            }
                    
                            private void Initialize()
                            {
                                this.reader.BaseStream.Position = 0x20;
                                var pageSize = this.reader.ReadUInt32();
                                var pageFlags = this.reader.ReadUInt32();
                                var pageCount = this.reader.ReadUInt32();
                                var rootSize = this.reader.ReadUInt32();
                                this.reader.ReadUInt32(); // skip reserved
                    
                                this.Pages = new PagedFile(this.reader.BaseStream, pageSize, pageCount);
                                this.freePageMapIndex = pageFlags;
                    
                                // Calculate the number of pages needed to store the root data
                                int rootPageCount = (int)(rootSize / pageSize);
                                if ((rootSize % pageSize) != 0)
                                    rootPageCount++;
                    
                                // Calculate the number of pages needed to store the list of pages
                                int rootIndexPages = (rootPageCount * 4) / (int)pageSize;
                                if (((rootPageCount * 4) % (int)pageSize) != 0)
                                    rootIndexPages++;
                    
                                // Read the page indices of the pages that contain the root pages
                                var rootIndices = new List<uint>(rootIndexPages);
                                for (int i = 0; i < rootIndexPages; i++)
                                    rootIndices.Add(this.reader.ReadUInt32());
                    
                                // Read the free page map
                                this.reader.BaseStream.Position = pageFlags * pageSize;
                                this.Pages.InitializeFreePageList(this.reader.ReadBytes((int)pageSize));
                    
                                this.rootPageListStreamInfo = new StreamInfo(rootIndices.ToArray(), (uint)rootPageCount * 4);
                    
                                // Finally actually read the root indices themselves
                                var rootPages = new List<uint>(rootPageCount);
                                using (var rootPageListStream = new PdbStream(this, this.rootPageListStreamInfo))
                                using (var pageReader = new BinaryReader(rootPageListStream))
                                {
                                    for (int i = 0; i < rootPageCount; i++)
                                        rootPages.Add(pageReader.ReadUInt32());
                                }
                    
                                this.rootStreamInfo = new StreamInfo(rootPages.ToArray(), rootSize);
                                using (var rootStream = new PdbStream(this, this.rootStreamInfo))
                                {
                                    var rootReader = new BinaryReader(rootStream);
                    
                                    uint streamCount = rootReader.ReadUInt32();
                    
                                    var streamLengths = new uint[streamCount];
                                    for (int i = 0; i < streamLengths.Length; i++)
                                        streamLengths[i] = rootReader.ReadUInt32();
                    
                                    var streamPages = new uint[streamCount][];
                                    for (int i = 0; i < streamPages.Length; i++)
                                    {
                                        if (streamLengths[i] > 0 && streamLengths[i] < int.MaxValue)
                                        {
                                            uint streamLengthInPages = streamLengths[i] / pageSize;
                                            if ((streamLengths[i] % pageSize) != 0)
                                                streamLengthInPages++;
                    
                                            streamPages[i] = new uint[streamLengthInPages];
                                            for (int j = 0; j < streamPages[i].Length; j++)
                                                streamPages[i][j] = rootReader.ReadUInt32();
                                        }
                                    }
                    
                                    for (int i = 0; i < streamLengths.Length; i++)
                                    {
                                        this.streams.Add(
                                            new StreamInfo(streamPages[i], streamLengths[i])
                                        );
                                    }
                                }
                            }
                        }
                        private sealed class StreamInfo
                        {
                            private uint[] pages;
                            private uint length;
                    
                            public StreamInfo(uint[] pages, uint length, bool dirty = false)
                            {
                                this.pages = pages;
                                this.length = length;
                                this.IsDirty = dirty;
                            }
                    
                            public uint[] Pages
                            {
                                get => this.pages;
                                set
                                {
                                    if (this.pages != value)
                                    {
                                        this.pages = value;
                                        this.IsDirty = true;
                                    }
                                }
                            }
                            public uint Length
                            {
                                get => this.length;
                                set
                                {
                                    if (this.length != value)
                                    {
                                        this.length = value;
                                        this.IsDirty = true;
                                    }
                                }
                            }
                            public bool IsDirty { get; private set; }
                        }
                    }
                    
                    1 Reply Last reply Reply Quote 0
                    • I Offline
                      it_9582
                      last edited by

                      Hi @atripp,

                      sorry for my late response, but I was in vocation. We have tested your send MicrosoftPdbFile class without any issues.
                      The read out PDB Informations (Guid and Age) are the same as our code read and the llvm-pdbutil dump --summary tool.

                      It seams so that another code fragment around this class will cause this issue.

                      Best regards

                      atrippA 1 Reply Last reply Reply Quote 0
                      • atrippA Offline
                        atripp inedo-engineer @it_9582
                        last edited by

                        Hi @it_9582 ,

                        Thanks for checking that! Well, I'm not sure then :)

                        From here, how about sending us the package? Then I can upload it and see about debugging in ProGet to find out where it's coming from.

                        If you can open a ticket and reference QA-3010 somewhere it'll link the issues right-up on our dashboard. Then you can attach the file to that ticket.

                        We'll respond on there, and eventually update this thread once we figure out the issue.

                        Thanks,
                        Alana

                        1 Reply Last reply Reply Quote 0
                        • gdivisG Offline
                          gdivis inedo-engineer
                          last edited by

                          We've tracked this down to an issue with native symbol files indexed in a PostgreSQL database. It will be fixed by PG-3243 in ProGet 2025.24, scheduled for release tomorrow (20 March 2026).

                          Packages with affected symbol files should be re-uploaded to ProGet following this release to resolve the lookup issue.

                          1 Reply Last reply Reply Quote 0
                          • I Offline
                            it_9582
                            last edited by

                            Hi @atripp, @gdivis,

                            we have checked this issue today and all seems to work like a charm.

                            Big thanks for quick and really good work :)

                            Best regards

                            1 Reply Last reply Reply Quote 0

                            Hello! It looks like you're interested in this conversation, but you don't have an account yet.

                            Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.

                            With your input, this post could be even better 💗

                            Register Login
                            • 1 / 1
                            • First post
                              Last post
                            Inedo Website Home • Support Home • Code of Conduct • Forums Guide • Documentation