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



  • Hi there,

    I currently want to evaluate the possibility to of using a nuget Feed as Symbol-Server (configured as Mixed) for our self compiled applications.
    To do this I attached the *.pdb File into a simple nuget package based on the following sample nuspec File:

    <?xml version="1.0"?>
    <package >
      <metadata>
        <id>Sample Tool</id>
        <version>1.0.0</version>
        <authors>Our Company</authors>
        <description>Native C++ Application</description>
      </metadata>
      <files>
        <file src="sample_tool.pdb" target="lib" />
      </files>
    </package>
    

    I uploaded this package into the feed, which shows me the package and also on the Symbols tab the pdb file with Id (58544d524263363572307558336f79534a39392b45413d3d) and Age (1).

    Now I configured the windbg to interact with the Feed Symbol-Server (set Symbol path to <Server-URL>/symbols/<feed-name>) and open the Executable for this pdb File.
    I activated than the noisy symbol loading (!sym noisy) because there are some issues while loading the symbols. The Log shows me that windbg want to download the pdb File from the a different URL, because the id is different (/symbols/<feed-name>/sample_tool.pdb/0511335DB9CE4BAF97DE8C9227DF7E101/sample_tool.pdb
    ).

    I analysed our PDB File and it seems that the ID generation of ProGet has possibly a issue because a call of llvm-pdbutil dump --summary sample_tool.pdb results in the following Information:

    • Age = 1
    • GUID = {0511335D-B9CE-4BAF-97DE-8C9227DF7E10}

    After combining this data (GUID + Age) the result is 0511335DB9CE4BAF97DE8C9227DF7E101 which is the same as Windbg want to download 0511335DB9CE4BAF97DE8C9227DF7E101.

    Do you have any suggestions what I can do to use this feature?

    Best regards


  • inedo-engineer

    Hi @it_9582 ,

    What version of ProGet are you using? There was a recent regression (PG-3204) that was fixed in ProGet 2025.19 with regards to symbol server. S hopefully upgrading will fix the issue.

    Cheers,
    Alana



  • Hi @atripp,

    currently we are using ProGet 2025.14, so we first check if an upgrade of ProGet 2024.19 or higher would fix the Issue.
    I will reply to this topic if the Issue always exists.

    Best regards



  • Hi @atripp,
    we have upgraded our ProGet Version to v2025.22 (Build 14) but the issue seems to be persist.
    I have also repackage and reupload the package but the result is still the same.

    The Information on windbg is also the same (it attempts to download the pdb-file with the ID 0511335DB9CE4BAF97DE8C9227DF7E101 but the Symbol on ProGet-Symbol shows me the ID 58544d524263363572307558336f79534a39392b45413d3d).
    The Log in Windbg looks like the following:

    SYMSRV:  BYINDEX: 0x2
             c:\symcache*<ProGet-Base-URL>/symbols/<feed-name>
             <pdb-name>.pdb
             0511335DB9CE4BAF97DE8C9227DF7E101
    SYMSRV:  HTTPGET: /symbols/<feed-name>/<pdb-name>.pdb/0511335DB9CE4BAF97DE8C9227DF7E101/<pdb-name>.pdb
    SYMSRV:  HttpQueryInfo: 80190194 - HTTP_STATUS_NOT_FOUND
    DBGHELP: <pdb-name> - no symbols loaded
    

    I have tested to manually download the file (over both IDs) but each time I get a 404 from the ProGet-Server with:

    • <ProGet-Base-URL>/symbols/<feed-name>/<pdb-name>.pdb/0511335DB9CE4BAF97DE8C9227DF7E101/<pdb-name>.pdb
    • <ProGet-Base-URL>/symbols/<feed-name>/<pdb-name>.pdb/58544d524263363572307558336f79534a39392b45413d3d/<pdb-name>.pdb

    Best regards


  • inedo-engineer

    Hi @it_9582 ,

    If I'm understanding correctly, the issue is that windbg is attempting to download the symbol with an ID of 0511335..., but the only symbol you're seeing in ProGet is 58544d5...?

    Since this is for C++, this is the Windows/PortablePdb format. In that case, ProGet is using the built-in class called MetadataReader to parse this information. I mean it's possible there's a bug in there, but I think it's more likely that it's the wrong file getting uploaded or something to that effect.

    As far as the URLS... I'm not sure what the correct one is, but windbg seems to try a whole bunch of URLS before it lands on the correct one in ProGet. But if you're seeing that 58544d5... symbol in ProGet, then it would be downloadable.

    Thanks,
    Alana



  • 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


  • inedo-engineer

    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



  • 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


  • inedo-engineer

    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()
        {
        }
    }
    


  • 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


  • inedo-engineer

    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



  • 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


  • inedo-engineer

    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; }
        }
    }
    

Log in to reply
 

Inedo Website HomeSupport HomeCode of ConductForums GuideDocumentation