mirror of https://github.com/icsharpcode/ILSpy.git
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.
639 lines
20 KiB
639 lines
20 KiB
// 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.Collections.Immutable; |
|
using System.Reflection.Metadata; |
|
using System.Reflection.Metadata.Ecma335; |
|
using System.Threading; |
|
|
|
using ICSharpCode.Decompiler.DebugInfo; |
|
using ICSharpCode.Decompiler.IL; |
|
using ICSharpCode.Decompiler.Metadata; |
|
using ICSharpCode.Decompiler.Util; |
|
|
|
namespace ICSharpCode.Decompiler.Disassembler |
|
{ |
|
/// <summary> |
|
/// Disassembles a method body. |
|
/// </summary> |
|
public class MethodBodyDisassembler |
|
{ |
|
readonly ITextOutput output; |
|
readonly CancellationToken cancellationToken; |
|
|
|
/// <summary> |
|
/// Show .try/finally as blocks in IL code; indent loops. |
|
/// </summary> |
|
public bool DetectControlStructure { get; set; } = true; |
|
|
|
/// <summary> |
|
/// Show sequence points if debug information is loaded in Cecil. |
|
/// </summary> |
|
public bool ShowSequencePoints { get; set; } |
|
|
|
/// <summary> |
|
/// Show metadata tokens for instructions with token operands. |
|
/// </summary> |
|
public bool ShowMetadataTokens { get; set; } |
|
|
|
/// <summary> |
|
/// Show metadata tokens for instructions with token operands in base 10. |
|
/// </summary> |
|
public bool ShowMetadataTokensInBase10 { get; set; } |
|
|
|
/// <summary> |
|
/// Show raw RVA offset and bytes before each instruction. |
|
/// </summary> |
|
public bool ShowRawRVAOffsetAndBytes { get; set; } |
|
|
|
/// <summary> |
|
/// Optional provider for sequence points. |
|
/// </summary> |
|
public IDebugInfoProvider DebugInfo { get; set; } |
|
|
|
IList<DebugInfo.SequencePoint> sequencePoints; |
|
int nextSequencePointIndex; |
|
|
|
// cache info |
|
MetadataFile module; |
|
MetadataReader metadata; |
|
MetadataGenericContext genericContext; |
|
DisassemblerSignatureTypeProvider signatureDecoder; |
|
|
|
public MethodBodyDisassembler(ITextOutput output, CancellationToken cancellationToken) |
|
{ |
|
this.output = output ?? throw new ArgumentNullException(nameof(output)); |
|
this.cancellationToken = cancellationToken; |
|
} |
|
|
|
public virtual void Disassemble(MetadataFile module, MethodDefinitionHandle handle) |
|
{ |
|
this.module = module ?? throw new ArgumentNullException(nameof(module)); |
|
metadata = module.Metadata; |
|
genericContext = new MetadataGenericContext(handle, module); |
|
signatureDecoder = new DisassemblerSignatureTypeProvider(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("// Header size: {0}", 0); |
|
output.WriteLine("// Code size: {0} (0x{0:x})", 0); |
|
output.WriteLine(".maxstack {0}", 0); |
|
output.WriteLine(); |
|
return; |
|
} |
|
MethodBodyBlock body; |
|
BlobReader bodyBlockReader; |
|
try |
|
{ |
|
body = module.GetMethodBody(methodDefinition.RelativeVirtualAddress); |
|
bodyBlockReader = module.GetSectionData(methodDefinition.RelativeVirtualAddress).GetReader(); |
|
} |
|
catch (BadImageFormatException ex) |
|
{ |
|
output.WriteLine("// {0}", ex.Message); |
|
return; |
|
} |
|
var blob = body.GetILReader(); |
|
int headerSize = ILParser.GetHeaderSize(bodyBlockReader); |
|
output.WriteLine("// Header size: {0}", headerSize); |
|
output.WriteLine("// Code size: {0} (0x{0:x})", blob.Length); |
|
output.WriteLine(".maxstack {0}", body.MaxStack); |
|
|
|
var entrypointHandle = MetadataTokens.MethodDefinitionHandle(module.CorHeader?.EntryPointTokenOrRelativeVirtualAddress ?? 0); |
|
if (handle == entrypointHandle) |
|
output.WriteLine(".entrypoint"); |
|
|
|
DisassembleLocalsBlock(handle, body); |
|
output.WriteLine(); |
|
|
|
sequencePoints = DebugInfo?.GetSequencePoints(handle) ?? EmptyList<DebugInfo.SequencePoint>.Instance; |
|
nextSequencePointIndex = 0; |
|
if (DetectControlStructure && blob.Length > 0) |
|
{ |
|
blob.Reset(); |
|
BitSet branchTargets = new(blob.Length); |
|
ILParser.SetBranchTargets(ref blob, branchTargets); |
|
blob.Reset(); |
|
WriteStructureBody(new ILStructure(module, handle, genericContext, body), branchTargets, ref blob, methodDefinition.RelativeVirtualAddress + headerSize); |
|
} |
|
else |
|
{ |
|
while (blob.RemainingBytes > 0) |
|
{ |
|
cancellationToken.ThrowIfCancellationRequested(); |
|
WriteInstruction(output, module, handle, ref blob, methodDefinition.RelativeVirtualAddress); |
|
} |
|
WriteExceptionHandlers(module, handle, body); |
|
} |
|
sequencePoints = null; |
|
} |
|
|
|
void DisassembleLocalsBlock(MethodDefinitionHandle method, MethodBodyBlock body) |
|
{ |
|
if (body.LocalSignature.IsNil) |
|
return; |
|
output.Write(".locals"); |
|
WriteMetadataToken(body.LocalSignature, spaceBefore: true); |
|
if (body.LocalVariablesInitialized) |
|
output.Write(" init"); |
|
var blob = metadata.GetStandaloneSignature(body.LocalSignature); |
|
var signature = ImmutableArray<Action<ILNameSyntax>>.Empty; |
|
try |
|
{ |
|
if (blob.GetKind() == StandaloneSignatureKind.LocalVariables) |
|
{ |
|
signature = blob.DecodeLocalSignature(signatureDecoder, genericContext); |
|
} |
|
else |
|
{ |
|
output.Write(" /* wrong signature kind */"); |
|
} |
|
} |
|
catch (BadImageFormatException ex) |
|
{ |
|
output.Write($" /* {ex.Message} */"); |
|
} |
|
output.Write(' '); |
|
output.WriteLine("("); |
|
output.Indent(); |
|
int index = 0; |
|
foreach (var v in signature) |
|
{ |
|
output.WriteLocalReference("[" + index + "]", "loc_" + index, isDefinition: true); |
|
output.Write(' '); |
|
v(ILNameSyntax.TypeName); |
|
if (DebugInfo != null && DebugInfo.TryGetName(method, index, out var name)) |
|
{ |
|
output.Write(" " + DisassemblerHelpers.Escape(name)); |
|
} |
|
if (index + 1 < signature.Length) |
|
output.Write(','); |
|
output.WriteLine(); |
|
index++; |
|
} |
|
output.Unindent(); |
|
output.WriteLine(")"); |
|
} |
|
|
|
internal void WriteExceptionHandlers(MetadataFile module, MethodDefinitionHandle handle, MethodBodyBlock body) |
|
{ |
|
this.module = module; |
|
metadata = module.Metadata; |
|
genericContext = new MetadataGenericContext(handle, module); |
|
signatureDecoder = new DisassemblerSignatureTypeProvider(module, output); |
|
var handlers = body.ExceptionRegions; |
|
if (!handlers.IsEmpty) |
|
{ |
|
output.WriteLine(); |
|
foreach (var eh in handlers) |
|
{ |
|
eh.WriteTo(module, genericContext, output); |
|
output.WriteLine(); |
|
} |
|
} |
|
} |
|
|
|
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.Filter: |
|
// handler block of filter block has no header |
|
break; |
|
case ExceptionRegionKind.Catch: |
|
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 ArgumentOutOfRangeException(); |
|
} |
|
output.WriteLine("{"); |
|
break; |
|
case ILStructureType.Filter: |
|
output.WriteLine("filter"); |
|
output.WriteLine("{"); |
|
break; |
|
default: |
|
throw new ArgumentOutOfRangeException(); |
|
} |
|
output.Indent(); |
|
} |
|
|
|
void WriteStructureBody(ILStructure s, BitSet branchTargets, ref BlobReader body, int methodRva) |
|
{ |
|
bool isFirstInstructionInStructure = true; |
|
bool prevInstructionWasBranch = false; |
|
int childIndex = 0; |
|
while (body.RemainingBytes > 0 && body.Offset < s.EndOffset) |
|
{ |
|
cancellationToken.ThrowIfCancellationRequested(); |
|
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, methodRva); |
|
WriteStructureFooter(child); |
|
} |
|
else |
|
{ |
|
if (!isFirstInstructionInStructure && (prevInstructionWasBranch || branchTargets[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, module, s.MethodHandle, ref body, methodRva); |
|
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 ArgumentOutOfRangeException(); |
|
} |
|
} |
|
|
|
protected virtual void WriteInstruction(ITextOutput output, MetadataFile metadataFile, MethodDefinitionHandle methodHandle, ref BlobReader blob, int methodRva) |
|
{ |
|
var metadata = metadataFile.Metadata; |
|
int offset = blob.Offset; |
|
if (ShowSequencePoints && nextSequencePointIndex < sequencePoints?.Count) |
|
{ |
|
var 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); |
|
OperandType opType = opCode.GetOperandType(); |
|
if (opCode.IsDefined() && opType.OperandSize() <= blob.RemainingBytes) |
|
{ |
|
WriteRVA(blob, offset + methodRva, opCode); |
|
output.WriteLocalReference(DisassemblerHelpers.OffsetToString(offset), offset, isDefinition: true); |
|
output.Write(": "); |
|
WriteOpCode(opCode); |
|
switch (opCode.GetOperandType()) |
|
{ |
|
case OperandType.BrTarget: |
|
case OperandType.ShortBrTarget: |
|
output.Write(' '); |
|
int targetOffset = ILParser.DecodeBranchTarget(ref blob, opCode); |
|
output.WriteLocalReference($"IL_{targetOffset:x4}", targetOffset); |
|
break; |
|
case OperandType.Field: |
|
case OperandType.Method: |
|
case OperandType.Sig: |
|
case OperandType.Type: |
|
output.Write(' '); |
|
int metadataToken = blob.ReadInt32(); |
|
EntityHandle? handle = MetadataTokenHelpers.TryAsEntityHandle(metadataToken); |
|
try |
|
{ |
|
handle?.WriteTo(module, output, genericContext); |
|
} |
|
catch (BadImageFormatException) |
|
{ |
|
handle = null; |
|
} |
|
WriteMetadataToken(handle, metadataToken, spaceBefore: true); |
|
break; |
|
case OperandType.Tok: |
|
output.Write(' '); |
|
metadataToken = blob.ReadInt32(); |
|
handle = MetadataTokenHelpers.TryAsEntityHandle(metadataToken); |
|
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; |
|
case HandleKind.MethodDefinition: |
|
output.Write("method "); |
|
break; |
|
} |
|
try |
|
{ |
|
handle?.WriteTo(module, output, genericContext); |
|
} |
|
catch (BadImageFormatException) |
|
{ |
|
handle = null; |
|
} |
|
WriteMetadataToken(handle, metadataToken, spaceBefore: true); |
|
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: |
|
metadataToken = blob.ReadInt32(); |
|
output.Write(' '); |
|
UserStringHandle? userString; |
|
string text; |
|
try |
|
{ |
|
userString = MetadataTokens.UserStringHandle(metadataToken); |
|
text = metadata.GetUserString(userString.Value); |
|
} |
|
catch (BadImageFormatException) |
|
{ |
|
userString = null; |
|
text = null; |
|
} |
|
if (userString != null) |
|
{ |
|
DisassemblerHelpers.WriteOperand(output, text); |
|
} |
|
WriteMetadataToken(userString, metadataToken, spaceBefore: true); |
|
break; |
|
case OperandType.Switch: |
|
var tmp = blob; |
|
int[] targets = ILParser.DecodeSwitchTargets(ref blob); |
|
if (ShowRawRVAOffsetAndBytes) |
|
{ |
|
output.WriteLine(" ("); |
|
} |
|
else |
|
{ |
|
output.Write(" ("); |
|
} |
|
tmp.ReadInt32(); |
|
for (int i = 0; i < targets.Length; i++) |
|
{ |
|
if (i > 0) |
|
{ |
|
if (ShowRawRVAOffsetAndBytes) |
|
{ |
|
output.WriteLine(","); |
|
} |
|
else |
|
{ |
|
output.Write(", "); |
|
} |
|
} |
|
if (ShowRawRVAOffsetAndBytes) |
|
{ |
|
output.Write("/* "); |
|
output.Write($"{tmp.ReadByte():X2}{tmp.ReadByte():X2}{tmp.ReadByte():X2}{tmp.ReadByte():X2}"); |
|
output.Write(" */ "); |
|
} |
|
if (ShowRawRVAOffsetAndBytes) |
|
{ |
|
output.Write(" "); |
|
} |
|
output.WriteLocalReference($"IL_{targets[i]:x4}", targets[i]); |
|
} |
|
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, methodHandle, index); |
|
} |
|
else |
|
{ |
|
DisassemblerHelpers.WriteParameterReference(output, metadata, methodHandle, 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, methodHandle, index); |
|
} |
|
else |
|
{ |
|
DisassemblerHelpers.WriteParameterReference(output, metadata, methodHandle, index); |
|
} |
|
break; |
|
} |
|
} |
|
else |
|
{ |
|
ushort opCodeValue = (ushort)opCode; |
|
if (opCodeValue > 0xFF) |
|
{ |
|
if (ShowRawRVAOffsetAndBytes) |
|
{ |
|
output.Write("/* "); |
|
output.Write($"0x{offset + methodRva:X8} {(ushort)opCode >> 8:X2}"); |
|
output.Write(" */ "); |
|
} |
|
output.WriteLocalReference(DisassemblerHelpers.OffsetToString(offset), offset, isDefinition: true); |
|
output.Write(": "); |
|
// split 16-bit value into two emitbyte directives |
|
output.WriteLine($".emitbyte 0x{(byte)(opCodeValue >> 8):x}"); |
|
if (ShowRawRVAOffsetAndBytes) |
|
{ |
|
output.Write("/* "); |
|
output.Write($"0x{offset + methodRva + 1:X8} {(ushort)opCode & 0xFF:X2}"); |
|
output.Write(" */ "); |
|
} |
|
// add label |
|
output.WriteLocalReference(DisassemblerHelpers.OffsetToString(offset + 1), offset + 1, isDefinition: true); |
|
output.Write(": "); |
|
output.Write($".emitbyte 0x{(byte)(opCodeValue & 0xFF):x}"); |
|
} |
|
else |
|
{ |
|
if (ShowRawRVAOffsetAndBytes) |
|
{ |
|
output.Write("/* "); |
|
output.Write($"0x{offset + methodRva:X8} {(ushort)opCode & 0xFF:X2}"); |
|
output.Write(" */ "); |
|
} |
|
output.WriteLocalReference(DisassemblerHelpers.OffsetToString(offset), offset, isDefinition: true); |
|
output.Write(": "); |
|
output.Write($".emitbyte 0x{(byte)opCodeValue:x}"); |
|
} |
|
} |
|
output.WriteLine(); |
|
} |
|
|
|
void WriteRVA(BlobReader blob, int offset, ILOpCode opCode) |
|
{ |
|
if (ShowRawRVAOffsetAndBytes) |
|
{ |
|
output.Write("/* "); |
|
var tmp = blob; |
|
if (opCode == ILOpCode.Switch) |
|
{ |
|
tmp.ReadInt32(); |
|
} |
|
else |
|
{ |
|
ILParser.SkipOperand(ref tmp, opCode); |
|
} |
|
output.Write($"0x{offset:X8} {(ushort)opCode:X2}"); |
|
int appendSpaces = (ushort)opCode > 0xFF ? 14 : 16; |
|
while (blob.Offset < tmp.Offset) |
|
{ |
|
output.Write($"{blob.ReadByte():X2}"); |
|
appendSpaces -= 2; |
|
} |
|
if (appendSpaces > 0) |
|
{ |
|
output.Write(new string(' ', appendSpaces)); |
|
} |
|
output.Write(" */ "); |
|
} |
|
} |
|
|
|
private void WriteOpCode(ILOpCode opCode) |
|
{ |
|
var opCodeInfo = new OpCodeInfo(opCode, opCode.GetDisplayName()); |
|
string index; |
|
switch (opCode) |
|
{ |
|
case ILOpCode.Ldarg_0: |
|
case ILOpCode.Ldarg_1: |
|
case ILOpCode.Ldarg_2: |
|
case ILOpCode.Ldarg_3: |
|
output.WriteReference(opCodeInfo, omitSuffix: true); |
|
index = opCodeInfo.Name.Substring(6); |
|
output.WriteLocalReference(index, "param_" + index); |
|
break; |
|
case ILOpCode.Ldloc_0: |
|
case ILOpCode.Ldloc_1: |
|
case ILOpCode.Ldloc_2: |
|
case ILOpCode.Ldloc_3: |
|
case ILOpCode.Stloc_0: |
|
case ILOpCode.Stloc_1: |
|
case ILOpCode.Stloc_2: |
|
case ILOpCode.Stloc_3: |
|
output.WriteReference(opCodeInfo, omitSuffix: true); |
|
index = opCodeInfo.Name.Substring(6); |
|
output.WriteLocalReference(index, "loc_" + index); |
|
break; |
|
default: |
|
output.WriteReference(opCodeInfo); |
|
break; |
|
} |
|
} |
|
|
|
private void WriteMetadataToken(EntityHandle handle, bool spaceBefore) |
|
{ |
|
WriteMetadataToken(handle, MetadataTokens.GetToken(handle), spaceBefore); |
|
} |
|
|
|
private void WriteMetadataToken(Handle? handle, int metadataToken, bool spaceBefore) |
|
{ |
|
ReflectionDisassembler.WriteMetadataToken(output, module, handle, metadataToken, |
|
spaceAfter: false, spaceBefore, ShowMetadataTokens, ShowMetadataTokensInBase10); |
|
} |
|
} |
|
}
|
|
|