340 lines
11 KiB
C#
340 lines
11 KiB
C#
// dnlib: See LICENSE.txt for more info
|
|
|
|
// C# & Visual Basic compiler's Custom Debug Info is "documented" in source code only, see Roslyn classes:
|
|
// CustomDebugInfoReader, CustomDebugInfoWriter, CustomDebugInfoEncoder
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Text;
|
|
using dnlib.DotNet.Emit;
|
|
using dnlib.IO;
|
|
|
|
namespace dnlib.DotNet.Pdb.WindowsPdb {
|
|
/// <summary>
|
|
/// Reads custom debug infos produced by the C# and Visual Basic compilers. They're stored in PDB files
|
|
/// as PDB method custom attributes with the name "MD2".
|
|
/// </summary>
|
|
struct PdbCustomDebugInfoReader {
|
|
/// <summary>
|
|
/// Reads custom debug info
|
|
/// </summary>
|
|
/// <param name="method">Method</param>
|
|
/// <param name="body">The method's body. Needs to be provided by the caller since we're called from
|
|
/// PDB-init code when the Body property hasn't been initialized yet</param>
|
|
/// <param name="result">Place all custom debug info in this list</param>
|
|
/// <param name="data">Custom debug info from the PDB file</param>
|
|
public static void Read(MethodDef method, CilBody body, IList<PdbCustomDebugInfo> result, byte[] data) {
|
|
try {
|
|
var reader = ByteArrayDataReaderFactory.CreateReader(data);
|
|
var cdiReader = new PdbCustomDebugInfoReader(method, body, ref reader);
|
|
cdiReader.Read(result);
|
|
}
|
|
catch (ArgumentException) {
|
|
}
|
|
catch (OutOfMemoryException) {
|
|
}
|
|
catch (IOException) {
|
|
}
|
|
}
|
|
|
|
readonly ModuleDef module;
|
|
readonly TypeDef typeOpt;
|
|
readonly CilBody bodyOpt;
|
|
readonly GenericParamContext gpContext;
|
|
DataReader reader;
|
|
|
|
PdbCustomDebugInfoReader(MethodDef method, CilBody body, ref DataReader reader) {
|
|
module = method.Module;
|
|
typeOpt = method.DeclaringType;
|
|
bodyOpt = body;
|
|
gpContext = GenericParamContext.Create(method);
|
|
this.reader = reader;
|
|
}
|
|
|
|
void Read(IList<PdbCustomDebugInfo> result) {
|
|
if (reader.Length < 4)
|
|
return;
|
|
int version = reader.ReadByte();
|
|
Debug.Assert(version == CustomDebugInfoConstants.Version);
|
|
if (version != CustomDebugInfoConstants.Version)
|
|
return;
|
|
int count = reader.ReadByte();
|
|
reader.Position += 2;
|
|
|
|
while (reader.CanRead(8U)) {
|
|
int recVersion = reader.ReadByte();
|
|
Debug.Assert(recVersion == CustomDebugInfoConstants.RecordVersion);
|
|
var recKind = (PdbCustomDebugInfoKind)reader.ReadByte();
|
|
reader.Position++;
|
|
int alignmentSize = reader.ReadByte();
|
|
int recSize = reader.ReadInt32();
|
|
if (recSize < 8 || (ulong)reader.Position - 8 + (uint)recSize > reader.Length)
|
|
return;
|
|
if (recKind <= PdbCustomDebugInfoKind.DynamicLocals)
|
|
alignmentSize = 0;
|
|
if (alignmentSize > 3)
|
|
return;
|
|
var nextRecPos = reader.Position - 8 + (uint)recSize;
|
|
|
|
if (recVersion == CustomDebugInfoConstants.RecordVersion) {
|
|
ulong recPosEnd = (ulong)reader.Position - 8 + (uint)recSize - (uint)alignmentSize;
|
|
var cdi = ReadRecord(recKind, recPosEnd);
|
|
Debug.Assert(cdi is not null);
|
|
Debug.Assert(reader.Position <= recPosEnd);
|
|
if (reader.Position > recPosEnd)
|
|
return;
|
|
if (cdi is not null) {
|
|
Debug.Assert(cdi.Kind == recKind);
|
|
result.Add(cdi);
|
|
}
|
|
}
|
|
|
|
reader.Position = nextRecPos;
|
|
}
|
|
}
|
|
|
|
PdbCustomDebugInfo ReadRecord(PdbCustomDebugInfoKind recKind, ulong recPosEnd) {
|
|
IMethodDefOrRef method;
|
|
byte[] data;
|
|
Local local;
|
|
int count;
|
|
int localIndex;
|
|
switch (recKind) {
|
|
case PdbCustomDebugInfoKind.UsingGroups:
|
|
count = reader.ReadUInt16();
|
|
if (count < 0)
|
|
return null;
|
|
var usingCountRec = new PdbUsingGroupsCustomDebugInfo(count);
|
|
for (int i = 0; i < count; i++)
|
|
usingCountRec.UsingCounts.Add(reader.ReadUInt16());
|
|
return usingCountRec;
|
|
|
|
case PdbCustomDebugInfoKind.ForwardMethodInfo:
|
|
method = module.ResolveToken(reader.ReadUInt32(), gpContext) as IMethodDefOrRef;
|
|
if (method is null)
|
|
return null;
|
|
return new PdbForwardMethodInfoCustomDebugInfo(method);
|
|
|
|
case PdbCustomDebugInfoKind.ForwardModuleInfo:
|
|
method = module.ResolveToken(reader.ReadUInt32(), gpContext) as IMethodDefOrRef;
|
|
if (method is null)
|
|
return null;
|
|
return new PdbForwardModuleInfoCustomDebugInfo(method);
|
|
|
|
case PdbCustomDebugInfoKind.StateMachineHoistedLocalScopes:
|
|
if (bodyOpt is null)
|
|
return null;
|
|
count = reader.ReadInt32();
|
|
if (count < 0)
|
|
return null;
|
|
var smScope = new PdbStateMachineHoistedLocalScopesCustomDebugInfo(count);
|
|
for (int i = 0; i < count; i++) {
|
|
uint startOffset = reader.ReadUInt32();
|
|
uint endOffset = reader.ReadUInt32();
|
|
if (startOffset > endOffset)
|
|
return null;
|
|
// Try to detect synthesized locals, whose start==end==0. The problem is that endOffset
|
|
// read from the PDB is inclusive (add 1 to get 'end'), so a synthesized local and a
|
|
// local at [0, 1) will be encoded the same {0, 0}.
|
|
if (endOffset == 0)
|
|
smScope.Scopes.Add(new StateMachineHoistedLocalScope());
|
|
else {
|
|
var start = GetInstruction(startOffset);
|
|
var end = GetInstruction(endOffset + 1);
|
|
if (start is null)
|
|
return null;
|
|
smScope.Scopes.Add(new StateMachineHoistedLocalScope(start, end));
|
|
}
|
|
}
|
|
return smScope;
|
|
|
|
case PdbCustomDebugInfoKind.StateMachineTypeName:
|
|
var name = ReadUnicodeZ(recPosEnd, needZeroChar: true);
|
|
if (name is null)
|
|
return null;
|
|
var type = GetNestedType(name);
|
|
if (type is null)
|
|
return null;
|
|
return new PdbStateMachineTypeNameCustomDebugInfo(type);
|
|
|
|
case PdbCustomDebugInfoKind.DynamicLocals:
|
|
if (bodyOpt is null)
|
|
return null;
|
|
count = reader.ReadInt32();
|
|
const int dynLocalRecSize = 64 + 4 + 4 + 2 * 64;
|
|
if (reader.Position + (ulong)(uint)count * dynLocalRecSize > recPosEnd)
|
|
return null;
|
|
var dynLocListRec = new PdbDynamicLocalsCustomDebugInfo(count);
|
|
for (int i = 0; i < count; i++) {
|
|
reader.Position += 64;
|
|
int flagsCount = reader.ReadInt32();
|
|
if ((uint)flagsCount > 64)
|
|
return null;
|
|
var dynLocRec = new PdbDynamicLocal(flagsCount);
|
|
var afterPos = reader.Position;
|
|
|
|
reader.Position -= 64 + 4;
|
|
for (int j = 0; j < flagsCount; j++)
|
|
dynLocRec.Flags.Add(reader.ReadByte());
|
|
reader.Position = afterPos;
|
|
|
|
localIndex = reader.ReadInt32();
|
|
// 'const' locals have index -1 but they're encoded as 0 by Roslyn
|
|
if (localIndex != 0 && (uint)localIndex >= (uint)bodyOpt.Variables.Count)
|
|
return null;
|
|
|
|
var nameEndPos = reader.Position + 2 * 64;
|
|
name = ReadUnicodeZ(nameEndPos, needZeroChar: false);
|
|
reader.Position = nameEndPos;
|
|
|
|
local = localIndex < bodyOpt.Variables.Count ? bodyOpt.Variables[localIndex] : null;
|
|
// Roslyn writes 0 to localIndex if it's a 'const' local, try to undo that now
|
|
if (localIndex == 0 && local is not null && local.Name != name)
|
|
local = null;
|
|
if (local is not null && local.Name == name)
|
|
name = null;
|
|
dynLocRec.Name = name;
|
|
dynLocRec.Local = local;
|
|
dynLocListRec.Locals.Add(dynLocRec);
|
|
}
|
|
return dynLocListRec;
|
|
|
|
case PdbCustomDebugInfoKind.EditAndContinueLocalSlotMap:
|
|
data = reader.ReadBytes((int)(recPosEnd - reader.Position));
|
|
return new PdbEditAndContinueLocalSlotMapCustomDebugInfo(data);
|
|
|
|
case PdbCustomDebugInfoKind.EditAndContinueLambdaMap:
|
|
data = reader.ReadBytes((int)(recPosEnd - reader.Position));
|
|
return new PdbEditAndContinueLambdaMapCustomDebugInfo(data);
|
|
|
|
case PdbCustomDebugInfoKind.TupleElementNames:
|
|
if (bodyOpt is null)
|
|
return null;
|
|
count = reader.ReadInt32();
|
|
if (count < 0)
|
|
return null;
|
|
var tupleListRec = new PdbTupleElementNamesCustomDebugInfo(count);
|
|
for (int i = 0; i < count; i++) {
|
|
int nameCount = reader.ReadInt32();
|
|
if ((uint)nameCount >= 10000)
|
|
return null;
|
|
var tupleInfo = new PdbTupleElementNames(nameCount);
|
|
|
|
for (int j = 0; j < nameCount; j++) {
|
|
var s = ReadUTF8Z(recPosEnd);
|
|
if (s is null)
|
|
return null;
|
|
tupleInfo.TupleElementNames.Add(s);
|
|
}
|
|
|
|
localIndex = reader.ReadInt32();
|
|
uint scopeStart = reader.ReadUInt32();
|
|
uint scopeEnd = reader.ReadUInt32();
|
|
name = ReadUTF8Z(recPosEnd);
|
|
if (name is null)
|
|
return null;
|
|
Debug.Assert(localIndex >= -1);
|
|
// -1 = 'const' local. Only 'const' locals have a scope
|
|
Debug.Assert((localIndex == -1) ^ (scopeStart == 0 && scopeEnd == 0));
|
|
|
|
if (localIndex == -1) {
|
|
local = null;
|
|
tupleInfo.ScopeStart = GetInstruction(scopeStart);
|
|
tupleInfo.ScopeEnd = GetInstruction(scopeEnd);
|
|
if (tupleInfo.ScopeStart is null)
|
|
return null;
|
|
}
|
|
else {
|
|
if ((uint)localIndex >= (uint)bodyOpt.Variables.Count)
|
|
return null;
|
|
local = bodyOpt.Variables[localIndex];
|
|
}
|
|
|
|
if (local is not null && local.Name == name)
|
|
name = null;
|
|
tupleInfo.Local = local;
|
|
tupleInfo.Name = name;
|
|
|
|
tupleListRec.Names.Add(tupleInfo);
|
|
}
|
|
return tupleListRec;
|
|
|
|
default:
|
|
Debug.Fail($"Unknown custom debug info kind: 0x{(int)recKind:X}");
|
|
data = reader.ReadBytes((int)(recPosEnd - reader.Position));
|
|
return new PdbUnknownCustomDebugInfo(recKind, data);
|
|
}
|
|
}
|
|
|
|
TypeDef GetNestedType(string name) {
|
|
if (typeOpt is null)
|
|
return null;
|
|
var nestedTypes = typeOpt.NestedTypes;
|
|
int count = nestedTypes.Count;
|
|
for (int i = 0; i < count; i++) {
|
|
var type = nestedTypes[i];
|
|
if (UTF8String.IsNullOrEmpty(type.Namespace)) {
|
|
if (type.Name == name)
|
|
return type;
|
|
var typeName = type.Name.String;
|
|
if (typeName.StartsWith(name) && typeName.Length >= name.Length + 2) {
|
|
int index = name.Length;
|
|
if (typeName[index] == '`') {
|
|
Debug.Assert(index + 1 < typeName.Length);
|
|
bool ok = true;
|
|
index++;
|
|
while (index < typeName.Length) {
|
|
if (!char.IsDigit(typeName[index])) {
|
|
ok = false;
|
|
break;
|
|
}
|
|
index++;
|
|
}
|
|
if (ok)
|
|
return type;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
string ReadUnicodeZ(ulong recPosEnd, bool needZeroChar) {
|
|
var sb = new StringBuilder();
|
|
|
|
for (;;) {
|
|
if (reader.Position >= recPosEnd)
|
|
return needZeroChar ? null : sb.ToString();
|
|
var c = reader.ReadChar();
|
|
if (c == 0)
|
|
return sb.ToString();
|
|
sb.Append(c);
|
|
}
|
|
}
|
|
|
|
string ReadUTF8Z(ulong recPosEnd) {
|
|
if (reader.Position > recPosEnd)
|
|
return null;
|
|
return reader.TryReadZeroTerminatedUtf8String();
|
|
}
|
|
|
|
Instruction GetInstruction(uint offset) {
|
|
var instructions = bodyOpt.Instructions;
|
|
int lo = 0, hi = instructions.Count - 1;
|
|
while (lo <= hi && hi != -1) {
|
|
int i = (lo + hi) / 2;
|
|
var instr = instructions[i];
|
|
if (instr.Offset == offset)
|
|
return instr;
|
|
if (offset < instr.Offset)
|
|
hi = i - 1;
|
|
else
|
|
lo = i + 1;
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
}
|