// 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; using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; using System.Reflection.PortableExecutable; using System.Security.Cryptography; using System.Text; using ICSharpCode.Decompiler.CSharp; using ICSharpCode.Decompiler.CSharp.OutputVisitor; using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.CSharp.TypeSystem; using ICSharpCode.Decompiler.IL; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.Util; namespace ICSharpCode.Decompiler.DebugInfo { public class PortablePdbWriter { public static readonly Guid CSharpLanguageGuid = new Guid("3f5162f8-07c6-11d3-9053-00c04fa302a1"); public static readonly Guid DebugInfoEmbeddedSource = new Guid("0e8a571b-6926-466e-b4ad-8ab04611f5fe"); public static readonly Guid HashAlgorithmSHA1 = new Guid("ff1816ec-aa5e-4d10-87f7-6f4963833460"); public static readonly Guid HashAlgorithmSHA256 = new Guid("8829d00f-11b8-4213-878b-770e8597ac16"); static readonly FileVersionInfo decompilerVersion = FileVersionInfo.GetVersionInfo(typeof(CSharpDecompiler).Assembly.Location); static readonly SHA256 hasher = SHA256.Create(); public static bool HasCodeViewDebugDirectoryEntry(PEFile file) { return file.Reader.ReadDebugDirectory().Any(entry => entry.Type == DebugDirectoryEntryType.CodeView); } public static void WritePdb(PEFile file, CSharpDecompiler decompiler, DecompilerSettings settings, Stream targetStream, bool noLogo = false) { MetadataBuilder metadata = new MetadataBuilder(); MetadataReader reader = file.Metadata; var entrypointHandle = MetadataTokens.MethodDefinitionHandle(file.Reader.PEHeaders.CorHeader.EntryPointTokenOrRelativeVirtualAddress); var sequencePointBlobs = 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 var syntaxTree = decompiler.DecompileTypes(new[] { handle }); if (!syntaxTree.HasChildren) continue; // Generate source and checksum var name = metadata.GetOrAddDocumentName(type.GetFullTypeName(reader).ReflectionName.Replace('.', Path.DirectorySeparatorChar) + ".cs"); if (!noLogo) syntaxTree.InsertChildAfter(null, new Comment(" PDB and source generated by ICSharpCode.Decompiler " + decompilerVersion.FileVersion), Roles.Comment); var sourceText = SyntaxTreeToString(syntaxTree, settings); var sourceBlob = WriteSourceToBlob(metadata, sourceText, out var sourceCheckSum); // 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()) { 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); } } var debugDir = file.Reader.ReadDebugDirectory().FirstOrDefault(dir => dir.Type == DebugDirectoryEntryType.CodeView); var portable = file.Reader.ReadCodeViewDebugDirectoryData(debugDir); var contentId = new BlobContentId(portable.Guid, debugDir.Stamp); PortablePdbBuilder serializer = new PortablePdbBuilder(metadata, GetRowCounts(reader), entrypointHandle, blobs => contentId); 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, out byte[] sourceCheckSum) { var builder = new BlobBuilder(); using (var memory = new MemoryStream()) { var deflate = new DeflateStream(memory, CompressionLevel.Optimal, leaveOpen: true); byte[] bytes = Encoding.UTF8.GetBytes(sourceText); deflate.Write(bytes, 0, bytes.Length); deflate.Close(); byte[] buffer = memory.ToArray(); builder.WriteInt32(bytes.Length); // compressed builder.WriteBytes(buffer); sourceCheckSum = hasher.ComputeHash(bytes); } return metadata.GetOrAddBlob(builder); } static BlobHandle EncodeSequencePoints(MetadataBuilder metadata, int localSignatureRowId, List sequencePoints) { if (sequencePoints.Count == 0) return default; var writer = new BlobBuilder(); // header: writer.WriteCompressedInteger(localSignatureRowId); int previousOffset = -1; int previousStartLine = -1; int previousStartColumn = -1; for (int i = 0; i < sequencePoints.Count; i++) { var sequencePoint = sequencePoints[i]; // delta IL offset: if (i > 0) writer.WriteCompressedInteger(sequencePoint.Offset - previousOffset); else writer.WriteCompressedInteger(sequencePoint.Offset); previousOffset = sequencePoint.Offset; if (sequencePoint.IsHidden) { writer.WriteInt16(0); continue; } int lineDelta = sequencePoint.EndLine - sequencePoint.StartLine; int columnDelta = sequencePoint.EndColumn - sequencePoint.StartColumn; writer.WriteCompressedInteger(lineDelta); if (lineDelta == 0) { writer.WriteCompressedInteger(columnDelta); } else { writer.WriteCompressedSignedInteger(columnDelta); } if (previousStartLine < 0) { writer.WriteCompressedInteger(sequencePoint.StartLine); writer.WriteCompressedInteger(sequencePoint.StartColumn); } else { writer.WriteCompressedSignedInteger(sequencePoint.StartLine - previousStartLine); writer.WriteCompressedSignedInteger(sequencePoint.StartColumn - previousStartColumn); } previousStartLine = sequencePoint.StartLine; previousStartColumn = sequencePoint.StartColumn; } return metadata.GetOrAddBlob(writer); } static ImmutableArray GetRowCounts(MetadataReader reader) { var builder = ImmutableArray.CreateBuilder(MetadataTokens.TableCount); for (int i = 0; i < MetadataTokens.TableCount; i++) { builder.Add(reader.GetTableRowCount((TableIndex)i)); } return builder.MoveToImmutable(); } static string SyntaxTreeToString(SyntaxTree syntaxTree, DecompilerSettings settings) { StringWriter w = new StringWriter(); TokenWriter tokenWriter = new TextWriterTokenWriter(w); syntaxTree.AcceptVisitor(new InsertParenthesesVisitor { InsertParenthesesForReadability = true }); tokenWriter = TokenWriter.WrapInWriterThatSetsLocationsInAST(tokenWriter); syntaxTree.AcceptVisitor(new CSharpOutputVisitor(tokenWriter, settings.CSharpFormattingOptions)); return w.ToString(); } } }