
3 changed files with 503 additions and 374 deletions
-
5ILSpy.ReadyToRun/ILSpy.ReadyToRun.csproj
-
432ILSpy.ReadyToRun/ReadyToRunDisassembler.cs
-
440ILSpy.ReadyToRun/ReadyToRunLanguage.cs
@ -0,0 +1,432 @@ |
|||
// 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.Diagnostics; |
|||
using System.Reflection.Metadata.Ecma335; |
|||
|
|||
using Iced.Intel; |
|||
|
|||
using ICSharpCode.Decompiler; |
|||
using ICSharpCode.Decompiler.IL; |
|||
using ICSharpCode.Decompiler.Metadata; |
|||
|
|||
using ILCompiler.Reflection.ReadyToRun; |
|||
using ILCompiler.Reflection.ReadyToRun.Amd64; |
|||
|
|||
namespace ICSharpCode.ILSpy.ReadyToRun |
|||
{ |
|||
internal class ReadyToRunDisassembler |
|||
{ |
|||
private readonly ITextOutput output; |
|||
private readonly ReadyToRunReader reader; |
|||
private readonly RuntimeFunction runtimeFunction; |
|||
|
|||
public ReadyToRunDisassembler(ITextOutput output, ReadyToRunReader reader, RuntimeFunction runtimeFunction) |
|||
{ |
|||
this.output = output; |
|||
this.reader = reader; |
|||
this.runtimeFunction = runtimeFunction; |
|||
} |
|||
|
|||
public void Disassemble(PEFile currentFile, int bitness, ulong address, bool showMetadataTokens, bool showMetadataTokensInBase10) |
|||
{ |
|||
// TODO: Decorate the disassembly with GCInfo
|
|||
ReadyToRunMethod readyToRunMethod = runtimeFunction.Method; |
|||
WriteCommentLine(readyToRunMethod.SignatureString); |
|||
|
|||
Dictionary<ulong, UnwindCode> unwindInfo = null; |
|||
if (ReadyToRunOptions.GetIsShowUnwindInfo(null) && bitness == 64) |
|||
{ |
|||
unwindInfo = WriteUnwindInfo(); |
|||
} |
|||
|
|||
bool isShowDebugInfo = ReadyToRunOptions.GetIsShowDebugInfo(null); |
|||
DebugInfoHelper debugInfo = null; |
|||
if (isShowDebugInfo) |
|||
{ |
|||
debugInfo = WriteDebugInfo(); |
|||
} |
|||
|
|||
byte[] codeBytes = new byte[runtimeFunction.Size]; |
|||
for (int i = 0; i < runtimeFunction.Size; i++) |
|||
{ |
|||
codeBytes[i] = reader.Image[reader.GetOffset(runtimeFunction.StartAddress) + i]; |
|||
} |
|||
|
|||
var codeReader = new ByteArrayCodeReader(codeBytes); |
|||
var decoder = Decoder.Create(bitness, codeReader); |
|||
decoder.IP = address; |
|||
ulong endRip = decoder.IP + (uint)codeBytes.Length; |
|||
|
|||
var instructions = new InstructionList(); |
|||
while (decoder.IP < endRip) |
|||
{ |
|||
decoder.Decode(out instructions.AllocUninitializedElement()); |
|||
} |
|||
|
|||
string disassemblyFormat = ReadyToRunOptions.GetDisassemblyFormat(null); |
|||
Formatter formatter = null; |
|||
if (disassemblyFormat.Equals(ReadyToRunOptions.intel)) |
|||
{ |
|||
formatter = new NasmFormatter(); |
|||
} |
|||
else |
|||
{ |
|||
Debug.Assert(disassemblyFormat.Equals(ReadyToRunOptions.gas)); |
|||
formatter = new GasFormatter(); |
|||
} |
|||
formatter.Options.DigitSeparator = "`"; |
|||
formatter.Options.FirstOperandCharIndex = 10; |
|||
var tempOutput = new StringOutput(); |
|||
ulong baseInstrIP = instructions[0].IP; |
|||
foreach (var instr in instructions) |
|||
{ |
|||
int byteBaseIndex = (int)(instr.IP - address); |
|||
if (isShowDebugInfo && runtimeFunction.DebugInfo != null) |
|||
{ |
|||
foreach (var bound in runtimeFunction.DebugInfo.BoundsList) |
|||
{ |
|||
if (bound.NativeOffset == byteBaseIndex) |
|||
{ |
|||
if (bound.ILOffset == (uint)DebugInfoBoundsType.Prolog) |
|||
{ |
|||
WriteCommentLine("Prolog"); |
|||
} |
|||
else if (bound.ILOffset == (uint)DebugInfoBoundsType.Epilog) |
|||
{ |
|||
WriteCommentLine("Epilog"); |
|||
} |
|||
else |
|||
{ |
|||
WriteCommentLine($"IL_{bound.ILOffset:x4}"); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
formatter.Format(instr, tempOutput); |
|||
output.Write(instr.IP.ToString("X16")); |
|||
output.Write(" "); |
|||
int instrLen = instr.Length; |
|||
for (int i = 0; i < instrLen; i++) |
|||
{ |
|||
output.Write(codeBytes[byteBaseIndex + i].ToString("X2")); |
|||
} |
|||
int missingBytes = 10 - instrLen; |
|||
for (int i = 0; i < missingBytes; i++) |
|||
{ |
|||
output.Write(" "); |
|||
} |
|||
output.Write(" "); |
|||
output.Write(tempOutput.ToStringAndReset()); |
|||
DecorateUnwindInfo(unwindInfo, baseInstrIP, instr); |
|||
DecorateDebugInfo(instr, debugInfo, baseInstrIP); |
|||
|
|||
DecorateCallSite(currentFile, showMetadataTokens, showMetadataTokensInBase10, instr); |
|||
} |
|||
output.WriteLine(); |
|||
} |
|||
|
|||
private void WriteCommentLine(string comment) |
|||
{ |
|||
output.WriteLine("; " + comment); |
|||
} |
|||
|
|||
private class NativeVarInfoRecord |
|||
{ |
|||
public ulong codeOffset; |
|||
public bool isStart; |
|||
public bool isRegRelative; |
|||
public string register; |
|||
public int registerOffset; |
|||
public Variable variable; |
|||
} |
|||
|
|||
private class DebugInfoHelper |
|||
{ |
|||
public List<NativeVarInfoRecord> records; |
|||
public int i; |
|||
public Dictionary<string, Dictionary<int, Variable>> registerRelativeVariables; |
|||
public Dictionary<string, Variable> registerVariables; |
|||
|
|||
public DebugInfoHelper() |
|||
{ |
|||
this.registerRelativeVariables = new Dictionary<string, Dictionary<int, Variable>>(); |
|||
this.registerVariables = new Dictionary<string, Variable>(); |
|||
} |
|||
|
|||
public void Update(ulong codeOffset) |
|||
{ |
|||
while (i < records.Count && records[i].codeOffset == codeOffset) |
|||
{ |
|||
if (records[i].isRegRelative) |
|||
{ |
|||
if (records[i].isStart) |
|||
{ |
|||
Dictionary<int, Variable> offsetToVariableMap; |
|||
if (!this.registerRelativeVariables.TryGetValue(records[i].register, out offsetToVariableMap)) |
|||
{ |
|||
offsetToVariableMap = new Dictionary<int, Variable>(); |
|||
this.registerRelativeVariables.Add(records[i].register, offsetToVariableMap); |
|||
} |
|||
offsetToVariableMap.Add(records[i].registerOffset, records[i].variable); |
|||
} |
|||
else |
|||
{ |
|||
this.registerRelativeVariables[records[i].register].Remove(records[i].registerOffset); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
if (records[i].isStart) |
|||
{ |
|||
this.registerVariables.Add(records[i].register, records[i].variable); |
|||
} |
|||
else |
|||
{ |
|||
this.registerVariables.Remove(records[i].register); |
|||
} |
|||
} |
|||
i++; |
|||
} |
|||
} |
|||
} |
|||
|
|||
private DebugInfoHelper WriteDebugInfo() |
|||
{ |
|||
List<NativeVarInfoRecord> records = new List<NativeVarInfoRecord>(); |
|||
foreach (RuntimeFunction runtimeFunction in runtimeFunction.Method.RuntimeFunctions) |
|||
{ |
|||
DebugInfo debugInfo = runtimeFunction.DebugInfo; |
|||
if (debugInfo != null && debugInfo.BoundsList.Count > 0) |
|||
{ |
|||
for (int i = 0; i < debugInfo.VariablesList.Count; ++i) |
|||
{ |
|||
var varLoc = debugInfo.VariablesList[i]; |
|||
|
|||
switch (varLoc.VariableLocation.VarLocType) |
|||
{ |
|||
case VarLocType.VLT_STK: |
|||
case VarLocType.VLT_STK_BYREF: |
|||
records.Add(new NativeVarInfoRecord { |
|||
codeOffset = varLoc.StartOffset, |
|||
isStart = true, |
|||
isRegRelative = true, |
|||
register = DebugInfo.GetPlatformSpecificRegister(debugInfo.Machine, varLoc.VariableLocation.Data1), |
|||
registerOffset = varLoc.VariableLocation.Data2, |
|||
variable = varLoc.Variable |
|||
}); |
|||
; |
|||
records.Add(new NativeVarInfoRecord { |
|||
codeOffset = varLoc.EndOffset, |
|||
isStart = false, |
|||
isRegRelative = true, |
|||
register = DebugInfo.GetPlatformSpecificRegister(debugInfo.Machine, varLoc.VariableLocation.Data1), |
|||
registerOffset = varLoc.VariableLocation.Data2, |
|||
variable = varLoc.Variable |
|||
}); |
|||
break; |
|||
case VarLocType.VLT_REG: |
|||
records.Add(new NativeVarInfoRecord { |
|||
codeOffset = varLoc.StartOffset, |
|||
isStart = true, |
|||
isRegRelative = false, |
|||
register = DebugInfo.GetPlatformSpecificRegister(debugInfo.Machine, varLoc.VariableLocation.Data1), |
|||
variable = varLoc.Variable |
|||
}); |
|||
records.Add(new NativeVarInfoRecord { |
|||
codeOffset = varLoc.EndOffset, |
|||
isStart = false, |
|||
isRegRelative = false, |
|||
register = DebugInfo.GetPlatformSpecificRegister(debugInfo.Machine, varLoc.VariableLocation.Data1), |
|||
variable = varLoc.Variable |
|||
}); |
|||
break; |
|||
default: |
|||
// TODO
|
|||
break; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
records.Sort((x, y) => { |
|||
if (x.codeOffset < y.codeOffset) |
|||
{ |
|||
return -1; |
|||
} |
|||
else if (x.codeOffset > y.codeOffset) |
|||
{ |
|||
return 1; |
|||
} |
|||
else |
|||
{ |
|||
if (!x.isStart && y.isStart) |
|||
{ |
|||
return -1; |
|||
} |
|||
else if (x.isStart && !y.isStart) |
|||
{ |
|||
return 1; |
|||
} |
|||
else |
|||
{ |
|||
return 0; |
|||
} |
|||
} |
|||
}); |
|||
return new DebugInfoHelper { |
|||
records = records |
|||
}; |
|||
} |
|||
|
|||
private Dictionary<ulong, UnwindCode> WriteUnwindInfo() |
|||
{ |
|||
Dictionary<ulong, UnwindCode> unwindCodes = new Dictionary<ulong, UnwindCode>(); |
|||
if (runtimeFunction.UnwindInfo is UnwindInfo amd64UnwindInfo) |
|||
{ |
|||
string parsedFlags = ""; |
|||
if ((amd64UnwindInfo.Flags & (int)UnwindFlags.UNW_FLAG_EHANDLER) != 0) |
|||
{ |
|||
parsedFlags += " EHANDLER"; |
|||
} |
|||
if ((amd64UnwindInfo.Flags & (int)UnwindFlags.UNW_FLAG_UHANDLER) != 0) |
|||
{ |
|||
parsedFlags += " UHANDLER"; |
|||
} |
|||
if ((amd64UnwindInfo.Flags & (int)UnwindFlags.UNW_FLAG_CHAININFO) != 0) |
|||
{ |
|||
parsedFlags += " CHAININFO"; |
|||
} |
|||
if (parsedFlags.Length == 0) |
|||
{ |
|||
parsedFlags = " NHANDLER"; |
|||
} |
|||
WriteCommentLine($"UnwindInfo:"); |
|||
WriteCommentLine($"Version: {amd64UnwindInfo.Version}"); |
|||
WriteCommentLine($"Flags: 0x{amd64UnwindInfo.Flags:X2}{parsedFlags}"); |
|||
WriteCommentLine($"FrameRegister: {((amd64UnwindInfo.FrameRegister == 0) ? "none" : amd64UnwindInfo.FrameRegister.ToString().ToLower())}"); |
|||
for (int unwindCodeIndex = 0; unwindCodeIndex < amd64UnwindInfo.UnwindCodes.Count; unwindCodeIndex++) |
|||
{ |
|||
unwindCodes.Add((ulong)(amd64UnwindInfo.UnwindCodes[unwindCodeIndex].CodeOffset), amd64UnwindInfo.UnwindCodes[unwindCodeIndex]); |
|||
} |
|||
} |
|||
return unwindCodes; |
|||
} |
|||
|
|||
private void DecorateUnwindInfo(Dictionary<ulong, UnwindCode> unwindInfo, ulong baseInstrIP, Instruction instr) |
|||
{ |
|||
ulong nextInstructionOffset = instr.NextIP - baseInstrIP; |
|||
if (unwindInfo != null && unwindInfo.ContainsKey(nextInstructionOffset)) |
|||
{ |
|||
UnwindCode unwindCode = unwindInfo[nextInstructionOffset]; |
|||
output.Write($" ; {unwindCode.UnwindOp}({unwindCode.OpInfoStr})"); |
|||
} |
|||
} |
|||
|
|||
private void DecorateDebugInfo(Instruction instr, DebugInfoHelper debugRecords, ulong baseInstrIP) |
|||
{ |
|||
if (debugRecords != null) |
|||
{ |
|||
|
|||
InstructionInfoFactory factory = new InstructionInfoFactory(); |
|||
InstructionInfo info = factory.GetInfo(instr); |
|||
ulong codeOffset = instr.IP - baseInstrIP; |
|||
debugRecords.Update(codeOffset); |
|||
Variable variable = null; |
|||
foreach (UsedMemory usedMemInfo in info.GetUsedMemory()) |
|||
{ |
|||
string baseRegister = usedMemInfo.Base.ToString(); |
|||
int displacement; |
|||
unchecked |
|||
{ displacement = (int)usedMemInfo.Displacement; } |
|||
Dictionary<int, Variable> offsetToVariableMap; |
|||
if (debugRecords.registerRelativeVariables.TryGetValue(usedMemInfo.Base.ToString(), out offsetToVariableMap)) |
|||
{ |
|||
if (offsetToVariableMap.TryGetValue(displacement, out variable)) |
|||
{ |
|||
output.Write($"; [{usedMemInfo.Base.ToString().ToLower()}{(displacement < 0 ? '-' : '+')}{Math.Abs(displacement):X}h] = {variable.Type} {variable.Index}"); |
|||
} |
|||
} |
|||
} |
|||
foreach (UsedRegister usedMemInfo in info.GetUsedRegisters()) |
|||
{ |
|||
if (debugRecords.registerVariables.TryGetValue(usedMemInfo.Register.ToString(), out variable)) |
|||
{ |
|||
output.Write($"; {usedMemInfo.Register.ToString().ToLower()} = {variable.Type} {variable.Index}"); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void DecorateCallSite(PEFile currentFile, bool showMetadataTokens, bool showMetadataTokensInBase10, Instruction instr) |
|||
{ |
|||
if (instr.IsCallNearIndirect) |
|||
{ |
|||
int importCellAddress = (int)instr.IPRelativeMemoryAddress; |
|||
if (reader.ImportSignatures.ContainsKey(importCellAddress)) |
|||
{ |
|||
output.Write(" ; "); |
|||
ReadyToRunSignature signature = reader.ImportSignatures[importCellAddress]; |
|||
switch (signature) |
|||
{ |
|||
case MethodDefEntrySignature methodDefSignature: |
|||
var methodDefToken = MetadataTokens.EntityHandle(unchecked((int)methodDefSignature.MethodDefToken)); |
|||
if (showMetadataTokens) |
|||
{ |
|||
if (showMetadataTokensInBase10) |
|||
{ |
|||
output.WriteReference(currentFile, methodDefToken, $"({MetadataTokens.GetToken(methodDefToken)}) ", "metadata"); |
|||
} |
|||
else |
|||
{ |
|||
output.WriteReference(currentFile, methodDefToken, $"({MetadataTokens.GetToken(methodDefToken):X8}) ", "metadata"); |
|||
} |
|||
} |
|||
methodDefToken.WriteTo(currentFile, output, Decompiler.Metadata.GenericContext.Empty); |
|||
break; |
|||
case MethodRefEntrySignature methodRefSignature: |
|||
var methodRefToken = MetadataTokens.EntityHandle(unchecked((int)methodRefSignature.MethodRefToken)); |
|||
if (showMetadataTokens) |
|||
{ |
|||
if (showMetadataTokensInBase10) |
|||
{ |
|||
output.WriteReference(currentFile, methodRefToken, $"({MetadataTokens.GetToken(methodRefToken)}) ", "metadata"); |
|||
} |
|||
else |
|||
{ |
|||
output.WriteReference(currentFile, methodRefToken, $"({MetadataTokens.GetToken(methodRefToken):X8}) ", "metadata"); |
|||
} |
|||
} |
|||
methodRefToken.WriteTo(currentFile, output, Decompiler.Metadata.GenericContext.Empty); |
|||
break; |
|||
default: |
|||
output.WriteLine(reader.ImportSignatures[importCellAddress].ToString(new SignatureFormattingOptions())); |
|||
break; |
|||
} |
|||
output.WriteLine(); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
output.WriteLine(); |
|||
} |
|||
} |
|||
} |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue