obfuz/Plugins/dnlib/DotNet/Pdb/Portable/PortablePdbReader.cs

397 lines
15 KiB
C#
Raw Normal View History

// dnlib: See LICENSE.txt for more info
// https://github.com/dotnet/corefx/blob/master/src/System.Reflection.Metadata/specs/PortablePdb-Metadata.md
using System;
using System.Collections.Generic;
using System.Diagnostics;
using dnlib.DotNet.Emit;
using dnlib.DotNet.MD;
using dnlib.DotNet.Pdb.Symbols;
using dnlib.IO;
namespace dnlib.DotNet.Pdb.Portable {
sealed class PortablePdbReader : SymbolReader {
readonly PdbFileKind pdbFileKind;
ModuleDef module;
readonly Metadata pdbMetadata;
SymbolDocument[] documents;
public override PdbFileKind PdbFileKind => pdbFileKind;
public override int UserEntryPoint => pdbMetadata.PdbStream.EntryPoint.ToInt32();
public override IList<SymbolDocument> Documents => documents;
public PortablePdbReader(DataReaderFactory pdbStream, PdbFileKind pdbFileKind) {
this.pdbFileKind = pdbFileKind;
pdbMetadata = MetadataFactory.CreateStandalonePortablePDB(pdbStream, true);
}
internal bool MatchesModule(Guid pdbGuid, uint timestamp, uint age) {
if (pdbMetadata.PdbStream is PdbStream pdbStream) {
var pdbGuidArray = pdbStream.Id;
Array.Resize(ref pdbGuidArray, 16);
if (new Guid(pdbGuidArray) != pdbGuid)
return false;
if (BitConverter.ToUInt32(pdbStream.Id, 16) != timestamp)
return false;
if (age != 1)
return false;
return true;
}
return false;
}
public override void Initialize(ModuleDef module) {
this.module = module;
documents = ReadDocuments();
}
static Guid GetLanguageVendor(Guid language) {
if (language == PdbDocumentConstants.LanguageCSharp || language == PdbDocumentConstants.LanguageVisualBasic || language == PdbDocumentConstants.LanguageFSharp)
return PdbDocumentConstants.LanguageVendorMicrosoft;
return Guid.Empty;
}
SymbolDocument[] ReadDocuments() {
Debug.Assert(module is not null);
var docTbl = pdbMetadata.TablesStream.DocumentTable;
var docs = new SymbolDocument[docTbl.Rows];
var nameReader = new DocumentNameReader(pdbMetadata.BlobStream);
var custInfos = ListCache<PdbCustomDebugInfo>.AllocList();
var gpContext = new GenericParamContext();
for (int i = 0; i < docs.Length; i++) {
uint rid = (uint)i + 1;
bool b = pdbMetadata.TablesStream.TryReadDocumentRow(rid, out var row);
Debug.Assert(b);
var url = nameReader.ReadDocumentName(row.Name);
var language = pdbMetadata.GuidStream.Read(row.Language) ?? Guid.Empty;
var languageVendor = GetLanguageVendor(language);
var documentType = PdbDocumentConstants.DocumentTypeText;
var checkSumAlgorithmId = pdbMetadata.GuidStream.Read(row.HashAlgorithm) ?? Guid.Empty;
var checkSum = pdbMetadata.BlobStream.ReadNoNull(row.Hash);
var mdToken = new MDToken(Table.Document, rid);
var token = mdToken.ToInt32();
custInfos.Clear();
GetCustomDebugInfos(token, gpContext, custInfos);
var custInfosArray = custInfos.Count == 0 ? Array2.Empty<PdbCustomDebugInfo>() : custInfos.ToArray();
docs[i] = new SymbolDocumentImpl(url, language, languageVendor, documentType, checkSumAlgorithmId, checkSum, custInfosArray, mdToken);
}
ListCache<PdbCustomDebugInfo>.Free(ref custInfos);
return docs;
}
bool TryGetSymbolDocument(uint rid, out SymbolDocument document) {
int index = (int)rid - 1;
if ((uint)index >= (uint)documents.Length) {
Debug.Fail($"Couldn't find document with rid 0x{rid:X6}");
document = null;
return false;
}
document = documents[index];
return true;
}
public override SymbolMethod GetMethod(MethodDef method, int version) {
if (version != 1)
return null;
var mdTable = pdbMetadata.TablesStream.MethodDebugInformationTable;
uint methodRid = method.Rid;
if (!mdTable.IsValidRID(methodRid))
return null;
var sequencePoints = ReadSequencePoints(methodRid) ?? Array2.Empty<SymbolSequencePoint>();
var gpContext = GenericParamContext.Create(method);
var rootScope = ReadScope(methodRid, gpContext);
var kickoffMethod = GetKickoffMethod(methodRid);
var symbolMethod = new SymbolMethodImpl(this, method.MDToken.ToInt32(), rootScope, sequencePoints, kickoffMethod);
rootScope.method = symbolMethod;
return symbolMethod;
}
int GetKickoffMethod(uint methodRid) {
uint rid = pdbMetadata.GetStateMachineMethodRid(methodRid);
if (rid == 0)
return 0;
if (!pdbMetadata.TablesStream.TryReadStateMachineMethodRow(rid, out var row))
return 0;
return 0x06000000 + (int)row.KickoffMethod;
}
SymbolSequencePoint[] ReadSequencePoints(uint methodRid) {
if (!pdbMetadata.TablesStream.MethodDebugInformationTable.IsValidRID(methodRid))
return null;
if (!pdbMetadata.TablesStream.TryReadMethodDebugInformationRow(methodRid, out var row))
return null;
if (row.SequencePoints == 0)
return null;
uint documentRid = row.Document;
if (!pdbMetadata.BlobStream.TryCreateReader(row.SequencePoints, out var seqPointsReader))
return null;
var seqPointsBuilder = ListCache<SymbolSequencePoint>.AllocList();
uint localSig = seqPointsReader.ReadCompressedUInt32();
if (documentRid == 0)
documentRid = seqPointsReader.ReadCompressedUInt32();
TryGetSymbolDocument(documentRid, out var document);
uint ilOffset = uint.MaxValue;
int line = -1, column = 0;
bool canReadDocumentRecord = false;
while (seqPointsReader.Position < seqPointsReader.Length) {
uint data = seqPointsReader.ReadCompressedUInt32();
if (data == 0 && canReadDocumentRecord) {
// document-record
documentRid = seqPointsReader.ReadCompressedUInt32();
TryGetSymbolDocument(documentRid, out document);
}
else {
// SequencePointRecord
Debug.Assert(document is not null);
if (document is null)
return null;
var symSeqPoint = new SymbolSequencePoint {
Document = document,
};
if (ilOffset == uint.MaxValue)
ilOffset = data;
else {
Debug.Assert(data != 0);
if (data == 0)
return null;
ilOffset += data;
}
symSeqPoint.Offset = (int)ilOffset;
uint dlines = seqPointsReader.ReadCompressedUInt32();
int dcolumns = dlines == 0 ? (int)seqPointsReader.ReadCompressedUInt32() : seqPointsReader.ReadCompressedInt32();
if (dlines == 0 && dcolumns == 0) {
// hidden-sequence-point-record
symSeqPoint.Line = SequencePointConstants.HIDDEN_LINE;
symSeqPoint.EndLine = SequencePointConstants.HIDDEN_LINE;
symSeqPoint.Column = SequencePointConstants.HIDDEN_COLUMN;
symSeqPoint.EndColumn = SequencePointConstants.HIDDEN_COLUMN;
}
else {
// sequence-point-record
if (line < 0) {
line = (int)seqPointsReader.ReadCompressedUInt32();
column = (int)seqPointsReader.ReadCompressedUInt32();
}
else {
line += seqPointsReader.ReadCompressedInt32();
column += seqPointsReader.ReadCompressedInt32();
}
symSeqPoint.Line = line;
symSeqPoint.EndLine = line + (int)dlines;
symSeqPoint.Column = column;
symSeqPoint.EndColumn = column + dcolumns;
}
seqPointsBuilder.Add(symSeqPoint);
}
canReadDocumentRecord = true;
}
Debug.Assert(seqPointsReader.Position == seqPointsReader.Length);
return ListCache<SymbolSequencePoint>.FreeAndToArray(ref seqPointsBuilder);
}
SymbolScopeImpl ReadScope(uint methodRid, GenericParamContext gpContext) {
var scopesRidList = pdbMetadata.GetLocalScopeRidList(methodRid);
SymbolScopeImpl rootScopeOrNull = null;
if (scopesRidList.Count != 0) {
var custInfos = ListCache<PdbCustomDebugInfo>.AllocList();
var stack = ListCache<SymbolScopeImpl>.AllocList();
var importScopeBlobReader = new ImportScopeBlobReader(module, pdbMetadata.BlobStream);
for (int i = 0; i < scopesRidList.Count; i++) {
var rid = scopesRidList[i];
int token = new MDToken(Table.LocalScope, rid).ToInt32();
bool b = pdbMetadata.TablesStream.TryReadLocalScopeRow(rid, out var row);
Debug.Assert(b);
uint startOffset = row.StartOffset;
uint endOffset = startOffset + row.Length;
SymbolScopeImpl parent = null;
while (stack.Count > 0) {
var nextParent = stack[stack.Count - 1];
if (startOffset >= nextParent.StartOffset && endOffset <= nextParent.EndOffset) {
parent = nextParent;
break;
}
stack.RemoveAt(stack.Count - 1);
}
Debug.Assert(parent is not null || rootScopeOrNull is null);
custInfos.Clear();
GetCustomDebugInfos(token, gpContext, custInfos);
var customDebugInfos = custInfos.Count == 0 ? Array2.Empty<PdbCustomDebugInfo>() : custInfos.ToArray();
var scope = new SymbolScopeImpl(this, parent, (int)startOffset, (int)endOffset, customDebugInfos);
if (rootScopeOrNull is null)
rootScopeOrNull = scope;
stack.Add(scope);
if (parent is not null)
parent.childrenList.Add(scope);
scope.importScope = ReadPdbImportScope(ref importScopeBlobReader, row.ImportScope, gpContext);
ReadVariables(scope, gpContext, pdbMetadata.GetLocalVariableRidList(rid));
ReadConstants(scope, pdbMetadata.GetLocalConstantRidList(rid));
}
ListCache<SymbolScopeImpl>.Free(ref stack);
ListCache<PdbCustomDebugInfo>.Free(ref custInfos);
}
return rootScopeOrNull ?? new SymbolScopeImpl(this, null, 0, int.MaxValue, Array2.Empty<PdbCustomDebugInfo>());
}
PdbImportScope ReadPdbImportScope(ref ImportScopeBlobReader importScopeBlobReader, uint importScope, GenericParamContext gpContext) {
if (importScope == 0)
return null;
const int MAX = 1000;
PdbImportScope result = null;
PdbImportScope prevScope = null;
for (int i = 0; importScope != 0; i++) {
Debug.Assert(i < MAX);
if (i >= MAX)
return null;
int token = new MDToken(Table.ImportScope, importScope).ToInt32();
if (!pdbMetadata.TablesStream.TryReadImportScopeRow(importScope, out var row))
return null;
var scope = new PdbImportScope();
GetCustomDebugInfos(token, gpContext, scope.CustomDebugInfos);
if (result is null)
result = scope;
if (prevScope is not null)
prevScope.Parent = scope;
importScopeBlobReader.Read(row.Imports, scope.Imports);
prevScope = scope;
importScope = row.Parent;
}
return result;
}
void ReadVariables(SymbolScopeImpl scope, GenericParamContext gpContext, RidList rids) {
if (rids.Count == 0)
return;
var table = pdbMetadata.TablesStream.LocalVariableTable;
var custInfos = ListCache<PdbCustomDebugInfo>.AllocList();
for (int i = 0; i < rids.Count; i++) {
var rid = rids[i];
int token = new MDToken(Table.LocalVariable, rid).ToInt32();
custInfos.Clear();
GetCustomDebugInfos(token, gpContext, custInfos);
var customDebugInfos = custInfos.Count == 0 ? Array2.Empty<PdbCustomDebugInfo>() : custInfos.ToArray();
bool b = pdbMetadata.TablesStream.TryReadLocalVariableRow(rid, out var row);
Debug.Assert(b);
var name = pdbMetadata.StringsStream.Read(row.Name);
scope.localsList.Add(new SymbolVariableImpl(name, ToSymbolVariableAttributes(row.Attributes), row.Index, customDebugInfos));
}
ListCache<PdbCustomDebugInfo>.Free(ref custInfos);
}
static PdbLocalAttributes ToSymbolVariableAttributes(ushort attributes) {
var res = PdbLocalAttributes.None;
const ushort DebuggerHidden = 0x0001;
if ((attributes & DebuggerHidden) != 0)
res |= PdbLocalAttributes.DebuggerHidden;
return res;
}
void ReadConstants(SymbolScopeImpl scope, RidList rids) {
if (rids.Count == 0)
return;
scope.SetConstants(pdbMetadata, rids);
}
internal void GetCustomDebugInfos(SymbolMethodImpl symMethod, MethodDef method, CilBody body, IList<PdbCustomDebugInfo> result) {
Debug.Assert(method.Module == module);
GetCustomDebugInfos(method.MDToken.ToInt32(), GenericParamContext.Create(method), result, method, body, out var asyncStepInfo);
if (asyncStepInfo is not null) {
var asyncMethod = TryCreateAsyncMethod(module, symMethod.KickoffMethod, asyncStepInfo.AsyncStepInfos, asyncStepInfo.CatchHandler);
Debug.Assert(asyncMethod is not null);
if (asyncMethod is not null)
result.Add(asyncMethod);
}
else if (symMethod.KickoffMethod != 0) {
var iteratorMethod = TryCreateIteratorMethod(module, symMethod.KickoffMethod);
Debug.Assert(iteratorMethod is not null);
if (iteratorMethod is not null)
result.Add(iteratorMethod);
}
}
PdbAsyncMethodCustomDebugInfo TryCreateAsyncMethod(ModuleDef module, int asyncKickoffMethod, IList<PdbAsyncStepInfo> asyncStepInfos, Instruction asyncCatchHandler) {
var kickoffToken = new MDToken(asyncKickoffMethod);
if (kickoffToken.Table != Table.Method)
return null;
var asyncMethod = new PdbAsyncMethodCustomDebugInfo(asyncStepInfos.Count);
asyncMethod.KickoffMethod = module.ResolveToken(kickoffToken) as MethodDef;
asyncMethod.CatchHandlerInstruction = asyncCatchHandler;
int count = asyncStepInfos.Count;
for (int i = 0; i < count; i++)
asyncMethod.StepInfos.Add(asyncStepInfos[i]);
return asyncMethod;
}
PdbIteratorMethodCustomDebugInfo TryCreateIteratorMethod(ModuleDef module, int iteratorKickoffMethod) {
var kickoffToken = new MDToken(iteratorKickoffMethod);
if (kickoffToken.Table != Table.Method)
return null;
var kickoffMethod = module.ResolveToken(kickoffToken) as MethodDef;
return new PdbIteratorMethodCustomDebugInfo(kickoffMethod);
}
public override void GetCustomDebugInfos(int token, GenericParamContext gpContext, IList<PdbCustomDebugInfo> result) {
GetCustomDebugInfos(token, gpContext, result, null, null, out var asyncStepInfo);
Debug.Assert(asyncStepInfo is null);
}
void GetCustomDebugInfos(int token, GenericParamContext gpContext, IList<PdbCustomDebugInfo> result, MethodDef methodOpt, CilBody bodyOpt, out PdbAsyncMethodSteppingInformationCustomDebugInfo asyncStepInfo) {
asyncStepInfo = null;
var mdToken = new MDToken(token);
var ridList = pdbMetadata.GetCustomDebugInformationRidList(mdToken.Table, mdToken.Rid);
if (ridList.Count == 0)
return;
var typeOpt = methodOpt?.DeclaringType;
for (int i = 0; i < ridList.Count; i++) {
var rid = ridList[i];
if (!pdbMetadata.TablesStream.TryReadCustomDebugInformationRow(rid, out var row))
continue;
var guid = pdbMetadata.GuidStream.Read(row.Kind);
if (!pdbMetadata.BlobStream.TryCreateReader(row.Value, out var reader))
continue;
Debug.Assert(guid is not null);
if (guid is null)
continue;
var cdi = PortablePdbCustomDebugInfoReader.Read(module, typeOpt, bodyOpt, gpContext, guid.Value, ref reader);
Debug.Assert(cdi is not null);
if (cdi is not null) {
if (cdi is PdbAsyncMethodSteppingInformationCustomDebugInfo asyncStepInfoTmp) {
Debug.Assert(asyncStepInfo is null);
asyncStepInfo = asyncStepInfoTmp;
}
else
result.Add(cdi);
}
}
}
public override void Dispose() => pdbMetadata.Dispose();
}
}