From 692665179172396fec712f2ed0da2417dc38d50a Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 22 Jul 2018 22:14:50 +0200 Subject: [PATCH] First draft of support for ImportScope and LocalScope tables. --- .../DebugInfo/ImportScopeInfo.cs | 41 ++++ .../DebugInfo/PortablePdbWriter.cs | 125 +++++----- .../DebugInfo/ScopesGenerator.cs | 215 ++++++++++++++++++ .../ICSharpCode.Decompiler.csproj | 2 + 4 files changed, 316 insertions(+), 67 deletions(-) create mode 100644 ICSharpCode.Decompiler/DebugInfo/ImportScopeInfo.cs create mode 100644 ICSharpCode.Decompiler/DebugInfo/ScopesGenerator.cs diff --git a/ICSharpCode.Decompiler/DebugInfo/ImportScopeInfo.cs b/ICSharpCode.Decompiler/DebugInfo/ImportScopeInfo.cs new file mode 100644 index 000000000..f4019aa11 --- /dev/null +++ b/ICSharpCode.Decompiler/DebugInfo/ImportScopeInfo.cs @@ -0,0 +1,41 @@ +// Copyright (c) 2018 Siegfried Pammer +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Collections.Generic; +using System.Reflection.Metadata; + +namespace ICSharpCode.Decompiler.DebugInfo +{ + class ImportScopeInfo + { + public readonly ImportScopeInfo Parent; + public ImportScopeHandle Handle; + public readonly HashSet MethodDefinitions = new HashSet(); + public readonly HashSet Imports = new HashSet(); + + public ImportScopeInfo() + { + Parent = null; + } + + public ImportScopeInfo(ImportScopeInfo parent) + { + Parent = parent; + } + } +} diff --git a/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs b/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs index 64432eb84..2eee6fd26 100644 --- a/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs +++ b/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs @@ -1,4 +1,22 @@ -using System; +// Copyright (c) 2018 Siegfried Pammer +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; @@ -43,67 +61,55 @@ namespace ICSharpCode.Decompiler.DebugInfo var hasher = SHA256.Create(); var sequencePointBlobs = new Dictionary(); - var importScopeBlobs = new Dictionary(); var emptyList = new List(); + var globalImportScope = metadata.AddImportScope(default, default); foreach (var handle in reader.GetTopLevelTypeDefinitions()) { var type = reader.GetTypeDefinition(handle); + + // Generate syntax tree, source and checksum var name = metadata.GetOrAddDocumentName("ILSpy_Generated_" + type.GetFullTypeName(reader) + "_" + Guid.NewGuid() + ".cs"); - var ast = decompiler.DecompileTypes(new[] { handle }); - ast.InsertChildAfter(null, new Comment(" PDB and source generated by ICSharpCode.Decompiler " + decompilerVersion.FileVersion), Roles.Comment); - var sourceText = SyntaxTreeToString(ast, settings); - var sequencePoints = decompiler.CreateSequencePoints(ast).ToDictionary(sp => (MethodDefinitionHandle)sp.Key.Method.MetadataToken, sp => sp.Value); + var syntaxTree = decompiler.DecompileTypes(new[] { handle }); + syntaxTree.InsertChildAfter(null, new Comment(" PDB and source generated by ICSharpCode.Decompiler " + decompilerVersion.FileVersion), Roles.Comment); + var sourceText = SyntaxTreeToString(syntaxTree, settings); var sourceCheckSum = hasher.ComputeHash(Encoding.UTF8.GetBytes(sourceText)); var sourceBlob = WriteSourceToBlob(metadata, sourceText); + // Create Document(Handle) var document = metadata.AddDocument(name, hashAlgorithm: metadata.GetOrAddGuid(HashAlgorithmSHA256), hash: metadata.GetOrAddBlob(sourceCheckSum), language: metadata.GetOrAddGuid(CSharpLanguageGuid)); + // Add embedded source to the PDB metadata.AddCustomDebugInformation(document, metadata.GetOrAddGuid(DebugInfoEmbeddedSource), sourceBlob); + ScopesGenerator.Generate(decompiler.TypeSystem, metadata, globalImportScope, syntaxTree); + + // Generate sequence points for the syntax tree + var sequencePoints = decompiler.CreateSequencePoints(syntaxTree).ToDictionary(sp => (MethodDefinitionHandle)sp.Key.Method.MetadataToken, sp => sp.Value); + foreach (var method in type.GetMethods()) { - var methodDef = reader.GetMethodDefinition(method); - if (!sequencePoints.TryGetValue(method, out var points)) - points = emptyList; - int localSignatureRowId; - MethodBodyBlock methodBody; - if (methodDef.RelativeVirtualAddress != 0) { - methodBody = file.Reader.GetMethodBody(methodDef.RelativeVirtualAddress); - localSignatureRowId = methodBody.LocalSignature.IsNil ? 0 : MetadataTokens.GetRowNumber(methodBody.LocalSignature); - } else { - methodBody = null; - localSignatureRowId = 0; - } - if (points.Count == 0) - sequencePointBlobs.Add(method, (default, default)); - else - sequencePointBlobs.Add(method, (document, EncodeSequencePoints(metadata, localSignatureRowId, points))); - importScopeBlobs.Add(method, (document, EncodeImportScope(metadata, reader, ast, decompiler.TypeSystem))); + ProcessMethod(method, document, sequencePoints, syntaxTree); } foreach (var nestedTypeHandle in type.GetNestedTypes()) { var nestedType = reader.GetTypeDefinition(nestedTypeHandle); foreach (var method in nestedType.GetMethods()) { - + ProcessMethod(method, document, sequencePoints, syntaxTree); } } } foreach (var method in reader.MethodDefinitions) { + var md = reader.GetMethodDefinition(method); + if (sequencePointBlobs.TryGetValue(method, out var info)) { metadata.AddMethodDebugInformation(info.Document, info.SequencePoints); } else { metadata.AddMethodDebugInformation(default, default); } - /*if (importScopeBlobs.TryGetValue(method, out var scopeInfo)) { - //metadata.AddImportScope(default, scopeInfo.ImportScope); - metadata.AddImportScope(default, default); - } else { - metadata.AddImportScope(default, default); - }*/ } var debugDir = file.Reader.ReadDebugDirectory().FirstOrDefault(dir => dir.Type == DebugDirectoryEntryType.CodeView); @@ -113,6 +119,28 @@ namespace ICSharpCode.Decompiler.DebugInfo BlobBuilder blobBuilder = new BlobBuilder(); serializer.Serialize(blobBuilder); blobBuilder.WriteContentTo(targetStream); + + void ProcessMethod(MethodDefinitionHandle method, DocumentHandle document, + Dictionary> sequencePoints, SyntaxTree syntaxTree) + { + var methodDef = reader.GetMethodDefinition(method); + int localSignatureRowId; + MethodBodyBlock methodBody; + if (methodDef.RelativeVirtualAddress != 0) { + methodBody = file.Reader.GetMethodBody(methodDef.RelativeVirtualAddress); + localSignatureRowId = methodBody.LocalSignature.IsNil ? 0 : MetadataTokens.GetRowNumber(methodBody.LocalSignature); + } else { + methodBody = null; + localSignatureRowId = 0; + } + if (!sequencePoints.TryGetValue(method, out var points)) + points = emptyList; + if (points.Count == 0) + sequencePointBlobs.Add(method, (default, default)); + else + sequencePointBlobs.Add(method, (document, EncodeSequencePoints(metadata, localSignatureRowId, points))); + + } } static BlobHandle WriteSourceToBlob(MetadataBuilder metadata, string sourceText) @@ -124,43 +152,6 @@ namespace ICSharpCode.Decompiler.DebugInfo return metadata.GetOrAddBlob(builder); } - static BlobHandle EncodeImportScope(MetadataBuilder metadata, MetadataReader reader, SyntaxTree ast, ICompilation compilation) - { - var scope = ast.Annotation()?.Resolve(compilation); - - if (scope == null) - return default; - - Dictionary assemblyReferences = new Dictionary(); - - var writer = new BlobBuilder(); - - foreach (var import in scope.Usings) { - foreach (var asm in import.ContributingModules) { - if (asm == compilation.MainModule) { - writer.WriteByte(1); - writer.WriteCompressedInteger(MetadataTokens.GetHeapOffset(metadata.GetOrAddBlobUTF8(import.FullName))); - } else { - writer.WriteByte(2); - if (!assemblyReferences.TryGetValue(asm, out var referenceHandle)) { - foreach (var h in reader.AssemblyReferences) { - var reference = reader.GetAssemblyReference(h); - string asmName = reader.GetString(reference.Name); - if (asmName == asm.AssemblyName) { - assemblyReferences.Add(asm, referenceHandle = h); - } - } - } - Debug.Assert(!referenceHandle.IsNil); - writer.WriteCompressedInteger(MetadataTokens.GetRowNumber(referenceHandle)); - writer.WriteCompressedInteger(MetadataTokens.GetHeapOffset(metadata.GetOrAddBlobUTF8(import.FullName))); - } - } - } - - return metadata.GetOrAddBlob(writer); - } - static BlobHandle EncodeSequencePoints(MetadataBuilder metadata, int localSignatureRowId, List sequencePoints) { if (sequencePoints.Count == 0) diff --git a/ICSharpCode.Decompiler/DebugInfo/ScopesGenerator.cs b/ICSharpCode.Decompiler/DebugInfo/ScopesGenerator.cs new file mode 100644 index 000000000..79536ee51 --- /dev/null +++ b/ICSharpCode.Decompiler/DebugInfo/ScopesGenerator.cs @@ -0,0 +1,215 @@ +// Copyright (c) 2018 Siegfried Pammer +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; +using System.Text; +using ICSharpCode.Decompiler.CSharp; +using ICSharpCode.Decompiler.CSharp.Syntax; +using ICSharpCode.Decompiler.IL; +using ICSharpCode.Decompiler.Metadata; +using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.Decompiler.Util; + +namespace ICSharpCode.Decompiler.DebugInfo +{ + class ScopesGenerator : DepthFirstAstVisitor + { + static readonly KeyComparer ILVariableKeyComparer = new KeyComparer(l => l.Index, Comparer.Default, EqualityComparer.Default); + + IDecompilerTypeSystem typeSystem; + ImportScopeInfo currentImportScope; + List importScopes = new List(); + List<(MethodDefinitionHandle Method, ImportScopeInfo Import, int Offset, int Length, HashSet Locals)> localScopes = new List<(MethodDefinitionHandle Method, ImportScopeInfo Import, int Offset, int Length, HashSet Locals)>(); + + private ScopesGenerator() + { + } + + public static void Generate(IDecompilerTypeSystem typeSystem, MetadataBuilder metadata, ImportScopeHandle globalImportScope, SyntaxTree syntaxTree) + { + var generator = new ScopesGenerator(); + generator.typeSystem = typeSystem; + syntaxTree.AcceptVisitor(generator, new ImportScopeInfo()); + foreach (var scope in generator.importScopes) { + var blob = EncodeImports(metadata, scope); + var importScope = scope.Handle = metadata.AddImportScope(scope.Parent == null ? globalImportScope : scope.Parent.Handle, blob); + } + + foreach (var localScope in generator.localScopes) { + LocalVariableHandle firstLocalVariable = default; + + foreach (var local in localScope.Locals.OrderBy(l => l.Index)) { + var h = metadata.AddLocalVariable(LocalVariableAttributes.None, local.Index, metadata.GetOrAddString(local.Name)); + if (firstLocalVariable.IsNil) + firstLocalVariable = h; + } + + metadata.AddLocalScope(localScope.Method, localScope.Import.Handle, firstLocalVariable, + default, localScope.Offset, localScope.Length); + } + } + + private static bool IsSupported(VariableKind kind) + { + return kind == VariableKind.Local + || kind == VariableKind.PinnedLocal + || kind == VariableKind.UsingLocal + || kind == VariableKind.ForeachLocal; + } + + static BlobHandle EncodeImports(MetadataBuilder metadata, ImportScopeInfo scope) + { + var writer = new BlobBuilder(); + + foreach (var import in scope.Imports) { + writer.WriteByte((byte)ImportDefinitionKind.ImportNamespace); + writer.WriteCompressedInteger(MetadataTokens.GetHeapOffset(metadata.GetOrAddBlobUTF8(import))); + } + + return metadata.GetOrAddBlob(writer); + } + + public override ImportScopeInfo VisitSyntaxTree(SyntaxTree unit, ImportScopeInfo data) + { + importScopes.Add(data); + currentImportScope = data; + base.VisitSyntaxTree(unit, data); + currentImportScope = data; + return data; + } + + public override ImportScopeInfo VisitNamespaceDeclaration(NamespaceDeclaration namespaceDeclaration, ImportScopeInfo data) + { + ImportScopeInfo scope = new ImportScopeInfo(data); + importScopes.Add(scope); + currentImportScope = scope; + base.VisitNamespaceDeclaration(namespaceDeclaration, scope); + currentImportScope = data; + return data; + } + + public override ImportScopeInfo VisitUsingDeclaration(UsingDeclaration usingDeclaration, ImportScopeInfo data) + { + data.Imports.Add(usingDeclaration.Namespace); + return data; + } + + public override ImportScopeInfo VisitMethodDeclaration(MethodDeclaration methodDeclaration, ImportScopeInfo data) + { + HandleMethod(methodDeclaration, data); + return data; + } + + public override ImportScopeInfo VisitPropertyDeclaration(PropertyDeclaration propertyDeclaration, ImportScopeInfo data) + { + var symbol = propertyDeclaration.GetSymbol(); + if (symbol is IProperty property && !property.MetadataToken.IsNil) { + if (property.CanGet && !property.Getter.MetadataToken.IsNil) + data.MethodDefinitions.Add((MethodDefinitionHandle)property.Getter.MetadataToken); + if (property.CanSet && !property.Setter.MetadataToken.IsNil) + data.MethodDefinitions.Add((MethodDefinitionHandle)property.Setter.MetadataToken); + } + return data; + } + + public override ImportScopeInfo VisitCustomEventDeclaration(CustomEventDeclaration eventDeclaration, ImportScopeInfo data) + { + HandleEvent(eventDeclaration, data); + return data; + } + + void HandleEvent(AstNode node, ImportScopeInfo data) + { + var symbol = node.GetSymbol(); + if (symbol is IEvent @event && !@event.MetadataToken.IsNil) { + if (@event.CanAdd && !@event.AddAccessor.MetadataToken.IsNil) + data.MethodDefinitions.Add((MethodDefinitionHandle)@event.AddAccessor.MetadataToken); + if (@event.CanRemove && !@event.RemoveAccessor.MetadataToken.IsNil) + data.MethodDefinitions.Add((MethodDefinitionHandle)@event.RemoveAccessor.MetadataToken); + if (@event.CanInvoke && !@event.InvokeAccessor.MetadataToken.IsNil) + data.MethodDefinitions.Add((MethodDefinitionHandle)@event.InvokeAccessor.MetadataToken); + } + } + + public override ImportScopeInfo VisitEventDeclaration(EventDeclaration eventDeclaration, ImportScopeInfo data) + { + HandleEvent(eventDeclaration, data); + return data; + } + + public override ImportScopeInfo VisitConstructorDeclaration(ConstructorDeclaration constructorDeclaration, ImportScopeInfo data) + { + HandleMethod(constructorDeclaration, data); + return data; + } + + public override ImportScopeInfo VisitDestructorDeclaration(DestructorDeclaration destructorDeclaration, ImportScopeInfo data) + { + HandleMethod(destructorDeclaration, data); + return data; + } + + void HandleMethod(AstNode node, ImportScopeInfo data) + { + var symbol = node.GetSymbol(); + var function = node.Annotations.OfType().FirstOrDefault(); + if (function != null && symbol is IMethod method && !method.MetadataToken.IsNil) { + MethodDefinitionHandle handle = (MethodDefinitionHandle)method.MetadataToken; + data.MethodDefinitions.Add(handle); + var file = typeSystem.MainModule.PEFile; + MethodDefinition md = file.Metadata.GetMethodDefinition(handle); + if (md.HasBody()) { + HandleMethodBody(function, file.Reader.GetMethodBody(md.RelativeVirtualAddress)); + } + } + } + + void HandleMethodBody(ILFunction function, MethodBodyBlock methodBody) + { + var localVariables = new HashSet(ILVariableKeyComparer); + + if (!methodBody.LocalSignature.IsNil) { + var types = typeSystem.MainModule.DecodeLocalSignature(methodBody.LocalSignature, + new TypeSystem.GenericContext(function.Method)); + + foreach (var v in function.Variables) { + switch (v.Kind) { + case VariableKind.Local: + case VariableKind.PinnedLocal: + case VariableKind.UsingLocal: + case VariableKind.ForeachLocal: + case VariableKind.Exception: + if (v.Index < types.Length && v.Type.Equals(types[v.Index])) { + localVariables.Add(v); + } + break; + default: + continue; + } + } + } + + localScopes.Add(((MethodDefinitionHandle)function.Method.MetadataToken, currentImportScope, + 0, methodBody.GetCodeSize(), localVariables)); + } + } +} diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 9f0800f9f..8bd2fad6e 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -243,6 +243,8 @@ + +