1227 lines
29 KiB
C#
1227 lines
29 KiB
C#
//
|
|
// Author:
|
|
// Jb Evain (jbevain@gmail.com)
|
|
//
|
|
// Copyright (c) 2008 - 2015 Jb Evain
|
|
// Copyright (c) 2008 - 2011 Novell, Inc.
|
|
//
|
|
// Licensed under the MIT/X11 license.
|
|
//
|
|
|
|
using MonoFN.Cecil.Cil;
|
|
using MonoFN.Cecil.PE;
|
|
using MonoFN.Collections.Generic;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
using System.Threading;
|
|
using SR = System.Reflection;
|
|
|
|
namespace MonoFN.Cecil.Cil {
|
|
|
|
[StructLayout (LayoutKind.Sequential)]
|
|
public struct ImageDebugDirectory {
|
|
public const int Size = 28;
|
|
|
|
public int Characteristics;
|
|
public int TimeDateStamp;
|
|
public short MajorVersion;
|
|
public short MinorVersion;
|
|
public ImageDebugType Type;
|
|
public int SizeOfData;
|
|
public int AddressOfRawData;
|
|
public int PointerToRawData;
|
|
}
|
|
|
|
public enum ImageDebugType {
|
|
CodeView = 2,
|
|
Deterministic = 16,
|
|
EmbeddedPortablePdb = 17,
|
|
}
|
|
|
|
public sealed class ImageDebugHeader {
|
|
|
|
readonly ImageDebugHeaderEntry [] entries;
|
|
|
|
public bool HasEntries {
|
|
get { return !entries.IsNullOrEmpty (); }
|
|
}
|
|
|
|
public ImageDebugHeaderEntry [] Entries {
|
|
get { return entries; }
|
|
}
|
|
|
|
public ImageDebugHeader (ImageDebugHeaderEntry [] entries)
|
|
{
|
|
this.entries = entries ?? Empty<ImageDebugHeaderEntry>.Array;
|
|
}
|
|
|
|
public ImageDebugHeader ()
|
|
: this (Empty<ImageDebugHeaderEntry>.Array)
|
|
{
|
|
}
|
|
|
|
public ImageDebugHeader (ImageDebugHeaderEntry entry)
|
|
: this (new [] { entry })
|
|
{
|
|
}
|
|
}
|
|
|
|
public sealed class ImageDebugHeaderEntry {
|
|
|
|
ImageDebugDirectory directory;
|
|
readonly byte [] data;
|
|
|
|
public ImageDebugDirectory Directory {
|
|
get { return directory; }
|
|
internal set { directory = value; }
|
|
}
|
|
|
|
public byte [] Data {
|
|
get { return data; }
|
|
}
|
|
|
|
public ImageDebugHeaderEntry (ImageDebugDirectory directory, byte [] data)
|
|
{
|
|
this.directory = directory;
|
|
this.data = data ?? Empty<byte>.Array;
|
|
}
|
|
}
|
|
|
|
public sealed class ScopeDebugInformation : DebugInformation {
|
|
|
|
internal InstructionOffset start;
|
|
internal InstructionOffset end;
|
|
internal ImportDebugInformation import;
|
|
internal Collection<ScopeDebugInformation> scopes;
|
|
internal Collection<VariableDebugInformation> variables;
|
|
internal Collection<ConstantDebugInformation> constants;
|
|
|
|
public InstructionOffset Start {
|
|
get { return start; }
|
|
set { start = value; }
|
|
}
|
|
|
|
public InstructionOffset End {
|
|
get { return end; }
|
|
set { end = value; }
|
|
}
|
|
|
|
public ImportDebugInformation Import {
|
|
get { return import; }
|
|
set { import = value; }
|
|
}
|
|
|
|
public bool HasScopes {
|
|
get { return !scopes.IsNullOrEmpty (); }
|
|
}
|
|
|
|
public Collection<ScopeDebugInformation> Scopes {
|
|
get {
|
|
if (scopes == null)
|
|
Interlocked.CompareExchange (ref scopes, new Collection<ScopeDebugInformation> (), null);
|
|
|
|
return scopes;
|
|
}
|
|
}
|
|
|
|
public bool HasVariables {
|
|
get { return !variables.IsNullOrEmpty (); }
|
|
}
|
|
|
|
public Collection<VariableDebugInformation> Variables {
|
|
get {
|
|
if (variables == null)
|
|
Interlocked.CompareExchange (ref variables, new Collection<VariableDebugInformation> (), null);
|
|
|
|
return variables;
|
|
}
|
|
}
|
|
|
|
public bool HasConstants {
|
|
get { return !constants.IsNullOrEmpty (); }
|
|
}
|
|
|
|
public Collection<ConstantDebugInformation> Constants {
|
|
get {
|
|
if (constants == null)
|
|
Interlocked.CompareExchange (ref constants, new Collection<ConstantDebugInformation> (), null);
|
|
|
|
return constants;
|
|
}
|
|
}
|
|
|
|
internal ScopeDebugInformation ()
|
|
{
|
|
this.token = new MetadataToken (TokenType.LocalScope);
|
|
}
|
|
|
|
public ScopeDebugInformation (Instruction start, Instruction end)
|
|
: this ()
|
|
{
|
|
if (start == null)
|
|
throw new ArgumentNullException ("start");
|
|
|
|
this.start = new InstructionOffset (start);
|
|
|
|
if (end != null)
|
|
this.end = new InstructionOffset (end);
|
|
}
|
|
|
|
public bool TryGetName (VariableDefinition variable, out string name)
|
|
{
|
|
name = null;
|
|
if (variables == null || variables.Count == 0)
|
|
return false;
|
|
|
|
for (int i = 0; i < variables.Count; i++) {
|
|
if (variables [i].Index == variable.Index) {
|
|
name = variables [i].Name;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public struct InstructionOffset {
|
|
|
|
readonly Instruction instruction;
|
|
readonly int? offset;
|
|
|
|
public int Offset {
|
|
get {
|
|
if (instruction != null)
|
|
return instruction.Offset;
|
|
if (offset.HasValue)
|
|
return offset.Value;
|
|
|
|
throw new NotSupportedException ();
|
|
}
|
|
}
|
|
|
|
public bool IsEndOfMethod {
|
|
get { return instruction == null && !offset.HasValue; }
|
|
}
|
|
|
|
internal bool IsResolved => instruction != null || !offset.HasValue;
|
|
|
|
internal Instruction ResolvedInstruction => instruction;
|
|
|
|
public InstructionOffset (Instruction instruction)
|
|
{
|
|
if (instruction == null)
|
|
throw new ArgumentNullException ("instruction");
|
|
|
|
this.instruction = instruction;
|
|
this.offset = null;
|
|
}
|
|
|
|
public InstructionOffset (int offset)
|
|
{
|
|
this.instruction = null;
|
|
this.offset = offset;
|
|
}
|
|
}
|
|
|
|
[Flags]
|
|
public enum VariableAttributes : ushort {
|
|
None = 0,
|
|
DebuggerHidden = 1,
|
|
}
|
|
|
|
public struct VariableIndex {
|
|
readonly VariableDefinition variable;
|
|
readonly int? index;
|
|
|
|
public int Index {
|
|
get {
|
|
if (variable != null)
|
|
return variable.Index;
|
|
if (index.HasValue)
|
|
return index.Value;
|
|
|
|
throw new NotSupportedException ();
|
|
}
|
|
}
|
|
|
|
internal bool IsResolved => variable != null;
|
|
|
|
internal VariableDefinition ResolvedVariable => variable;
|
|
|
|
public VariableIndex (VariableDefinition variable)
|
|
{
|
|
if (variable == null)
|
|
throw new ArgumentNullException ("variable");
|
|
|
|
this.variable = variable;
|
|
this.index = null;
|
|
}
|
|
|
|
public VariableIndex (int index)
|
|
{
|
|
this.variable = null;
|
|
this.index = index;
|
|
}
|
|
}
|
|
|
|
public abstract class DebugInformation : ICustomDebugInformationProvider {
|
|
|
|
internal MetadataToken token;
|
|
internal Collection<CustomDebugInformation> custom_infos;
|
|
|
|
public MetadataToken MetadataToken {
|
|
get { return token; }
|
|
set { token = value; }
|
|
}
|
|
|
|
public bool HasCustomDebugInformations {
|
|
get { return !custom_infos.IsNullOrEmpty (); }
|
|
}
|
|
|
|
public Collection<CustomDebugInformation> CustomDebugInformations {
|
|
get {
|
|
if (custom_infos == null)
|
|
Interlocked.CompareExchange (ref custom_infos, new Collection<CustomDebugInformation> (), null);
|
|
|
|
return custom_infos;
|
|
}
|
|
}
|
|
|
|
internal DebugInformation ()
|
|
{
|
|
}
|
|
}
|
|
|
|
public sealed class VariableDebugInformation : DebugInformation {
|
|
|
|
string name;
|
|
ushort attributes;
|
|
internal VariableIndex index;
|
|
|
|
public int Index {
|
|
get { return index.Index; }
|
|
}
|
|
|
|
public string Name {
|
|
get { return name; }
|
|
set { name = value; }
|
|
}
|
|
|
|
public VariableAttributes Attributes {
|
|
get { return (VariableAttributes)attributes; }
|
|
set { attributes = (ushort)value; }
|
|
}
|
|
|
|
public bool IsDebuggerHidden {
|
|
get { return attributes.GetAttributes ((ushort)VariableAttributes.DebuggerHidden); }
|
|
set { attributes = attributes.SetAttributes ((ushort)VariableAttributes.DebuggerHidden, value); }
|
|
}
|
|
|
|
internal VariableDebugInformation (int index, string name)
|
|
{
|
|
if (name == null)
|
|
throw new ArgumentNullException ("name");
|
|
|
|
this.index = new VariableIndex (index);
|
|
this.name = name;
|
|
}
|
|
|
|
public VariableDebugInformation (VariableDefinition variable, string name)
|
|
{
|
|
if (variable == null)
|
|
throw new ArgumentNullException ("variable");
|
|
if (name == null)
|
|
throw new ArgumentNullException ("name");
|
|
|
|
this.index = new VariableIndex (variable);
|
|
this.name = name;
|
|
this.token = new MetadataToken (TokenType.LocalVariable);
|
|
}
|
|
}
|
|
|
|
public sealed class ConstantDebugInformation : DebugInformation {
|
|
|
|
string name;
|
|
TypeReference constant_type;
|
|
object value;
|
|
|
|
public string Name {
|
|
get { return name; }
|
|
set { name = value; }
|
|
}
|
|
|
|
public TypeReference ConstantType {
|
|
get { return constant_type; }
|
|
set { constant_type = value; }
|
|
}
|
|
|
|
public object Value {
|
|
get { return value; }
|
|
set { this.value = value; }
|
|
}
|
|
|
|
public ConstantDebugInformation (string name, TypeReference constant_type, object value)
|
|
{
|
|
if (name == null)
|
|
throw new ArgumentNullException ("name");
|
|
|
|
this.name = name;
|
|
this.constant_type = constant_type;
|
|
this.value = value;
|
|
this.token = new MetadataToken (TokenType.LocalConstant);
|
|
}
|
|
}
|
|
|
|
public enum ImportTargetKind : byte {
|
|
ImportNamespace = 1,
|
|
ImportNamespaceInAssembly = 2,
|
|
ImportType = 3,
|
|
ImportXmlNamespaceWithAlias = 4,
|
|
ImportAlias = 5,
|
|
DefineAssemblyAlias = 6,
|
|
DefineNamespaceAlias = 7,
|
|
DefineNamespaceInAssemblyAlias = 8,
|
|
DefineTypeAlias = 9,
|
|
}
|
|
|
|
public sealed class ImportTarget {
|
|
|
|
internal ImportTargetKind kind;
|
|
|
|
internal string @namespace;
|
|
internal TypeReference type;
|
|
internal AssemblyNameReference reference;
|
|
internal string alias;
|
|
|
|
public string Namespace {
|
|
get { return @namespace; }
|
|
set { @namespace = value; }
|
|
}
|
|
|
|
public TypeReference Type {
|
|
get { return type; }
|
|
set { type = value; }
|
|
}
|
|
|
|
public AssemblyNameReference AssemblyReference {
|
|
get { return reference; }
|
|
set { reference = value; }
|
|
}
|
|
|
|
public string Alias {
|
|
get { return alias; }
|
|
set { alias = value; }
|
|
}
|
|
|
|
public ImportTargetKind Kind {
|
|
get { return kind; }
|
|
set { kind = value; }
|
|
}
|
|
|
|
public ImportTarget (ImportTargetKind kind)
|
|
{
|
|
this.kind = kind;
|
|
}
|
|
}
|
|
|
|
public sealed class ImportDebugInformation : DebugInformation {
|
|
|
|
internal ImportDebugInformation parent;
|
|
internal Collection<ImportTarget> targets;
|
|
|
|
public bool HasTargets {
|
|
get { return !targets.IsNullOrEmpty (); }
|
|
}
|
|
|
|
public Collection<ImportTarget> Targets {
|
|
get {
|
|
if (targets == null)
|
|
Interlocked.CompareExchange (ref targets, new Collection<ImportTarget> (), null);
|
|
|
|
return targets;
|
|
}
|
|
}
|
|
|
|
public ImportDebugInformation Parent {
|
|
get { return parent; }
|
|
set { parent = value; }
|
|
}
|
|
|
|
public ImportDebugInformation ()
|
|
{
|
|
this.token = new MetadataToken (TokenType.ImportScope);
|
|
}
|
|
}
|
|
|
|
public interface ICustomDebugInformationProvider : IMetadataTokenProvider {
|
|
bool HasCustomDebugInformations { get; }
|
|
Collection<CustomDebugInformation> CustomDebugInformations { get; }
|
|
}
|
|
|
|
public enum CustomDebugInformationKind {
|
|
Binary,
|
|
StateMachineScope,
|
|
DynamicVariable,
|
|
DefaultNamespace,
|
|
AsyncMethodBody,
|
|
EmbeddedSource,
|
|
SourceLink,
|
|
}
|
|
|
|
public abstract class CustomDebugInformation : DebugInformation {
|
|
|
|
Guid identifier;
|
|
|
|
public Guid Identifier {
|
|
get { return identifier; }
|
|
}
|
|
|
|
public abstract CustomDebugInformationKind Kind { get; }
|
|
|
|
internal CustomDebugInformation (Guid identifier)
|
|
{
|
|
this.identifier = identifier;
|
|
this.token = new MetadataToken (TokenType.CustomDebugInformation);
|
|
}
|
|
}
|
|
|
|
public sealed class BinaryCustomDebugInformation : CustomDebugInformation {
|
|
|
|
byte [] data;
|
|
|
|
public byte [] Data {
|
|
get { return data; }
|
|
set { data = value; }
|
|
}
|
|
|
|
public override CustomDebugInformationKind Kind {
|
|
get { return CustomDebugInformationKind.Binary; }
|
|
}
|
|
|
|
public BinaryCustomDebugInformation (Guid identifier, byte [] data)
|
|
: base (identifier)
|
|
{
|
|
this.data = data;
|
|
}
|
|
}
|
|
|
|
public sealed class AsyncMethodBodyDebugInformation : CustomDebugInformation {
|
|
|
|
internal InstructionOffset catch_handler;
|
|
internal Collection<InstructionOffset> yields;
|
|
internal Collection<InstructionOffset> resumes;
|
|
internal Collection<MethodDefinition> resume_methods;
|
|
|
|
public InstructionOffset CatchHandler {
|
|
get { return catch_handler; }
|
|
set { catch_handler = value; }
|
|
}
|
|
|
|
public Collection<InstructionOffset> Yields {
|
|
get {
|
|
if (yields == null)
|
|
Interlocked.CompareExchange (ref yields, new Collection<InstructionOffset> (), null);
|
|
|
|
return yields;
|
|
}
|
|
}
|
|
|
|
public Collection<InstructionOffset> Resumes {
|
|
get {
|
|
if (resumes == null)
|
|
Interlocked.CompareExchange (ref resumes, new Collection<InstructionOffset> (), null);
|
|
|
|
return resumes;
|
|
}
|
|
}
|
|
|
|
public Collection<MethodDefinition> ResumeMethods {
|
|
get { return resume_methods ?? (resume_methods = new Collection<MethodDefinition> ()); }
|
|
}
|
|
|
|
public override CustomDebugInformationKind Kind {
|
|
get { return CustomDebugInformationKind.AsyncMethodBody; }
|
|
}
|
|
|
|
public static Guid KindIdentifier = new Guid ("{54FD2AC5-E925-401A-9C2A-F94F171072F8}");
|
|
|
|
internal AsyncMethodBodyDebugInformation (int catchHandler)
|
|
: base (KindIdentifier)
|
|
{
|
|
this.catch_handler = new InstructionOffset (catchHandler);
|
|
}
|
|
|
|
public AsyncMethodBodyDebugInformation (Instruction catchHandler)
|
|
: base (KindIdentifier)
|
|
{
|
|
this.catch_handler = new InstructionOffset (catchHandler);
|
|
}
|
|
|
|
public AsyncMethodBodyDebugInformation ()
|
|
: base (KindIdentifier)
|
|
{
|
|
this.catch_handler = new InstructionOffset (-1);
|
|
}
|
|
}
|
|
|
|
public sealed class StateMachineScope {
|
|
|
|
internal InstructionOffset start;
|
|
internal InstructionOffset end;
|
|
|
|
public InstructionOffset Start {
|
|
get { return start; }
|
|
set { start = value; }
|
|
}
|
|
|
|
public InstructionOffset End {
|
|
get { return end; }
|
|
set { end = value; }
|
|
}
|
|
|
|
internal StateMachineScope (int start, int end)
|
|
{
|
|
this.start = new InstructionOffset (start);
|
|
this.end = new InstructionOffset (end);
|
|
}
|
|
|
|
public StateMachineScope (Instruction start, Instruction end)
|
|
{
|
|
this.start = new InstructionOffset (start);
|
|
this.end = end != null ? new InstructionOffset (end) : new InstructionOffset ();
|
|
}
|
|
}
|
|
|
|
public sealed class StateMachineScopeDebugInformation : CustomDebugInformation {
|
|
|
|
internal Collection<StateMachineScope> scopes;
|
|
|
|
public Collection<StateMachineScope> Scopes {
|
|
get { return scopes ?? (scopes = new Collection<StateMachineScope> ()); }
|
|
}
|
|
|
|
public override CustomDebugInformationKind Kind {
|
|
get { return CustomDebugInformationKind.StateMachineScope; }
|
|
}
|
|
|
|
public static Guid KindIdentifier = new Guid ("{6DA9A61E-F8C7-4874-BE62-68BC5630DF71}");
|
|
|
|
public StateMachineScopeDebugInformation ()
|
|
: base (KindIdentifier)
|
|
{
|
|
}
|
|
}
|
|
|
|
public sealed class EmbeddedSourceDebugInformation : CustomDebugInformation {
|
|
|
|
internal uint index;
|
|
internal MetadataReader debug_reader;
|
|
internal bool resolved;
|
|
internal byte [] content;
|
|
internal bool compress;
|
|
|
|
public byte [] Content {
|
|
get {
|
|
if (!resolved)
|
|
Resolve ();
|
|
|
|
return content;
|
|
}
|
|
set {
|
|
content = value;
|
|
resolved = true;
|
|
}
|
|
}
|
|
|
|
public bool Compress {
|
|
get {
|
|
if (!resolved)
|
|
Resolve ();
|
|
|
|
return compress;
|
|
}
|
|
set {
|
|
compress = value;
|
|
resolved = true;
|
|
}
|
|
}
|
|
|
|
public override CustomDebugInformationKind Kind {
|
|
get { return CustomDebugInformationKind.EmbeddedSource; }
|
|
}
|
|
|
|
public static Guid KindIdentifier = new Guid ("{0E8A571B-6926-466E-B4AD-8AB04611F5FE}");
|
|
|
|
internal EmbeddedSourceDebugInformation (uint index, MetadataReader debug_reader)
|
|
: base (KindIdentifier)
|
|
{
|
|
this.index = index;
|
|
this.debug_reader = debug_reader;
|
|
}
|
|
|
|
public EmbeddedSourceDebugInformation (byte [] content, bool compress)
|
|
: base (KindIdentifier)
|
|
{
|
|
this.resolved = true;
|
|
this.content = content;
|
|
this.compress = compress;
|
|
}
|
|
|
|
internal byte [] ReadRawEmbeddedSourceDebugInformation ()
|
|
{
|
|
if (debug_reader == null)
|
|
throw new InvalidOperationException ();
|
|
|
|
return debug_reader.ReadRawEmbeddedSourceDebugInformation (index);
|
|
}
|
|
|
|
void Resolve ()
|
|
{
|
|
if (resolved)
|
|
return;
|
|
|
|
if (debug_reader == null)
|
|
throw new InvalidOperationException ();
|
|
|
|
var row = debug_reader.ReadEmbeddedSourceDebugInformation (index);
|
|
content = row.Col1;
|
|
compress = row.Col2;
|
|
resolved = true;
|
|
}
|
|
}
|
|
|
|
public sealed class SourceLinkDebugInformation : CustomDebugInformation {
|
|
|
|
internal string content;
|
|
|
|
public string Content {
|
|
get { return content; }
|
|
set { content = value; }
|
|
}
|
|
|
|
public override CustomDebugInformationKind Kind {
|
|
get { return CustomDebugInformationKind.SourceLink; }
|
|
}
|
|
|
|
public static Guid KindIdentifier = new Guid ("{CC110556-A091-4D38-9FEC-25AB9A351A6A}");
|
|
|
|
public SourceLinkDebugInformation (string content)
|
|
: base (KindIdentifier)
|
|
{
|
|
this.content = content;
|
|
}
|
|
}
|
|
|
|
public sealed class MethodDebugInformation : DebugInformation {
|
|
|
|
internal MethodDefinition method;
|
|
internal Collection<SequencePoint> sequence_points;
|
|
internal ScopeDebugInformation scope;
|
|
internal MethodDefinition kickoff_method;
|
|
internal int code_size;
|
|
internal MetadataToken local_var_token;
|
|
|
|
public MethodDefinition Method {
|
|
get { return method; }
|
|
}
|
|
|
|
public bool HasSequencePoints {
|
|
get { return !sequence_points.IsNullOrEmpty (); }
|
|
}
|
|
|
|
public Collection<SequencePoint> SequencePoints {
|
|
get {
|
|
if (sequence_points == null)
|
|
Interlocked.CompareExchange (ref sequence_points, new Collection<SequencePoint> (), null);
|
|
|
|
return sequence_points;
|
|
}
|
|
}
|
|
|
|
public ScopeDebugInformation Scope {
|
|
get { return scope; }
|
|
set { scope = value; }
|
|
}
|
|
|
|
public MethodDefinition StateMachineKickOffMethod {
|
|
get { return kickoff_method; }
|
|
set { kickoff_method = value; }
|
|
}
|
|
|
|
internal MethodDebugInformation (MethodDefinition method)
|
|
{
|
|
if (method == null)
|
|
throw new ArgumentNullException ("method");
|
|
|
|
this.method = method;
|
|
this.token = new MetadataToken (TokenType.MethodDebugInformation, method.MetadataToken.RID);
|
|
}
|
|
|
|
public SequencePoint GetSequencePoint (Instruction instruction)
|
|
{
|
|
if (!HasSequencePoints)
|
|
return null;
|
|
|
|
for (int i = 0; i < sequence_points.Count; i++)
|
|
if (sequence_points [i].Offset == instruction.Offset)
|
|
return sequence_points [i];
|
|
|
|
return null;
|
|
}
|
|
|
|
public IDictionary<Instruction, SequencePoint> GetSequencePointMapping ()
|
|
{
|
|
var instruction_mapping = new Dictionary<Instruction, SequencePoint> ();
|
|
if (!HasSequencePoints || !method.HasBody)
|
|
return instruction_mapping;
|
|
|
|
var offset_mapping = new Dictionary<int, SequencePoint> (sequence_points.Count);
|
|
|
|
for (int i = 0; i < sequence_points.Count; i++) {
|
|
if (!offset_mapping.ContainsKey (sequence_points [i].Offset))
|
|
offset_mapping.Add (sequence_points [i].Offset, sequence_points [i]);
|
|
}
|
|
|
|
var instructions = method.Body.Instructions;
|
|
|
|
for (int i = 0; i < instructions.Count; i++) {
|
|
SequencePoint sequence_point;
|
|
if (offset_mapping.TryGetValue (instructions [i].Offset, out sequence_point))
|
|
instruction_mapping.Add (instructions [i], sequence_point);
|
|
}
|
|
|
|
return instruction_mapping;
|
|
}
|
|
|
|
public IEnumerable<ScopeDebugInformation> GetScopes ()
|
|
{
|
|
if (scope == null)
|
|
return Empty<ScopeDebugInformation>.Array;
|
|
|
|
return GetScopes (new [] { scope });
|
|
}
|
|
|
|
static IEnumerable<ScopeDebugInformation> GetScopes (IList<ScopeDebugInformation> scopes)
|
|
{
|
|
for (int i = 0; i < scopes.Count; i++) {
|
|
var scope = scopes [i];
|
|
|
|
yield return scope;
|
|
|
|
if (!scope.HasScopes)
|
|
continue;
|
|
|
|
foreach (var sub_scope in GetScopes (scope.Scopes))
|
|
yield return sub_scope;
|
|
}
|
|
}
|
|
|
|
public bool TryGetName (VariableDefinition variable, out string name)
|
|
{
|
|
name = null;
|
|
|
|
var has_name = false;
|
|
var unique_name = "";
|
|
|
|
foreach (var scope in GetScopes ()) {
|
|
string slot_name;
|
|
if (!scope.TryGetName (variable, out slot_name))
|
|
continue;
|
|
|
|
if (!has_name) {
|
|
has_name = true;
|
|
unique_name = slot_name;
|
|
continue;
|
|
}
|
|
|
|
if (unique_name != slot_name)
|
|
return false;
|
|
}
|
|
|
|
name = unique_name;
|
|
return has_name;
|
|
}
|
|
}
|
|
|
|
public interface ISymbolReader : IDisposable {
|
|
|
|
ISymbolWriterProvider GetWriterProvider ();
|
|
bool ProcessDebugHeader (ImageDebugHeader header);
|
|
MethodDebugInformation Read (MethodDefinition method);
|
|
}
|
|
|
|
public interface ISymbolReaderProvider {
|
|
ISymbolReader GetSymbolReader (ModuleDefinition module, string fileName);
|
|
ISymbolReader GetSymbolReader (ModuleDefinition module, Stream symbolStream);
|
|
}
|
|
|
|
#if !NET_CORE
|
|
[Serializable]
|
|
#endif
|
|
public sealed class SymbolsNotFoundException : FileNotFoundException {
|
|
|
|
public SymbolsNotFoundException (string message) : base (message)
|
|
{
|
|
}
|
|
|
|
#if !NET_CORE
|
|
SymbolsNotFoundException (
|
|
System.Runtime.Serialization.SerializationInfo info,
|
|
System.Runtime.Serialization.StreamingContext context)
|
|
: base (info, context)
|
|
{
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if !NET_CORE
|
|
[Serializable]
|
|
#endif
|
|
public sealed class SymbolsNotMatchingException : InvalidOperationException {
|
|
|
|
public SymbolsNotMatchingException (string message) : base (message)
|
|
{
|
|
}
|
|
|
|
#if !NET_CORE
|
|
SymbolsNotMatchingException (
|
|
System.Runtime.Serialization.SerializationInfo info,
|
|
System.Runtime.Serialization.StreamingContext context)
|
|
: base (info, context)
|
|
{
|
|
}
|
|
#endif
|
|
}
|
|
|
|
public class DefaultSymbolReaderProvider : ISymbolReaderProvider {
|
|
|
|
readonly bool throw_if_no_symbol;
|
|
|
|
public DefaultSymbolReaderProvider ()
|
|
: this (throwIfNoSymbol: true)
|
|
{
|
|
}
|
|
|
|
public DefaultSymbolReaderProvider (bool throwIfNoSymbol)
|
|
{
|
|
throw_if_no_symbol = throwIfNoSymbol;
|
|
}
|
|
|
|
public ISymbolReader GetSymbolReader (ModuleDefinition module, string fileName)
|
|
{
|
|
if (module.Image.HasDebugTables ())
|
|
return null;
|
|
|
|
if (module.HasDebugHeader) {
|
|
var header = module.GetDebugHeader ();
|
|
var entry = header.GetEmbeddedPortablePdbEntry ();
|
|
if (entry != null)
|
|
return new EmbeddedPortablePdbReaderProvider ().GetSymbolReader (module, fileName);
|
|
}
|
|
|
|
var pdb_file_name = Mixin.GetPdbFileName (fileName);
|
|
|
|
if (File.Exists (pdb_file_name)) {
|
|
if (Mixin.IsPortablePdb (Mixin.GetPdbFileName (fileName)))
|
|
return new PortablePdbReaderProvider ().GetSymbolReader (module, fileName);
|
|
|
|
try {
|
|
return SymbolProvider.GetReaderProvider (SymbolKind.NativePdb).GetSymbolReader (module, fileName);
|
|
}
|
|
catch (Exception) {
|
|
// We might not include support for native pdbs.
|
|
}
|
|
}
|
|
|
|
var mdb_file_name = Mixin.GetMdbFileName (fileName);
|
|
if (File.Exists (mdb_file_name)) {
|
|
try {
|
|
return SymbolProvider.GetReaderProvider (SymbolKind.Mdb).GetSymbolReader (module, fileName);
|
|
}
|
|
catch (Exception) {
|
|
// We might not include support for mdbs.
|
|
}
|
|
}
|
|
|
|
if (throw_if_no_symbol)
|
|
throw new SymbolsNotFoundException (string.Format ("No symbol found for file: {0}", fileName));
|
|
|
|
return null;
|
|
}
|
|
|
|
public ISymbolReader GetSymbolReader (ModuleDefinition module, Stream symbolStream)
|
|
{
|
|
if (module.Image.HasDebugTables ())
|
|
return null;
|
|
|
|
if (module.HasDebugHeader) {
|
|
var header = module.GetDebugHeader ();
|
|
var entry = header.GetEmbeddedPortablePdbEntry ();
|
|
if (entry != null)
|
|
return new EmbeddedPortablePdbReaderProvider ().GetSymbolReader (module, "");
|
|
}
|
|
|
|
Mixin.CheckStream (symbolStream);
|
|
Mixin.CheckReadSeek (symbolStream);
|
|
|
|
var position = symbolStream.Position;
|
|
|
|
const int portablePdbHeader = 0x424a5342;
|
|
|
|
var reader = new BinaryStreamReader (symbolStream);
|
|
var intHeader = reader.ReadInt32 ();
|
|
symbolStream.Position = position;
|
|
|
|
if (intHeader == portablePdbHeader) {
|
|
return new PortablePdbReaderProvider ().GetSymbolReader (module, symbolStream);
|
|
}
|
|
|
|
const string nativePdbHeader = "Microsoft C/C++ MSF 7.00";
|
|
|
|
var bytesHeader = reader.ReadBytes (nativePdbHeader.Length);
|
|
symbolStream.Position = position;
|
|
var isNativePdb = true;
|
|
|
|
for (var i = 0; i < bytesHeader.Length; i++) {
|
|
if (bytesHeader [i] != (byte)nativePdbHeader [i]) {
|
|
isNativePdb = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (isNativePdb) {
|
|
try {
|
|
return SymbolProvider.GetReaderProvider (SymbolKind.NativePdb).GetSymbolReader (module, symbolStream);
|
|
}
|
|
catch (Exception) {
|
|
// We might not include support for native pdbs.
|
|
}
|
|
}
|
|
|
|
const long mdbHeader = 0x45e82623fd7fa614;
|
|
|
|
var longHeader = reader.ReadInt64 ();
|
|
symbolStream.Position = position;
|
|
|
|
if (longHeader == mdbHeader) {
|
|
try {
|
|
return SymbolProvider.GetReaderProvider (SymbolKind.Mdb).GetSymbolReader (module, symbolStream);
|
|
}
|
|
catch (Exception) {
|
|
// We might not include support for mdbs.
|
|
}
|
|
}
|
|
|
|
if (throw_if_no_symbol)
|
|
throw new SymbolsNotFoundException (string.Format ("No symbols found in stream"));
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
enum SymbolKind {
|
|
NativePdb,
|
|
PortablePdb,
|
|
EmbeddedPortablePdb,
|
|
Mdb,
|
|
}
|
|
|
|
static class SymbolProvider {
|
|
|
|
static SR.AssemblyName GetSymbolAssemblyName (SymbolKind kind)
|
|
{
|
|
if (kind == SymbolKind.PortablePdb)
|
|
throw new ArgumentException ();
|
|
|
|
var suffix = GetSymbolNamespace (kind);
|
|
|
|
var cecil_name = typeof (SymbolProvider).Assembly.GetName ();
|
|
|
|
var name = new SR.AssemblyName {
|
|
Name = cecil_name.Name + "." + suffix,
|
|
Version = cecil_name.Version,
|
|
#if NET_CORE
|
|
CultureName = cecil_name.CultureName,
|
|
#else
|
|
CultureInfo = cecil_name.CultureInfo,
|
|
#endif
|
|
};
|
|
|
|
name.SetPublicKeyToken (cecil_name.GetPublicKeyToken ());
|
|
|
|
return name;
|
|
}
|
|
|
|
static Type GetSymbolType (SymbolKind kind, string fullname)
|
|
{
|
|
var type = Type.GetType (fullname);
|
|
if (type != null)
|
|
return type;
|
|
|
|
var assembly_name = GetSymbolAssemblyName (kind);
|
|
|
|
type = Type.GetType (fullname + ", " + assembly_name.FullName);
|
|
if (type != null)
|
|
return type;
|
|
|
|
try {
|
|
var assembly = SR.Assembly.Load (assembly_name);
|
|
if (assembly != null)
|
|
return assembly.GetType (fullname);
|
|
}
|
|
catch (FileNotFoundException) {
|
|
}
|
|
catch (FileLoadException) {
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public static ISymbolReaderProvider GetReaderProvider (SymbolKind kind)
|
|
{
|
|
if (kind == SymbolKind.PortablePdb)
|
|
return new PortablePdbReaderProvider ();
|
|
if (kind == SymbolKind.EmbeddedPortablePdb)
|
|
return new EmbeddedPortablePdbReaderProvider ();
|
|
|
|
var provider_name = GetSymbolTypeName (kind, "ReaderProvider");
|
|
var type = GetSymbolType (kind, provider_name);
|
|
if (type == null)
|
|
throw new TypeLoadException ("Could not find symbol provider type " + provider_name);
|
|
|
|
return (ISymbolReaderProvider)Activator.CreateInstance (type);
|
|
}
|
|
|
|
static string GetSymbolTypeName (SymbolKind kind, string name)
|
|
{
|
|
return "MonoFN.Cecil" + "." + GetSymbolNamespace (kind) + "." + kind + name;
|
|
}
|
|
|
|
static string GetSymbolNamespace (SymbolKind kind)
|
|
{
|
|
if (kind == SymbolKind.PortablePdb || kind == SymbolKind.EmbeddedPortablePdb)
|
|
return "Cil";
|
|
if (kind == SymbolKind.NativePdb)
|
|
return "Pdb";
|
|
if (kind == SymbolKind.Mdb)
|
|
return "Mdb";
|
|
|
|
throw new ArgumentException ();
|
|
}
|
|
}
|
|
|
|
public interface ISymbolWriter : IDisposable {
|
|
|
|
ISymbolReaderProvider GetReaderProvider ();
|
|
ImageDebugHeader GetDebugHeader ();
|
|
void Write (MethodDebugInformation info);
|
|
}
|
|
|
|
public interface ISymbolWriterProvider {
|
|
|
|
ISymbolWriter GetSymbolWriter (ModuleDefinition module, string fileName);
|
|
ISymbolWriter GetSymbolWriter (ModuleDefinition module, Stream symbolStream);
|
|
}
|
|
|
|
public class DefaultSymbolWriterProvider : ISymbolWriterProvider {
|
|
|
|
public ISymbolWriter GetSymbolWriter (ModuleDefinition module, string fileName)
|
|
{
|
|
var reader = module.SymbolReader;
|
|
if (reader == null)
|
|
throw new InvalidOperationException ();
|
|
|
|
if (module.Image != null && module.Image.HasDebugTables ())
|
|
return null;
|
|
|
|
return reader.GetWriterProvider ().GetSymbolWriter (module, fileName);
|
|
}
|
|
|
|
public ISymbolWriter GetSymbolWriter (ModuleDefinition module, Stream symbolStream)
|
|
{
|
|
throw new NotSupportedException ();
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace MonoFN.Cecil {
|
|
|
|
static partial class Mixin {
|
|
|
|
public static ImageDebugHeaderEntry GetCodeViewEntry (this ImageDebugHeader header)
|
|
{
|
|
return GetEntry (header, ImageDebugType.CodeView);
|
|
}
|
|
|
|
public static ImageDebugHeaderEntry GetDeterministicEntry (this ImageDebugHeader header)
|
|
{
|
|
return GetEntry (header, ImageDebugType.Deterministic);
|
|
}
|
|
|
|
public static ImageDebugHeader AddDeterministicEntry (this ImageDebugHeader header)
|
|
{
|
|
var entry = new ImageDebugHeaderEntry (new ImageDebugDirectory { Type = ImageDebugType.Deterministic }, Empty<byte>.Array);
|
|
if (header == null)
|
|
return new ImageDebugHeader (entry);
|
|
|
|
var entries = new ImageDebugHeaderEntry [header.Entries.Length + 1];
|
|
Array.Copy (header.Entries, entries, header.Entries.Length);
|
|
entries [entries.Length - 1] = entry;
|
|
return new ImageDebugHeader (entries);
|
|
}
|
|
|
|
public static ImageDebugHeaderEntry GetEmbeddedPortablePdbEntry (this ImageDebugHeader header)
|
|
{
|
|
return GetEntry (header, ImageDebugType.EmbeddedPortablePdb);
|
|
}
|
|
|
|
private static ImageDebugHeaderEntry GetEntry (this ImageDebugHeader header, ImageDebugType type)
|
|
{
|
|
if (!header.HasEntries)
|
|
return null;
|
|
|
|
for (var i = 0; i < header.Entries.Length; i++) {
|
|
var entry = header.Entries [i];
|
|
if (entry.Directory.Type == type)
|
|
return entry;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public static string GetPdbFileName (string assemblyFileName)
|
|
{
|
|
return Path.ChangeExtension (assemblyFileName, ".pdb");
|
|
}
|
|
|
|
public static string GetMdbFileName (string assemblyFileName)
|
|
{
|
|
return assemblyFileName + ".mdb";
|
|
}
|
|
|
|
public static bool IsPortablePdb (string fileName)
|
|
{
|
|
using (var file = new FileStream (fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
|
return IsPortablePdb (file);
|
|
}
|
|
|
|
public static bool IsPortablePdb (Stream stream)
|
|
{
|
|
const uint ppdb_signature = 0x424a5342;
|
|
|
|
if (stream.Length < 4) return false;
|
|
var position = stream.Position;
|
|
try {
|
|
var reader = new BinaryReader (stream);
|
|
return reader.ReadUInt32 () == ppdb_signature;
|
|
}
|
|
finally {
|
|
stream.Position = position;
|
|
}
|
|
}
|
|
}
|
|
}
|