// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team // // 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.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; using System.Threading; using ICSharpCode.Decompiler.IL; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.Util; namespace ICSharpCode.Decompiler.Disassembler { /// /// Disassembles a method body. /// public class MethodBodyDisassembler { readonly ITextOutput output; readonly CancellationToken cancellationToken; /// /// Show .try/finally as blocks in IL code; indent loops. /// public bool DetectControlStructure { get; set; } = true; /// /// Show sequence points if debug information is loaded in Cecil. /// public bool ShowSequencePoints { get; set; } IList sequencePoints; int nextSequencePointIndex; // cache info PEFile module; MetadataReader metadata; GenericContext genericContext; DisassemblerSignatureProvider signatureDecoder; public MethodBodyDisassembler(ITextOutput output, CancellationToken cancellationToken) { this.output = output ?? throw new ArgumentNullException(nameof(output)); this.cancellationToken = cancellationToken; } public virtual void Disassemble(PEFile module, MethodDefinitionHandle handle) { this.module = module; metadata = module.Metadata; genericContext = new GenericContext(handle, module); signatureDecoder = new DisassemblerSignatureProvider(module, output); var methodDefinition = metadata.GetMethodDefinition(handle); // start writing IL code output.WriteLine("// Method begins at RVA 0x{0:x4}", methodDefinition.RelativeVirtualAddress); if (methodDefinition.RelativeVirtualAddress == 0) { output.WriteLine("// Code size {0} (0x{0:x})", 0); output.WriteLine(".maxstack {0}", 0); output.WriteLine(); return; } var body = module.Reader.GetMethodBody(methodDefinition.RelativeVirtualAddress); var blob = body.GetILReader(); output.WriteLine("// Code size {0} (0x{0:x})", blob.Length); output.WriteLine(".maxstack {0}", body.MaxStack); var entrypointHandle = MetadataTokens.MethodDefinitionHandle(module.Reader.PEHeaders.CorHeader.EntryPointTokenOrRelativeVirtualAddress); if (handle == entrypointHandle) output.WriteLine(".entrypoint"); DisassembleLocalsBlock(body); output.WriteLine(); sequencePoints = module.DebugInfo?.GetSequencePoints(handle) ?? EmptyList.Instance; nextSequencePointIndex = 0; if (DetectControlStructure && blob.Length > 0) { blob.Reset(); HashSet branchTargets = GetBranchTargets(blob); blob.Reset(); WriteStructureBody(new ILStructure(module, handle, genericContext, body), branchTargets, ref blob); } else { while (blob.RemainingBytes > 0) { WriteInstruction(output, metadata, handle, ref blob); } WriteExceptionHandlers(module, handle, body); } sequencePoints = null; } void DisassembleLocalsBlock(MethodBodyBlock body) { if (body.LocalSignature.IsNil) return; var blob = metadata.GetStandaloneSignature(body.LocalSignature); if (blob.GetKind() != StandaloneSignatureKind.LocalVariables) return; var reader = metadata.GetBlobReader(blob.Signature); if (reader.Length < 2) return; reader.Offset = 1; if (reader.ReadCompressedInteger() == 0) return; var signature = blob.DecodeLocalSignature(signatureDecoder, genericContext); if (!signature.IsEmpty) { output.Write(".locals "); if (body.LocalVariablesInitialized) output.Write("init "); output.WriteLine("("); output.Indent(); int index = 0; foreach (var v in signature) { output.WriteDefinition("[" + index + "] ", v); v(ILNameSyntax.TypeName); if (index + 1 < signature.Length) output.Write(','); output.WriteLine(); index++; } output.Unindent(); output.WriteLine(")"); } } internal void WriteExceptionHandlers(PEFile module, MethodDefinitionHandle handle, MethodBodyBlock body) { this.module = module; metadata = module.Metadata; genericContext = new GenericContext(handle, module); signatureDecoder = new DisassemblerSignatureProvider(module, output); var handlers = body.ExceptionRegions; if (!handlers.IsEmpty) { output.WriteLine(); foreach (var eh in handlers) { eh.WriteTo(module, genericContext, output); output.WriteLine(); } } } HashSet GetBranchTargets(BlobReader blob) { HashSet branchTargets = new HashSet(); while (blob.RemainingBytes > 0) { var opCode = ILParser.DecodeOpCode(ref blob); if (opCode == ILOpCode.Switch) { branchTargets.UnionWith(ILParser.DecodeSwitchTargets(ref blob)); } else if (opCode.IsBranch()) { branchTargets.Add(ILParser.DecodeBranchTarget(ref blob, opCode)); } else { ILParser.SkipOperand(ref blob, opCode); } } return branchTargets; } void WriteStructureHeader(ILStructure s) { switch (s.Type) { case ILStructureType.Loop: output.Write("// loop start"); if (s.LoopEntryPointOffset >= 0) { output.Write(" (head: "); DisassemblerHelpers.WriteOffsetReference(output, s.LoopEntryPointOffset); output.Write(')'); } output.WriteLine(); break; case ILStructureType.Try: output.WriteLine(".try"); output.WriteLine("{"); break; case ILStructureType.Handler: switch (s.ExceptionHandler.Kind) { case ExceptionRegionKind.Catch: case ExceptionRegionKind.Filter: output.Write("catch"); if (!s.ExceptionHandler.CatchType.IsNil) { output.Write(' '); s.ExceptionHandler.CatchType.WriteTo(s.Module, output, s.GenericContext, ILNameSyntax.TypeName); } output.WriteLine(); break; case ExceptionRegionKind.Finally: output.WriteLine("finally"); break; case ExceptionRegionKind.Fault: output.WriteLine("fault"); break; default: throw new NotSupportedException(); } output.WriteLine("{"); break; case ILStructureType.Filter: output.WriteLine("filter"); output.WriteLine("{"); break; default: throw new NotSupportedException(); } output.Indent(); } void WriteStructureBody(ILStructure s, HashSet branchTargets, ref BlobReader body) { bool isFirstInstructionInStructure = true; bool prevInstructionWasBranch = false; int childIndex = 0; while (body.RemainingBytes > 0 && body.Offset < s.EndOffset) { int offset = body.Offset; if (childIndex < s.Children.Count && s.Children[childIndex].StartOffset <= offset && offset < s.Children[childIndex].EndOffset) { ILStructure child = s.Children[childIndex++]; WriteStructureHeader(child); WriteStructureBody(child, branchTargets, ref body); WriteStructureFooter(child); } else { if (!isFirstInstructionInStructure && (prevInstructionWasBranch || branchTargets.Contains(offset))) { output.WriteLine(); // put an empty line after branches, and in front of branch targets } var currentOpCode = ILParser.DecodeOpCode(ref body); body.Offset = offset; // reset IL stream WriteInstruction(output, metadata, s.MethodHandle, ref body); prevInstructionWasBranch = currentOpCode.IsBranch() || currentOpCode.IsReturn() || currentOpCode == ILOpCode.Throw || currentOpCode == ILOpCode.Rethrow || currentOpCode == ILOpCode.Switch; } isFirstInstructionInStructure = false; } } void WriteStructureFooter(ILStructure s) { output.Unindent(); switch (s.Type) { case ILStructureType.Loop: output.WriteLine("// end loop"); break; case ILStructureType.Try: output.WriteLine("} // end .try"); break; case ILStructureType.Handler: output.WriteLine("} // end handler"); break; case ILStructureType.Filter: output.WriteLine("} // end filter"); break; default: throw new NotSupportedException(); } } protected virtual void WriteInstruction(ITextOutput output, MetadataReader metadata, MethodDefinitionHandle methodDefinition, ref BlobReader blob) { int offset = blob.Offset; if (ShowSequencePoints && nextSequencePointIndex < sequencePoints?.Count) { Metadata.SequencePoint sp = sequencePoints[nextSequencePointIndex]; if (sp.Offset <= offset) { output.Write("// sequence point: "); if (sp.Offset != offset) { output.Write("!! at " + DisassemblerHelpers.OffsetToString(sp.Offset) + " !!"); } if (sp.IsHidden) { output.WriteLine("hidden"); } else { output.WriteLine($"(line {sp.StartLine}, col {sp.StartColumn}) to (line {sp.EndLine}, col {sp.EndColumn}) in {sp.DocumentUrl}"); } nextSequencePointIndex++; } } ILOpCode opCode = ILParser.DecodeOpCode(ref blob); output.WriteDefinition(DisassemblerHelpers.OffsetToString(offset), offset); output.Write(": "); if (opCode.IsDefined()) { output.WriteReference(opCode.GetDisplayName(), new OpCodeInfo(opCode, opCode.GetDisplayName())); switch (opCode.GetOperandType()) { case OperandType.BrTarget: case OperandType.ShortBrTarget: output.Write(' '); int targetOffset = ILParser.DecodeBranchTarget(ref blob, opCode); output.WriteReference($"IL_{targetOffset:x4}", targetOffset, true); break; case OperandType.Field: case OperandType.Method: case OperandType.Sig: case OperandType.Type: output.Write(' '); var handle = MetadataTokens.EntityHandle(blob.ReadInt32()); handle.WriteTo(module, output, genericContext); break; case OperandType.Tok: output.Write(' '); handle = MetadataTokens.EntityHandle(blob.ReadInt32()); switch (handle.Kind) { case HandleKind.MemberReference: switch (metadata.GetMemberReference((MemberReferenceHandle)handle).GetKind()) { case MemberReferenceKind.Method: output.Write("method "); break; case MemberReferenceKind.Field: output.Write("field "); break; } break; case HandleKind.FieldDefinition: output.Write("field "); break; } handle.WriteTo(module, output, genericContext); break; case OperandType.ShortI: output.Write(' '); DisassemblerHelpers.WriteOperand(output, blob.ReadSByte()); break; case OperandType.I: output.Write(' '); DisassemblerHelpers.WriteOperand(output, blob.ReadInt32()); break; case OperandType.I8: output.Write(' '); DisassemblerHelpers.WriteOperand(output, blob.ReadInt64()); break; case OperandType.ShortR: output.Write(' '); DisassemblerHelpers.WriteOperand(output, blob.ReadSingle()); break; case OperandType.R: output.Write(' '); DisassemblerHelpers.WriteOperand(output, blob.ReadDouble()); break; case OperandType.String: var userString = metadata.GetUserString(MetadataTokens.UserStringHandle(blob.ReadInt32())); output.Write(' '); DisassemblerHelpers.WriteOperand(output, userString); break; case OperandType.Switch: int[] targets = ILParser.DecodeSwitchTargets(ref blob); output.Write(" ("); for (int i = 0; i < targets.Length; i++) { if (i > 0) output.Write(", "); output.WriteReference($"IL_{targets[i]:x4}", targets[i], true); } output.Write(")"); break; case OperandType.Variable: output.Write(' '); int index = blob.ReadUInt16(); if (opCode == ILOpCode.Ldloc || opCode == ILOpCode.Ldloca || opCode == ILOpCode.Stloc) { DisassemblerHelpers.WriteVariableReference(output, metadata, methodDefinition, index); } else { DisassemblerHelpers.WriteParameterReference(output, metadata, methodDefinition, index); } break; case OperandType.ShortVariable: output.Write(' '); index = blob.ReadByte(); if (opCode == ILOpCode.Ldloc_s || opCode == ILOpCode.Ldloca_s || opCode == ILOpCode.Stloc_s) { DisassemblerHelpers.WriteVariableReference(output, metadata, methodDefinition, index); } else { DisassemblerHelpers.WriteParameterReference(output, metadata, methodDefinition, index); } break; } } else { output.Write($".emitbyte 0x{opCode:x}"); } output.WriteLine(); } } }