You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

393 lines
14 KiB

  1. // Copyright (c) 2018 Siegfried Pammer
  2. //
  3. // Permission is hereby granted, free of charge, to any person obtaining a copy of this
  4. // software and associated documentation files (the "Software"), to deal in the Software
  5. // without restriction, including without limitation the rights to use, copy, modify, merge,
  6. // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
  7. // to whom the Software is furnished to do so, subject to the following conditions:
  8. //
  9. // The above copyright notice and this permission notice shall be included in all copies or
  10. // substantial portions of the Software.
  11. //
  12. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  13. // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  14. // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
  15. // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  16. // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  17. // DEALINGS IN THE SOFTWARE.
  18. using System;
  19. using System.Collections.Generic;
  20. using System.Collections.Immutable;
  21. using System.Diagnostics;
  22. using System.IO;
  23. using System.IO.Compression;
  24. using System.Linq;
  25. using System.Reflection.Metadata;
  26. using System.Reflection.Metadata.Ecma335;
  27. using System.Reflection.PortableExecutable;
  28. using System.Security.Cryptography;
  29. using System.Text;
  30. using ICSharpCode.Decompiler.CSharp;
  31. using ICSharpCode.Decompiler.CSharp.OutputVisitor;
  32. using ICSharpCode.Decompiler.CSharp.ProjectDecompiler;
  33. using ICSharpCode.Decompiler.CSharp.Syntax;
  34. using ICSharpCode.Decompiler.CSharp.Transforms;
  35. using ICSharpCode.Decompiler.IL;
  36. using ICSharpCode.Decompiler.Metadata;
  37. using ICSharpCode.Decompiler.TypeSystem;
  38. using ICSharpCode.Decompiler.Util;
  39. namespace ICSharpCode.Decompiler.DebugInfo
  40. {
  41. public class PortablePdbWriter
  42. {
  43. static readonly FileVersionInfo decompilerVersion = FileVersionInfo.GetVersionInfo(typeof(CSharpDecompiler).Assembly.Location);
  44. public static bool HasCodeViewDebugDirectoryEntry(PEFile file)
  45. {
  46. return file != null && file.Reader.ReadDebugDirectory().Any(entry => entry.Type == DebugDirectoryEntryType.CodeView);
  47. }
  48. private static bool IncludeTypeWhenGeneratingPdb(PEFile module, TypeDefinitionHandle type, DecompilerSettings settings)
  49. {
  50. var metadata = module.Metadata;
  51. var typeDef = metadata.GetTypeDefinition(type);
  52. string name = metadata.GetString(typeDef.Name);
  53. string ns = metadata.GetString(typeDef.Namespace);
  54. if (name == "<Module>" || CSharpDecompiler.MemberIsHidden(module, type, settings))
  55. return false;
  56. if (ns == "XamlGeneratedNamespace" && name == "GeneratedInternalTypeHelper")
  57. return false;
  58. if (!typeDef.IsNested && RemoveEmbeddedAttributes.attributeNames.Contains(ns + "." + name))
  59. return false;
  60. return true;
  61. }
  62. public static void WritePdb(
  63. PEFile file,
  64. CSharpDecompiler decompiler,
  65. DecompilerSettings settings,
  66. Stream targetStream,
  67. bool noLogo = false,
  68. BlobContentId? pdbId = null,
  69. IProgress<DecompilationProgress> progress = null)
  70. {
  71. MetadataBuilder metadata = new MetadataBuilder();
  72. MetadataReader reader = file.Metadata;
  73. var entrypointHandle = MetadataTokens.MethodDefinitionHandle(file.Reader.PEHeaders.CorHeader.EntryPointTokenOrRelativeVirtualAddress);
  74. var sequencePointBlobs = new Dictionary<MethodDefinitionHandle, (DocumentHandle Document, BlobHandle SequencePoints)>();
  75. var emptyList = new List<SequencePoint>();
  76. var localScopes = new List<(MethodDefinitionHandle Method, ImportScopeInfo Import, int Offset, int Length, HashSet<ILVariable> Locals)>();
  77. var stateMachineMethods = new List<(MethodDefinitionHandle MoveNextMethod, MethodDefinitionHandle KickoffMethod)>();
  78. var customDebugInfo = new List<(EntityHandle Parent, GuidHandle Guid, BlobHandle Blob)>();
  79. var customMethodDebugInfo = new List<(MethodDefinitionHandle Parent, GuidHandle Guid, BlobHandle Blob)>();
  80. var globalImportScope = metadata.AddImportScope(default, default);
  81. string BuildFileNameFromTypeName(TypeDefinitionHandle handle)
  82. {
  83. var typeName = handle.GetFullTypeName(reader).TopLevelTypeName;
  84. string ns = settings.UseNestedDirectoriesForNamespaces
  85. ? WholeProjectDecompiler.CleanUpPath(typeName.Namespace)
  86. : WholeProjectDecompiler.CleanUpDirectoryName(typeName.Namespace);
  87. return Path.Combine(ns, WholeProjectDecompiler.CleanUpFileName(typeName.Name, ".cs"));
  88. }
  89. var sourceFiles = reader.GetTopLevelTypeDefinitions().Where(t => IncludeTypeWhenGeneratingPdb(file, t, settings)).GroupBy(BuildFileNameFromTypeName).ToList();
  90. DecompilationProgress currentProgress = new() {
  91. TotalUnits = sourceFiles.Count,
  92. UnitsCompleted = 0,
  93. Title = "Generating portable PDB..."
  94. };
  95. foreach (var sourceFile in sourceFiles)
  96. {
  97. // Generate syntax tree
  98. var syntaxTree = decompiler.DecompileTypes(sourceFile);
  99. if (progress != null)
  100. {
  101. currentProgress.UnitsCompleted++;
  102. progress.Report(currentProgress);
  103. }
  104. if (!syntaxTree.HasChildren)
  105. continue;
  106. // Generate source and checksum
  107. if (!noLogo)
  108. syntaxTree.InsertChildAfter(null, new Comment(" PDB and source generated by ICSharpCode.Decompiler " + decompilerVersion.FileVersion), Roles.Comment);
  109. var sourceText = SyntaxTreeToString(syntaxTree, settings);
  110. // Generate sequence points for the syntax tree
  111. var sequencePoints = decompiler.CreateSequencePoints(syntaxTree);
  112. // Generate other debug information
  113. var debugInfoGen = new DebugInfoGenerator(decompiler.TypeSystem);
  114. syntaxTree.AcceptVisitor(debugInfoGen);
  115. lock (metadata)
  116. {
  117. var sourceBlob = WriteSourceToBlob(metadata, sourceText, out var sourceCheckSum);
  118. var name = metadata.GetOrAddDocumentName(sourceFile.Key);
  119. // Create Document(Handle)
  120. var document = metadata.AddDocument(name,
  121. hashAlgorithm: metadata.GetOrAddGuid(KnownGuids.HashAlgorithmSHA256),
  122. hash: metadata.GetOrAddBlob(sourceCheckSum),
  123. language: metadata.GetOrAddGuid(KnownGuids.CSharpLanguageGuid));
  124. // Add embedded source to the PDB
  125. customDebugInfo.Add((document,
  126. metadata.GetOrAddGuid(KnownGuids.EmbeddedSource),
  127. sourceBlob));
  128. debugInfoGen.GenerateImportScopes(metadata, globalImportScope);
  129. localScopes.AddRange(debugInfoGen.LocalScopes);
  130. foreach (var function in debugInfoGen.Functions)
  131. {
  132. var method = function.MoveNextMethod ?? function.Method;
  133. var methodHandle = (MethodDefinitionHandle)method.MetadataToken;
  134. sequencePoints.TryGetValue(function, out var points);
  135. ProcessMethod(methodHandle, document, points, syntaxTree);
  136. if (function.MoveNextMethod != null)
  137. {
  138. stateMachineMethods.Add((
  139. (MethodDefinitionHandle)function.MoveNextMethod.MetadataToken,
  140. (MethodDefinitionHandle)function.Method.MetadataToken
  141. ));
  142. customDebugInfo.Add((
  143. function.MoveNextMethod.MetadataToken,
  144. metadata.GetOrAddGuid(KnownGuids.StateMachineHoistedLocalScopes),
  145. metadata.GetOrAddBlob(BuildStateMachineHoistedLocalScopes(function))
  146. ));
  147. }
  148. if (function.IsAsync)
  149. {
  150. customMethodDebugInfo.Add((methodHandle,
  151. metadata.GetOrAddGuid(KnownGuids.MethodSteppingInformation),
  152. metadata.GetOrAddBlob(function.AsyncDebugInfo.BuildBlob(methodHandle))));
  153. }
  154. }
  155. }
  156. }
  157. foreach (var method in reader.MethodDefinitions)
  158. {
  159. var md = reader.GetMethodDefinition(method);
  160. if (sequencePointBlobs.TryGetValue(method, out var info))
  161. {
  162. metadata.AddMethodDebugInformation(info.Document, info.SequencePoints);
  163. }
  164. else
  165. {
  166. metadata.AddMethodDebugInformation(default, default);
  167. }
  168. }
  169. localScopes.Sort((x, y) => {
  170. if (x.Method != y.Method)
  171. {
  172. return MetadataTokens.GetRowNumber(x.Method) - MetadataTokens.GetRowNumber(y.Method);
  173. }
  174. if (x.Offset != y.Offset)
  175. {
  176. return x.Offset - y.Offset;
  177. }
  178. return y.Length - x.Length;
  179. });
  180. foreach (var localScope in localScopes)
  181. {
  182. int nextRow = metadata.GetRowCount(TableIndex.LocalVariable) + 1;
  183. var firstLocalVariable = MetadataTokens.LocalVariableHandle(nextRow);
  184. foreach (var local in localScope.Locals.OrderBy(l => l.Index))
  185. {
  186. var localVarName = local.Name != null ? metadata.GetOrAddString(local.Name) : default;
  187. metadata.AddLocalVariable(LocalVariableAttributes.None, local.Index.Value, localVarName);
  188. }
  189. metadata.AddLocalScope(localScope.Method, localScope.Import.Handle, firstLocalVariable,
  190. default, localScope.Offset, localScope.Length);
  191. }
  192. stateMachineMethods.SortBy(row => MetadataTokens.GetRowNumber(row.MoveNextMethod));
  193. foreach (var row in stateMachineMethods)
  194. {
  195. metadata.AddStateMachineMethod(row.MoveNextMethod, row.KickoffMethod);
  196. }
  197. customMethodDebugInfo.SortBy(row => MetadataTokens.GetRowNumber(row.Parent));
  198. foreach (var row in customMethodDebugInfo)
  199. {
  200. metadata.AddCustomDebugInformation(row.Parent, row.Guid, row.Blob);
  201. }
  202. customDebugInfo.SortBy(row => MetadataTokens.GetRowNumber(row.Parent));
  203. foreach (var row in customDebugInfo)
  204. {
  205. metadata.AddCustomDebugInformation(row.Parent, row.Guid, row.Blob);
  206. }
  207. if (pdbId == null)
  208. {
  209. var debugDir = file.Reader.ReadDebugDirectory().FirstOrDefault(dir => dir.Type == DebugDirectoryEntryType.CodeView);
  210. var portable = file.Reader.ReadCodeViewDebugDirectoryData(debugDir);
  211. pdbId = new BlobContentId(portable.Guid, debugDir.Stamp);
  212. }
  213. PortablePdbBuilder serializer = new PortablePdbBuilder(metadata, GetRowCounts(reader), entrypointHandle, blobs => pdbId.Value);
  214. BlobBuilder blobBuilder = new BlobBuilder();
  215. serializer.Serialize(blobBuilder);
  216. blobBuilder.WriteContentTo(targetStream);
  217. void ProcessMethod(MethodDefinitionHandle method, DocumentHandle document,
  218. List<SequencePoint> sequencePoints, SyntaxTree syntaxTree)
  219. {
  220. var methodDef = reader.GetMethodDefinition(method);
  221. int localSignatureRowId;
  222. MethodBodyBlock methodBody;
  223. if (methodDef.RelativeVirtualAddress != 0)
  224. {
  225. methodBody = file.Reader.GetMethodBody(methodDef.RelativeVirtualAddress);
  226. localSignatureRowId = methodBody.LocalSignature.IsNil ? 0 : MetadataTokens.GetRowNumber(methodBody.LocalSignature);
  227. }
  228. else
  229. {
  230. methodBody = null;
  231. localSignatureRowId = 0;
  232. }
  233. // Check if sequence points were already processed - ILFunction gets defined in C# twice:
  234. // This may happen if a compiler-generated function gets transformed into a lambda expression,
  235. // but its method definition is not removed from the syntax tree.
  236. if (!sequencePointBlobs.ContainsKey(method))
  237. {
  238. if (sequencePoints?.Count > 0)
  239. sequencePointBlobs.Add(method, (document, EncodeSequencePoints(metadata, localSignatureRowId, sequencePoints)));
  240. else
  241. sequencePointBlobs.Add(method, (default, default));
  242. }
  243. else
  244. {
  245. Debug.Assert(false, "Duplicate sequence point definition detected: " + MetadataTokens.GetToken(method).ToString("X8"));
  246. }
  247. }
  248. }
  249. static BlobBuilder BuildStateMachineHoistedLocalScopes(ILFunction function)
  250. {
  251. var builder = new BlobBuilder();
  252. foreach (var variable in function.Variables.Where(v => v.StateMachineField != null).OrderBy(v => MetadataTokens.GetRowNumber(v.StateMachineField.MetadataToken)))
  253. {
  254. builder.WriteUInt32(0);
  255. builder.WriteUInt32((uint)function.CodeSize);
  256. }
  257. return builder;
  258. }
  259. static BlobHandle WriteSourceToBlob(MetadataBuilder metadata, string sourceText, out byte[] sourceCheckSum)
  260. {
  261. var builder = new BlobBuilder();
  262. using (var memory = new MemoryStream())
  263. {
  264. var deflate = new DeflateStream(memory, CompressionLevel.Optimal, leaveOpen: true);
  265. byte[] bytes = Encoding.UTF8.GetBytes(sourceText);
  266. deflate.Write(bytes, 0, bytes.Length);
  267. deflate.Close();
  268. byte[] buffer = memory.ToArray();
  269. builder.WriteInt32(bytes.Length); // compressed
  270. builder.WriteBytes(buffer);
  271. using (var hasher = SHA256.Create())
  272. {
  273. sourceCheckSum = hasher.ComputeHash(bytes);
  274. }
  275. }
  276. return metadata.GetOrAddBlob(builder);
  277. }
  278. static BlobHandle EncodeSequencePoints(MetadataBuilder metadata, int localSignatureRowId, List<SequencePoint> sequencePoints)
  279. {
  280. if (sequencePoints.Count == 0)
  281. return default;
  282. var writer = new BlobBuilder();
  283. // header:
  284. writer.WriteCompressedInteger(localSignatureRowId);
  285. int previousOffset = -1;
  286. int previousStartLine = -1;
  287. int previousStartColumn = -1;
  288. for (int i = 0; i < sequencePoints.Count; i++)
  289. {
  290. var sequencePoint = sequencePoints[i];
  291. // delta IL offset:
  292. if (i > 0)
  293. writer.WriteCompressedInteger(sequencePoint.Offset - previousOffset);
  294. else
  295. writer.WriteCompressedInteger(sequencePoint.Offset);
  296. previousOffset = sequencePoint.Offset;
  297. if (sequencePoint.IsHidden)
  298. {
  299. writer.WriteInt16(0);
  300. continue;
  301. }
  302. int lineDelta = sequencePoint.EndLine - sequencePoint.StartLine;
  303. int columnDelta = sequencePoint.EndColumn - sequencePoint.StartColumn;
  304. writer.WriteCompressedInteger(lineDelta);
  305. if (lineDelta == 0)
  306. {
  307. writer.WriteCompressedInteger(columnDelta);
  308. }
  309. else
  310. {
  311. writer.WriteCompressedSignedInteger(columnDelta);
  312. }
  313. if (previousStartLine < 0)
  314. {
  315. writer.WriteCompressedInteger(sequencePoint.StartLine);
  316. writer.WriteCompressedInteger(sequencePoint.StartColumn);
  317. }
  318. else
  319. {
  320. writer.WriteCompressedSignedInteger(sequencePoint.StartLine - previousStartLine);
  321. writer.WriteCompressedSignedInteger(sequencePoint.StartColumn - previousStartColumn);
  322. }
  323. previousStartLine = sequencePoint.StartLine;
  324. previousStartColumn = sequencePoint.StartColumn;
  325. }
  326. return metadata.GetOrAddBlob(writer);
  327. }
  328. static ImmutableArray<int> GetRowCounts(MetadataReader reader)
  329. {
  330. var builder = ImmutableArray.CreateBuilder<int>(MetadataTokens.TableCount);
  331. for (int i = 0; i < MetadataTokens.TableCount; i++)
  332. {
  333. builder.Add(reader.GetTableRowCount((TableIndex)i));
  334. }
  335. return builder.MoveToImmutable();
  336. }
  337. static string SyntaxTreeToString(SyntaxTree syntaxTree, DecompilerSettings settings)
  338. {
  339. StringWriter w = new StringWriter();
  340. TokenWriter tokenWriter = new TextWriterTokenWriter(w);
  341. tokenWriter = TokenWriter.WrapInWriterThatSetsLocationsInAST(tokenWriter);
  342. syntaxTree.AcceptVisitor(new CSharpOutputVisitor(tokenWriter, settings.CSharpFormattingOptions));
  343. return w.ToString();
  344. }
  345. }
  346. }