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