obfuz/Plugins/dnlib/DotNet/Pdb/WindowsPdb/WindowsPdbWriter.cs

445 lines
15 KiB
C#
Raw Normal View History

2025-04-08 20:31:44 +08:00
// dnlib: See LICENSE.txt for more info
using System;
using System.Collections.Generic;
using System.Diagnostics.SymbolStore;
using dnlib.DotNet.Emit;
using dnlib.DotNet.Writer;
namespace dnlib.DotNet.Pdb.WindowsPdb {
sealed class WindowsPdbWriter : IDisposable {
SymbolWriter writer;
readonly PdbState pdbState;
readonly ModuleDef module;
readonly Metadata metadata;
readonly Dictionary<PdbDocument, ISymbolDocumentWriter> pdbDocs = new Dictionary<PdbDocument, ISymbolDocumentWriter>();
readonly SequencePointHelper seqPointsHelper = new SequencePointHelper();
readonly Dictionary<Instruction, uint> instrToOffset;
readonly PdbCustomDebugInfoWriterContext customDebugInfoWriterContext;
readonly int localsEndScopeIncValue;
public ILogger Logger { get; set; }
public WindowsPdbWriter(SymbolWriter writer, PdbState pdbState, Metadata metadata)
: this(pdbState, metadata) {
if (pdbState is null)
throw new ArgumentNullException(nameof(pdbState));
if (metadata is null)
throw new ArgumentNullException(nameof(metadata));
this.writer = writer ?? throw new ArgumentNullException(nameof(writer));
writer.Initialize(metadata);
}
WindowsPdbWriter(PdbState pdbState, Metadata metadata) {
this.pdbState = pdbState;
this.metadata = metadata;
module = metadata.Module;
instrToOffset = new Dictionary<Instruction, uint>();
customDebugInfoWriterContext = new PdbCustomDebugInfoWriterContext();
localsEndScopeIncValue = PdbUtils.IsEndInclusive(PdbFileKind.WindowsPDB, pdbState.Compiler) ? 1 : 0;
}
ISymbolDocumentWriter Add(PdbDocument pdbDoc) {
if (pdbDocs.TryGetValue(pdbDoc, out var docWriter))
return docWriter;
docWriter = writer.DefineDocument(pdbDoc.Url, pdbDoc.Language, pdbDoc.LanguageVendor, pdbDoc.DocumentType);
docWriter.SetCheckSum(pdbDoc.CheckSumAlgorithmId, pdbDoc.CheckSum);
if (TryGetCustomDebugInfo(pdbDoc, out PdbEmbeddedSourceCustomDebugInfo sourceCdi))
docWriter.SetSource(sourceCdi.SourceCodeBlob);
pdbDocs.Add(pdbDoc, docWriter);
return docWriter;
}
static bool TryGetCustomDebugInfo<TCDI>(IHasCustomDebugInformation hci, out TCDI cdi) where TCDI : PdbCustomDebugInfo {
var cdis = hci.CustomDebugInfos;
int count = cdis.Count;
for (int i = 0; i < count; i++) {
if (cdis[i] is TCDI cdi2) {
cdi = cdi2;
return true;
}
}
cdi = null;
return false;
}
public void Write() {
writer.SetUserEntryPoint(GetUserEntryPointToken());
var cdiBuilder = new List<PdbCustomDebugInfo>();
foreach (var type in module.GetTypes()) {
if (type is null)
continue;
var typeMethods = type.Methods;
int count = typeMethods.Count;
for (int i = 0; i < count; i++) {
var method = typeMethods[i];
if (method is null)
continue;
if (!ShouldAddMethod(method))
continue;
Write(method, cdiBuilder);
}
}
if (TryGetCustomDebugInfo(module, out PdbSourceLinkCustomDebugInfo sourceLinkCdi))
writer.SetSourceLinkData(sourceLinkCdi.FileBlob);
if (TryGetCustomDebugInfo(module, out PdbSourceServerCustomDebugInfo sourceServerCdi))
writer.SetSourceServerData(sourceServerCdi.FileBlob);
}
bool ShouldAddMethod(MethodDef method) {
var body = method.Body;
if (body is null)
return false;
if (body.HasPdbMethod)
return true;
var bodyVariables = body.Variables;
int count = bodyVariables.Count;
for (int i = 0; i < count; i++) {
var local = bodyVariables[i];
// Don't check whether it's the empty string. Only check for null.
if (local.Name is not null)
return true;
if (local.Attributes != 0)
return true;
}
var bodyInstructions = body.Instructions;
count = bodyInstructions.Count;
for (int i = 0; i < count; i++) {
if (bodyInstructions[i].SequencePoint is not null)
return true;
}
return false;
}
sealed class SequencePointHelper {
readonly Dictionary<PdbDocument, bool> checkedPdbDocs = new Dictionary<PdbDocument, bool>();
int[] instrOffsets = Array2.Empty<int>();
int[] startLines;
int[] startColumns;
int[] endLines;
int[] endColumns;
public void Write(WindowsPdbWriter pdbWriter, IList<Instruction> instrs) {
checkedPdbDocs.Clear();
while (true) {
PdbDocument currPdbDoc = null;
bool otherDocsAvailable = false;
int index = 0, instrOffset = 0;
Instruction instr = null;
for (int i = 0; i < instrs.Count; i++, instrOffset += instr.GetSize()) {
instr = instrs[i];
var seqp = instr.SequencePoint;
if (seqp is null || seqp.Document is null)
continue;
if (checkedPdbDocs.ContainsKey(seqp.Document))
continue;
if (currPdbDoc is null)
currPdbDoc = seqp.Document;
else if (currPdbDoc != seqp.Document) {
otherDocsAvailable = true;
continue;
}
if (index >= instrOffsets.Length) {
int newSize = index * 2;
if (newSize < 64)
newSize = 64;
Array.Resize(ref instrOffsets, newSize);
Array.Resize(ref startLines, newSize);
Array.Resize(ref startColumns, newSize);
Array.Resize(ref endLines, newSize);
Array.Resize(ref endColumns, newSize);
}
instrOffsets[index] = instrOffset;
startLines[index] = seqp.StartLine;
startColumns[index] = seqp.StartColumn;
endLines[index] = seqp.EndLine;
endColumns[index] = seqp.EndColumn;
index++;
}
if (index != 0)
pdbWriter.writer.DefineSequencePoints(pdbWriter.Add(currPdbDoc), (uint)index, instrOffsets, startLines, startColumns, endLines, endColumns);
if (!otherDocsAvailable)
break;
if (currPdbDoc is not null)
checkedPdbDocs.Add(currPdbDoc, true);
}
}
}
struct CurrentMethod {
readonly WindowsPdbWriter pdbWriter;
public readonly MethodDef Method;
readonly Dictionary<Instruction, uint> toOffset;
public readonly uint BodySize;
public CurrentMethod(WindowsPdbWriter pdbWriter, MethodDef method, Dictionary<Instruction, uint> toOffset) {
this.pdbWriter = pdbWriter;
Method = method;
this.toOffset = toOffset;
toOffset.Clear();
uint offset = 0;
var instructions = method.Body.Instructions;
int count = instructions.Count;
for (int i = 0; i < count; i++) {
var instr = instructions[i];
toOffset[instr] = offset;
offset += (uint)instr.GetSize();
}
BodySize = offset;
}
public readonly int GetOffset(Instruction instr) {
if (instr is null)
return (int)BodySize;
if (toOffset.TryGetValue(instr, out uint offset))
return (int)offset;
pdbWriter.Error("Instruction was removed from the body but is referenced from PdbScope: {0}", instr);
return (int)BodySize;
}
}
void Write(MethodDef method, List<PdbCustomDebugInfo> cdiBuilder) {
uint rid = metadata.GetRid(method);
if (rid == 0) {
Error("Method {0} ({1:X8}) is not defined in this module ({2})", method, method.MDToken.Raw, module);
return;
}
var info = new CurrentMethod(this, method, instrToOffset);
var body = method.Body;
var symbolToken = new MDToken(MD.Table.Method, rid);
writer.OpenMethod(symbolToken);
seqPointsHelper.Write(this, info.Method.Body.Instructions);
var pdbMethod = body.PdbMethod;
if (pdbMethod is null)
body.PdbMethod = pdbMethod = new PdbMethod();
var scope = pdbMethod.Scope;
if (scope is null)
pdbMethod.Scope = scope = new PdbScope();
if (scope.Namespaces.Count == 0 && scope.Variables.Count == 0 && scope.Constants.Count == 0) {
if (scope.Scopes.Count == 0) {
// We must open at least one sub scope (the sym writer creates the 'method' scope
// which covers the whole method) or the native PDB reader will fail to read all
// sequence points.
writer.OpenScope(0);
writer.CloseScope((int)info.BodySize);
}
else {
var scopes = scope.Scopes;
int count = scopes.Count;
for (int i = 0; i < count; i++)
WriteScope(ref info, scopes[i], 0);
}
}
else {
// C++/.NET (some methods)
WriteScope(ref info, scope, 0);
}
GetPseudoCustomDebugInfos(method.CustomDebugInfos, cdiBuilder, out var asyncMethod);
if (cdiBuilder.Count != 0) {
customDebugInfoWriterContext.Logger = GetLogger();
var cdiData = PdbCustomDebugInfoWriter.Write(metadata, method, customDebugInfoWriterContext, cdiBuilder);
if (cdiData is not null)
writer.SetSymAttribute(symbolToken, "MD2", cdiData);
}
if (asyncMethod is not null) {
if (!writer.SupportsAsyncMethods)
Error("PDB symbol writer doesn't support writing async methods");
else
WriteAsyncMethod(ref info, asyncMethod);
}
writer.CloseMethod();
}
void GetPseudoCustomDebugInfos(IList<PdbCustomDebugInfo> customDebugInfos, List<PdbCustomDebugInfo> cdiBuilder, out PdbAsyncMethodCustomDebugInfo asyncMethod) {
cdiBuilder.Clear();
asyncMethod = null;
int count = customDebugInfos.Count;
for (int i = 0; i < count; i++) {
var cdi = customDebugInfos[i];
switch (cdi.Kind) {
case PdbCustomDebugInfoKind.AsyncMethod:
if (asyncMethod is not null)
Error("Duplicate async method custom debug info");
else
asyncMethod = (PdbAsyncMethodCustomDebugInfo)cdi;
break;
default:
if ((uint)cdi.Kind > byte.MaxValue)
Error("Custom debug info {0} isn't supported by Windows PDB files", cdi.Kind);
else
cdiBuilder.Add(cdi);
break;
}
}
}
uint GetMethodToken(MethodDef method) {
uint rid = metadata.GetRid(method);
if (rid == 0)
Error("Method {0} ({1:X8}) is not defined in this module ({2})", method, method.MDToken.Raw, module);
return new MDToken(MD.Table.Method, rid).Raw;
}
void WriteAsyncMethod(ref CurrentMethod info, PdbAsyncMethodCustomDebugInfo asyncMethod) {
if (asyncMethod.KickoffMethod is null) {
Error("KickoffMethod is null");
return;
}
uint kickoffMethod = GetMethodToken(asyncMethod.KickoffMethod);
writer.DefineKickoffMethod(kickoffMethod);
if (asyncMethod.CatchHandlerInstruction is not null) {
int catchHandlerILOffset = info.GetOffset(asyncMethod.CatchHandlerInstruction);
writer.DefineCatchHandlerILOffset((uint)catchHandlerILOffset);
}
var stepInfos = asyncMethod.StepInfos;
var yieldOffsets = new uint[stepInfos.Count];
var breakpointOffset = new uint[stepInfos.Count];
var breakpointMethods = new uint[stepInfos.Count];
for (int i = 0; i < yieldOffsets.Length; i++) {
var stepInfo = stepInfos[i];
if (stepInfo.YieldInstruction is null) {
Error("YieldInstruction is null");
return;
}
if (stepInfo.BreakpointMethod is null) {
Error("BreakpointMethod is null");
return;
}
if (stepInfo.BreakpointInstruction is null) {
Error("BreakpointInstruction is null");
return;
}
yieldOffsets[i] = (uint)info.GetOffset(stepInfo.YieldInstruction);
breakpointOffset[i] = (uint)GetExternalInstructionOffset(ref info, stepInfo.BreakpointMethod, stepInfo.BreakpointInstruction);
breakpointMethods[i] = GetMethodToken(stepInfo.BreakpointMethod);
}
writer.DefineAsyncStepInfo(yieldOffsets, breakpointOffset, breakpointMethods);
}
int GetExternalInstructionOffset(ref CurrentMethod info, MethodDef method, Instruction instr) {
if (info.Method == method)
return info.GetOffset(instr);
var body = method.Body;
if (body is null) {
Error("Method body is null");
return 0;
}
var instrs = body.Instructions;
int offset = 0;
for (int i = 0; i < instrs.Count; i++) {
var currInstr = instrs[i];
if (currInstr == instr)
return offset;
offset += currInstr.GetSize();
}
if (instr is null)
return offset;
Error("Async method instruction has been removed but it's still being referenced by PDB info: BP Instruction: {0}, BP Method: {1} (0x{2:X8}), Current Method: {3} (0x{4:X8})", instr, method, method.MDToken.Raw, info.Method, info.Method.MDToken.Raw);
return 0;
}
void WriteScope(ref CurrentMethod info, PdbScope scope, int recursionCounter) {
if (recursionCounter >= 1000) {
Error("Too many PdbScopes");
return;
}
int startOffset = info.GetOffset(scope.Start);
int endOffset = info.GetOffset(scope.End);
writer.OpenScope(startOffset);
AddLocals(info.Method, scope.Variables, (uint)startOffset, (uint)endOffset);
if (scope.Constants.Count > 0) {
var constants = scope.Constants;
var sig = new FieldSig();
for (int i = 0; i < constants.Count; i++) {
var constant = constants[i];
sig.Type = constant.Type;
var token = metadata.GetToken(sig);
writer.DefineConstant(constant.Name, constant.Value ?? boxedZeroInt32, token.Raw);
}
}
var scopeNamespaces = scope.Namespaces;
int count = scopeNamespaces.Count;
for (int i = 0; i < count; i++)
writer.UsingNamespace(scopeNamespaces[i]);
var scopes = scope.Scopes;
count = scopes.Count;
for (int i = 0; i < count; i++)
WriteScope(ref info, scopes[i], recursionCounter + 1);
writer.CloseScope(startOffset == 0 && endOffset == info.BodySize ? endOffset : endOffset - localsEndScopeIncValue);
}
static readonly object boxedZeroInt32 = 0;
void AddLocals(MethodDef method, IList<PdbLocal> locals, uint startOffset, uint endOffset) {
if (locals.Count == 0)
return;
uint token = metadata.GetLocalVarSigToken(method);
if (token == 0) {
Error("Method {0} ({1:X8}) has no local signature token", method, method.MDToken.Raw);
return;
}
int count = locals.Count;
for (int i = 0; i < count; i++) {
var local = locals[i];
uint attrs = GetPdbLocalFlags(local.Attributes);
if (attrs == 0 && local.Name is null)
continue;
writer.DefineLocalVariable(local.Name ?? string.Empty, attrs,
token, 1, (uint)local.Index, 0, 0, startOffset, endOffset);
}
}
static uint GetPdbLocalFlags(PdbLocalAttributes attributes) {
if ((attributes & PdbLocalAttributes.DebuggerHidden) != 0)
return (uint)CorSymVarFlag.VAR_IS_COMP_GEN;
return 0;
}
MDToken GetUserEntryPointToken() {
var ep = pdbState.UserEntryPoint;
if (ep is null)
return default;
uint rid = metadata.GetRid(ep);
if (rid == 0) {
Error("PDB user entry point method {0} ({1:X8}) is not defined in this module ({2})", ep, ep.MDToken.Raw, module);
return default;
}
return new MDToken(MD.Table.Method, rid);
}
public bool GetDebugInfo(ChecksumAlgorithm pdbChecksumAlgorithm, ref uint pdbAge, out Guid guid, out uint stamp, out IMAGE_DEBUG_DIRECTORY idd, out byte[] codeViewData) =>
writer.GetDebugInfo(pdbChecksumAlgorithm, ref pdbAge, out guid, out stamp, out idd, out codeViewData);
public void Close() => writer.Close();
ILogger GetLogger() => Logger ?? DummyLogger.ThrowModuleWriterExceptionOnErrorInstance;
void Error(string message, params object[] args) => GetLogger().Log(this, LoggerEvent.Error, message, args);
/// <inheritdoc/>
public void Dispose() {
if (writer is not null)
Close();
writer?.Dispose();
writer = null;
}
}
}