// dnlib: See LICENSE.txt for more info
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using dnlib.DotNet.Emit;
using dnlib.DotNet.Pdb.Symbols;
using dnlib.DotNet.Pdb.WindowsPdb;
using dnlib.IO;
namespace dnlib.DotNet.Pdb.Managed {
///
/// A managed PDB reader implementation for .NET modules.
///
sealed class PdbReader : SymbolReader {
MsfStream[] streams;
Dictionary names;
Dictionary strings;
List modules;
ModuleDef module;
const int STREAM_ROOT = 0;
const int STREAM_NAMES = 1;
const int STREAM_TPI = 2;
const int STREAM_DBI = 3;
const ushort STREAM_INVALID_INDEX = ushort.MaxValue;
Dictionary documents;
Dictionary functions;
byte[] sourcelinkData;
byte[] srcsrvData;
uint entryPt;
public override PdbFileKind PdbFileKind => PdbFileKind.WindowsPDB;
uint Age { get; set; }
Guid Guid { get; set; }
internal bool MatchesModule => expectedGuid == Guid && expectedAge == Age;
readonly Guid expectedGuid;
readonly uint expectedAge;
public PdbReader(Guid expectedGuid, uint expectedAge) {
this.expectedGuid = expectedGuid;
this.expectedAge = expectedAge;
}
public override void Initialize(ModuleDef module) => this.module = module;
///
/// Read the PDB in the specified stream.
///
/// PDB file data reader
public void Read(DataReader reader) {
try {
ReadInternal(ref reader);
}
catch (Exception ex) {
if (ex is PdbException)
throw;
throw new PdbException(ex);
}
finally {
streams = null;
names = null;
strings = null;
modules = null;
}
}
static uint RoundUpDiv(uint value, uint divisor) => (value + divisor - 1) / divisor;
void ReadInternal(ref DataReader reader) {
string sig = reader.ReadString(30, Encoding.ASCII);
if (sig != "Microsoft C/C++ MSF 7.00\r\n\u001ADS\0")
throw new PdbException("Invalid signature");
reader.Position += 2;
uint pageSize = reader.ReadUInt32();
/*uint fpm = */reader.ReadUInt32();
uint pageCount = reader.ReadUInt32();
uint rootSize = reader.ReadUInt32();
reader.ReadUInt32();
var numOfRootPages = RoundUpDiv(rootSize, pageSize);
var numOfPtrPages = RoundUpDiv(numOfRootPages * 4, pageSize);
if (pageCount * pageSize != reader.Length)
throw new PdbException("File size mismatch");
var pages = new DataReader[pageCount];
uint offset = 0;
for (uint i = 0; i < pageCount; i++) {
pages[i] = reader.Slice(offset, pageSize);
offset += pageSize;
}
var rootPages = new DataReader[numOfRootPages];
int pageIndex = 0;
for (int i = 0; i < numOfPtrPages && pageIndex < numOfRootPages; i++) {
var ptrPage = pages[reader.ReadUInt32()];
ptrPage.Position = 0;
for (; ptrPage.Position < ptrPage.Length && pageIndex < numOfRootPages; pageIndex++)
rootPages[pageIndex] = pages[ptrPage.ReadUInt32()];
}
ReadRootDirectory(new MsfStream(rootPages, rootSize), pages, pageSize);
ReadNames();
if (!MatchesModule)
return;
ReadStringTable();
var tokenMapStream = ReadModules();
documents = new Dictionary(StringComparer.OrdinalIgnoreCase);
foreach (var module in modules) {
if (IsValidStreamIndex(module.StreamId))
module.LoadFunctions(this, ref streams[module.StreamId].Content);
}
if (IsValidStreamIndex(tokenMapStream ?? STREAM_INVALID_INDEX))
ApplyRidMap(ref streams[tokenMapStream.Value].Content);
functions = new Dictionary();
foreach (var module in modules) {
foreach (var func in module.Functions) {
func.reader = this;
functions.Add(func.Token, func);
}
}
sourcelinkData = TryGetRawFileData("sourcelink");
srcsrvData = TryGetRawFileData("srcsrv");
}
byte[] TryGetRawFileData(string name) {
if (!names.TryGetValue(name, out uint streamId))
return null;
if (streamId > ushort.MaxValue || !IsValidStreamIndex((ushort)streamId))
return null;
return streams[streamId].Content.ToArray();
}
bool IsValidStreamIndex(ushort index) => index != STREAM_INVALID_INDEX && index < streams.Length;
void ReadRootDirectory(MsfStream stream, DataReader[] pages, uint pageSize) {
uint streamNum = stream.Content.ReadUInt32();
var streamSizes = new uint[streamNum];
for (int i = 0; i < streamSizes.Length; i++)
streamSizes[i] = stream.Content.ReadUInt32();
streams = new MsfStream[streamNum];
for (int i = 0; i < streamSizes.Length; i++) {
if (streamSizes[i] == 0xffffffff) {
streams[i] = null;
continue;
}
var pageCount = RoundUpDiv(streamSizes[i], pageSize);
var streamPages = new DataReader[pageCount];
for (int j = 0; j < streamPages.Length; j++)
streamPages[j] = pages[stream.Content.ReadUInt32()];
streams[i] = new MsfStream(streamPages, streamSizes[i]);
}
}
void ReadNames() {
ref var stream = ref streams[STREAM_NAMES].Content;
stream.Position = 8;
Age = stream.ReadUInt32();
Guid = stream.ReadGuid();
uint nameSize = stream.ReadUInt32();
var nameData = stream.Slice(stream.Position, nameSize);
stream.Position += nameSize;
/*uint entryCount = */stream.ReadUInt32();
uint entryCapacity = stream.ReadUInt32();
var entryOk = new BitArray(stream.ReadBytes(stream.ReadInt32() * 4));
if (stream.ReadUInt32() != 0)
throw new NotSupportedException();
names = new Dictionary(StringComparer.OrdinalIgnoreCase);
entryCapacity = Math.Min(entryCapacity, (uint)entryOk.Count);
for (int i = 0; i < entryCapacity; i++) {
if (!entryOk[i])
continue;
var pos = stream.ReadUInt32();
var streamId = stream.ReadUInt32();
nameData.Position = pos;
var streamName = ReadCString(ref nameData);
names[streamName] = streamId;
}
}
void ReadStringTable() {
if (!names.TryGetValue("/names", out uint streamId))
throw new PdbException("String table not found");
ref var stream = ref streams[streamId].Content;
stream.Position = 8;
uint strSize = stream.ReadUInt32();
var strData = stream.Slice(stream.Position, strSize);
stream.Position += strSize;
uint count = stream.ReadUInt32();
strings = new Dictionary((int)count);
for (uint i = 0; i < count; i++) {
var pos = stream.ReadUInt32();
if (pos == 0)
continue;
strData.Position = pos;
strings[pos] = ReadCString(ref strData);
}
}
static uint ReadSizeField(ref DataReader reader) {
int size = reader.ReadInt32();
return size <= 0 ? 0 : (uint)size;
}
ushort? ReadModules() {
ref var stream = ref streams[STREAM_DBI].Content;
modules = new List();
if (stream.Length == 0)
return null;
stream.Position = 20;
ushort symrecStream = stream.ReadUInt16();
stream.Position += 2;
uint gpmodiSize = ReadSizeField(ref stream); // gpmodiSize
uint otherSize = 0;
otherSize += ReadSizeField(ref stream); // secconSize
otherSize += ReadSizeField(ref stream); // secmapSize
otherSize += ReadSizeField(ref stream); // filinfSize
otherSize += ReadSizeField(ref stream); // tsmapSize
stream.ReadUInt32(); // mfcIndex
uint dbghdrSize = ReadSizeField(ref stream);
otherSize += ReadSizeField(ref stream); // ecinfoSize
stream.Position += 8;
var moduleStream = stream.Slice(stream.Position, gpmodiSize);
while (moduleStream.Position < moduleStream.Length) {
var module = new DbiModule();
module.Read(ref moduleStream);
modules.Add(module);
}
if (IsValidStreamIndex(symrecStream))
ReadGlobalSymbols(ref streams[symrecStream].Content);
if (dbghdrSize != 0) {
stream.Position += gpmodiSize;
stream.Position += otherSize;
stream.Position += 12;
return stream.ReadUInt16();
}
return null;
}
internal DbiDocument GetDocument(uint nameId) {
var name = strings[nameId];
if (!documents.TryGetValue(name, out var doc)) {
doc = new DbiDocument(name);
if (names.TryGetValue($"/src/files/{name}", out uint streamId))
doc.Read(ref streams[streamId].Content);
documents.Add(name, doc);
}
return doc;
}
void ReadGlobalSymbols(ref DataReader reader) {
reader.Position = 0;
while (reader.Position < reader.Length) {
var size = reader.ReadUInt16();
var begin = reader.Position;
var end = begin + size;
if ((SymbolType)reader.ReadUInt16() == SymbolType.S_PUB32) {
reader.Position += 4;
var offset = reader.ReadUInt32();
reader.Position += 2;
var name = ReadCString(ref reader);
if (name == "COM+_Entry_Point") {
entryPt = offset;
break;
}
}
reader.Position = end;
}
}
void ApplyRidMap(ref DataReader reader) {
reader.Position = 0;
var map = new uint[reader.Length / 4];
for (int i = 0; i < map.Length; i++)
map[i] = reader.ReadUInt32();
foreach (var module in modules) {
foreach (var func in module.Functions) {
var rid = (uint)func.Token & 0x00ffffff;
rid = map[rid];
func.token = (int)((func.Token & 0xff000000) | rid);
}
}
if (entryPt != 0) {
var rid = entryPt & 0x00ffffff;
rid = map[rid];
entryPt = (entryPt & 0xff000000) | rid;
}
}
internal static string ReadCString(ref DataReader reader) => reader.TryReadZeroTerminatedUtf8String() ?? string.Empty;
public override SymbolMethod GetMethod(MethodDef method, int version) {
if (version != 1)
return null;
if (functions.TryGetValue(method.MDToken.ToInt32(), out var symMethod))
return symMethod;
return null;
}
public override IList Documents {
get {
if (documentsResult is null) {
var docs = new SymbolDocument[documents.Count];
int i = 0;
foreach (var kv in documents)
docs[i++] = kv.Value;
documentsResult = docs;
}
return documentsResult;
}
}
volatile SymbolDocument[] documentsResult;
public override int UserEntryPoint => (int)entryPt;
internal void GetCustomDebugInfos(DbiFunction symMethod, MethodDef method, CilBody body, IList result) {
const string CDI_NAME = "MD2";
var asyncMethod = PseudoCustomDebugInfoFactory.TryCreateAsyncMethod(method.Module, method, body, symMethod.AsyncKickoffMethod, symMethod.AsyncStepInfos, symMethod.AsyncCatchHandlerILOffset);
if (asyncMethod is not null)
result.Add(asyncMethod);
var cdiData = symMethod.Root.GetSymAttribute(CDI_NAME);
if (cdiData is null)
return;
PdbCustomDebugInfoReader.Read(method, body, result, cdiData);
}
public override void GetCustomDebugInfos(int token, GenericParamContext gpContext, IList result) {
if (token == 0x00000001)
GetCustomDebugInfos_ModuleDef(result);
}
void GetCustomDebugInfos_ModuleDef(IList result) {
if (sourcelinkData is not null)
result.Add(new PdbSourceLinkCustomDebugInfo(sourcelinkData));
if (srcsrvData is not null)
result.Add(new PdbSourceServerCustomDebugInfo(srcsrvData));
}
}
}