mirror of https://github.com/icsharpcode/ILSpy.git
4 changed files with 295 additions and 0 deletions
@ -0,0 +1,231 @@ |
|||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Collections.Immutable; |
||||||
|
using System.Diagnostics; |
||||||
|
using System.IO; |
||||||
|
using System.Linq; |
||||||
|
using System.Reflection.Metadata; |
||||||
|
using System.Reflection.Metadata.Ecma335; |
||||||
|
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.Pdb |
||||||
|
{ |
||||||
|
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); |
||||||
|
|
||||||
|
|
||||||
|
public static void WritePdb(PEFile file, CSharpDecompiler decompiler, DecompilerSettings settings, Stream targetStream) |
||||||
|
{ |
||||||
|
MetadataBuilder metadata = new MetadataBuilder(); |
||||||
|
MetadataReader reader = file.GetMetadataReader(); |
||||||
|
var entrypointHandle = MetadataTokens.MethodDefinitionHandle(file.Reader.PEHeaders.CorHeader.EntryPointTokenOrRelativeVirtualAddress); |
||||||
|
|
||||||
|
var hasher = SHA256.Create(); |
||||||
|
var sequencePointBlobs = new Dictionary<MethodDefinitionHandle, (DocumentHandle Document, BlobHandle SequencePoints)>(); |
||||||
|
var importScopeBlobs = new Dictionary<MethodDefinitionHandle, (DocumentHandle Document, BlobHandle ImportScope)>(); |
||||||
|
var emptyList = new List<Metadata.SequencePoint>(); |
||||||
|
|
||||||
|
foreach (var handle in reader.GetTopLevelTypeDefinitions()) { |
||||||
|
var type = reader.GetTypeDefinition(handle); |
||||||
|
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 sourceCheckSum = hasher.ComputeHash(Encoding.UTF8.GetBytes(sourceText)); |
||||||
|
var sourceBlob = WriteSourceToBlob(metadata, sourceText); |
||||||
|
|
||||||
|
var document = metadata.AddDocument(name, |
||||||
|
hashAlgorithm: metadata.GetOrAddGuid(HashAlgorithmSHA256), |
||||||
|
hash: metadata.GetOrAddBlob(sourceCheckSum), |
||||||
|
language: metadata.GetOrAddGuid(CSharpLanguageGuid)); |
||||||
|
|
||||||
|
metadata.AddCustomDebugInformation(document, metadata.GetOrAddGuid(DebugInfoEmbeddedSource), sourceBlob); |
||||||
|
|
||||||
|
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.Compilation))); |
||||||
|
} |
||||||
|
|
||||||
|
foreach (var nestedTypeHandle in type.GetNestedTypes()) { |
||||||
|
var nestedType = reader.GetTypeDefinition(nestedTypeHandle); |
||||||
|
|
||||||
|
foreach (var method in nestedType.GetMethods()) { |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
foreach (var method in reader.MethodDefinitions) { |
||||||
|
if (sequencePointBlobs.TryGetValue(method, out var info)) { |
||||||
|
metadata.AddMethodDebugInformation(info.Document, info.SequencePoints); |
||||||
|
//metadata.AddMethodDebugInformation(default, default);
|
||||||
|
} else { |
||||||
|
metadata.AddMethodDebugInformation(default, default); |
||||||
|
} |
||||||
|
if (importScopeBlobs.TryGetValue(method, out var scopeInfo)) { |
||||||
|
//metadata.AddImportScope(default, scopeInfo.ImportScope);
|
||||||
|
metadata.AddMethodDebugInformation(default, default); |
||||||
|
} else { |
||||||
|
metadata.AddImportScope(default, default); |
||||||
|
} |
||||||
|
} |
||||||
|
var debugDir = file.Reader.ReadDebugDirectory().FirstOrDefault(dir => dir.IsPortableCodeView); |
||||||
|
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); |
||||||
|
} |
||||||
|
|
||||||
|
static BlobHandle WriteSourceToBlob(MetadataBuilder metadata, string sourceText) |
||||||
|
{ |
||||||
|
var builder = new BlobBuilder(); |
||||||
|
builder.WriteInt32(0); // uncompressed
|
||||||
|
builder.WriteUTF8(sourceText); |
||||||
|
|
||||||
|
return metadata.GetOrAddBlob(builder); |
||||||
|
} |
||||||
|
|
||||||
|
static BlobHandle EncodeImportScope(MetadataBuilder metadata, MetadataReader reader, SyntaxTree ast, ICompilation compilation) |
||||||
|
{ |
||||||
|
var scope = ast.Annotation<UsingScope>()?.Resolve(compilation); |
||||||
|
|
||||||
|
if (scope == null) |
||||||
|
return default; |
||||||
|
|
||||||
|
Dictionary<IAssembly, AssemblyReferenceHandle> assemblyReferences = new Dictionary<IAssembly, AssemblyReferenceHandle>(); |
||||||
|
|
||||||
|
var writer = new BlobBuilder(); |
||||||
|
|
||||||
|
foreach (var import in scope.Usings) { |
||||||
|
foreach (var asm in import.ContributingAssemblies) { |
||||||
|
if (asm == compilation.MainAssembly) { |
||||||
|
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<Metadata.SequencePoint> 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<int> GetRowCounts(MetadataReader reader) |
||||||
|
{ |
||||||
|
var builder = ImmutableArray.CreateBuilder<int>(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(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,62 @@ |
|||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Diagnostics; |
||||||
|
using System.IO; |
||||||
|
using System.Linq; |
||||||
|
using System.Text; |
||||||
|
using System.Threading.Tasks; |
||||||
|
using ICSharpCode.Decompiler; |
||||||
|
using ICSharpCode.Decompiler.CSharp; |
||||||
|
using ICSharpCode.Decompiler.Pdb; |
||||||
|
using ICSharpCode.ILSpy.TextView; |
||||||
|
using Microsoft.Win32; |
||||||
|
|
||||||
|
namespace ICSharpCode.ILSpy.TreeNodes |
||||||
|
{ |
||||||
|
[ExportContextMenuEntry(Header = "Generate portable PDB")] |
||||||
|
class GeneratePdbContextMenuEntry : IContextMenuEntry |
||||||
|
{ |
||||||
|
public void Execute(TextViewContext context) |
||||||
|
{ |
||||||
|
var language = MainWindow.Instance.CurrentLanguage; |
||||||
|
var assembly = (context.SelectedTreeNodes?.FirstOrDefault() as AssemblyTreeNode)?.LoadedAssembly; |
||||||
|
if (assembly == null) return; |
||||||
|
SaveFileDialog dlg = new SaveFileDialog(); |
||||||
|
dlg.FileName = DecompilerTextView.CleanUpName(assembly.ShortName) + ".pdb"; |
||||||
|
dlg.Filter = "Portable PDB|*.pdb|All files|*.*"; |
||||||
|
if (dlg.ShowDialog() != true) return; |
||||||
|
DecompilationOptions options = new DecompilationOptions(); |
||||||
|
string fileName = dlg.FileName; |
||||||
|
MainWindow.Instance.TextView.RunWithCancellation(ct => Task<AvalonEditTextOutput>.Factory.StartNew(() => { |
||||||
|
AvalonEditTextOutput output = new AvalonEditTextOutput(); |
||||||
|
Stopwatch stopwatch = Stopwatch.StartNew(); |
||||||
|
using (FileStream stream = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Write)) { |
||||||
|
try { |
||||||
|
var file = assembly.GetPEFileOrNull(); |
||||||
|
var decompiler = new CSharpDecompiler(file, options.DecompilerSettings); |
||||||
|
PortablePdbWriter.WritePdb(file, decompiler, options.DecompilerSettings, stream); |
||||||
|
} catch (OperationCanceledException) { |
||||||
|
output.WriteLine(); |
||||||
|
output.WriteLine("Generation was cancelled."); |
||||||
|
throw; |
||||||
|
} |
||||||
|
} |
||||||
|
stopwatch.Stop(); |
||||||
|
output.WriteLine("Generation complete in " + stopwatch.Elapsed.TotalSeconds.ToString("F1") + " seconds."); |
||||||
|
output.WriteLine(); |
||||||
|
output.AddButton(null, "Open Explorer", delegate { Process.Start("explorer", "/select,\"" + fileName + "\""); }); |
||||||
|
output.WriteLine(); |
||||||
|
return output; |
||||||
|
}, ct)).Then(output => MainWindow.Instance.TextView.ShowText(output)).HandleExceptions(); |
||||||
|
} |
||||||
|
|
||||||
|
public bool IsEnabled(TextViewContext context) => true; |
||||||
|
|
||||||
|
public bool IsVisible(TextViewContext context) |
||||||
|
{ |
||||||
|
return context.SelectedTreeNodes?.Length == 1 |
||||||
|
&& context.SelectedTreeNodes?.FirstOrDefault() is AssemblyTreeNode tn |
||||||
|
&& !tn.LoadedAssembly.HasLoadError; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue