/// <summary> /// Replace relative offset in the disassembled instruction with the true target RVA. /// </summary> /// <param name="instruction">Disassembled instruction to modify</param> /// <param name="target">Target string to replace offset with</param> /// <param name="rtf">Runtime function being disassembled</param> private void ReplaceRelativeOffset(ref string instruction, int target, RuntimeFunction rtf) { int outputOffset = target; if (_options.Naked) { outputOffset -= rtf.StartAddress; } ReplaceRelativeOffset(ref instruction, string.Format("0x{0:X4}", outputOffset), rtf); }
/// <summary> /// Dumps disassembly and register liveness /// </summary> internal override void DumpDisasm(RuntimeFunction rtf, int imageOffset) { int indent = (_options.Naked ? _options.HideOffsets ? 4 : 11 : 32); string indentString = new string(' ', indent); int rtfOffset = 0; int codeOffset = rtf.CodeOffset; while (rtfOffset < rtf.Size) { string instr; int instrSize = _disassembler.GetInstruction(rtf, imageOffset, rtfOffset, out instr); if (_r2r.Machine == Machine.Amd64 && ((ILCompiler.Reflection.ReadyToRun.Amd64.UnwindInfo)rtf.UnwindInfo).UnwindCodes.ContainsKey(codeOffset)) { List <ILCompiler.Reflection.ReadyToRun.Amd64.UnwindCode> codes = ((ILCompiler.Reflection.ReadyToRun.Amd64.UnwindInfo)rtf.UnwindInfo).UnwindCodes[codeOffset]; foreach (ILCompiler.Reflection.ReadyToRun.Amd64.UnwindCode code in codes) { _writer.Write($"{indentString}{code.UnwindOp} {code.OpInfoStr}"); if (code.NextFrameOffset != -1) { _writer.WriteLine($"{indentString}{code.NextFrameOffset}"); } _writer.WriteLine(); } } if (!_options.HideTransitions && rtf.Method.GcInfo?.Transitions != null && rtf.Method.GcInfo.Transitions.TryGetValue(codeOffset, out List <BaseGcTransition> transitionsForOffset)) { string[] formattedTransitions = new string[transitionsForOffset.Count]; for (int transitionIndex = 0; transitionIndex < formattedTransitions.Length; transitionIndex++) { formattedTransitions[transitionIndex] = transitionsForOffset[transitionIndex].ToString(); } if (_options.Normalize) { Array.Sort(formattedTransitions); } foreach (string transition in formattedTransitions) { _writer.WriteLine($"{indentString}{transition}"); } } /* According to https://msdn.microsoft.com/en-us/library/ck9asaa9.aspx and src/vm/gcinfodecoder.cpp * UnwindCode and GcTransition CodeOffsets are encoded with a -1 adjustment (that is, it's the offset of the start of the next instruction) */ _writer.Write(instr); rtfOffset += instrSize; codeOffset += instrSize; } }
/// <summary> /// Get the RVAs of the runtime functions for each method /// </summary> private void ParseRuntimeFunctions(bool[] isEntryPoint, int runtimeFunctionOffset, int runtimeFunctionSize) { int curOffset = 0; foreach (R2RMethod method in R2RMethods) { int runtimeFunctionId = method.EntryPointRuntimeFunctionId; if (runtimeFunctionId == -1) { continue; } curOffset = runtimeFunctionOffset + runtimeFunctionId * runtimeFunctionSize; GcInfo gcInfo = null; int codeOffset = 0; do { int startRva = NativeReader.ReadInt32(Image, ref curOffset); int endRva = -1; if (Machine == Machine.Amd64) { endRva = NativeReader.ReadInt32(Image, ref curOffset); } int unwindRva = NativeReader.ReadInt32(Image, ref curOffset); int unwindOffset = GetOffset(unwindRva); BaseUnwindInfo unwindInfo = null; if (Machine == Machine.Amd64) { unwindInfo = new Amd64.UnwindInfo(Image, unwindOffset); if (isEntryPoint[runtimeFunctionId]) { gcInfo = new GcInfo(Image, unwindOffset + unwindInfo.Size, Machine, R2RHeader.MajorVersion); } } else if (Machine == Machine.I386) { unwindInfo = new x86.UnwindInfo(Image, unwindOffset); if (isEntryPoint[runtimeFunctionId]) { //gcInfo = new GcInfo(Image, unwindOffset + unwindInfo.Size, Machine, R2RHeader.MajorVersion); } } RuntimeFunction rtf = new RuntimeFunction(runtimeFunctionId, startRva, endRva, unwindRva, codeOffset, method, unwindInfo, gcInfo); method.RuntimeFunctions.Add(rtf); runtimeFunctionId++; codeOffset += rtf.Size; }while (runtimeFunctionId < isEntryPoint.Length && !isEntryPoint[runtimeFunctionId]); } }
/// <summary> /// Check whether a given target RVA corresponds to another runtime function within the same method. /// </summary> /// <param name="rva">Target RVA to analyze</param> /// <param name="rtf">Runtime function being disassembled</param> /// <param name="runtimeFunctionIndex">Output runtime function index if found, -1 otherwise</param> /// <returns>true if target runtime function has been found, false otherwise</returns> private bool IsAnotherRuntimeFunctionWithinMethod(int rva, RuntimeFunction rtf, out int runtimeFunctionIndex) { for (int rtfIndex = 0; rtfIndex < rtf.Method.RuntimeFunctions.Count; rtfIndex++) { if (rva == rtf.Method.RuntimeFunctions[rtfIndex].StartAddress) { runtimeFunctionIndex = rtfIndex; return(true); } } runtimeFunctionIndex = -1; return(false); }
public unsafe static int GetInstruction(IntPtr Disasm, RuntimeFunction rtf, int imageOffset, int rtfOffset, byte[] image, out string instr) { int instrSize = 1; fixed(byte *p = image) { IntPtr ptr = (IntPtr)(p + imageOffset + rtfOffset); instrSize = DumpInstruction(Disasm, (ulong)(rtf.StartAddress + rtfOffset), ptr, rtf.Size); } IntPtr pBuffer = GetOutputBuffer(); instr = Marshal.PtrToStringAnsi(pBuffer); return(instrSize); }
/// <summary> /// Dumps one runtime function. /// </summary> private void DumpRuntimeFunction(R2RReader r2r, RuntimeFunction rtf) { if (_disasm) { _writer.WriteLine($"Id: {rtf.Id}"); CoreDisTools.DumpCodeBlock(_disassembler, rtf.StartAddress, r2r.GetOffset(rtf.StartAddress), r2r.Image, rtf.Size); } else { _writer.Write($"{rtf}"); } if (_raw) { DumpBytes(r2r, rtf.StartAddress, (uint)rtf.Size); } _writer.WriteLine(); }
/// <summary> /// X86 disassembler has a bug in decoding absolute indirections, mistaking them for RIP-relative indirections /// </summary> /// <param name="rtf">Runtime function</param> /// <param name="imageOffset">Offset within the image byte array</param> /// <param name="rtfOffset">Offset within the runtime function</param> /// <param name="instrSize">Instruction size</param> /// <param name="instruction">Textual representation of the instruction</param> private void ProbeX86Quirks(RuntimeFunction rtf, int imageOffset, int rtfOffset, int instrSize, ref string instruction) { int leftBracket; int rightBracketPlusOne; int absoluteAddress; if (TryParseRipRelative(instruction, out leftBracket, out rightBracketPlusOne, out absoluteAddress)) { int target = absoluteAddress - (int)_reader.ImageBase; StringBuilder translated = new StringBuilder(); translated.Append(instruction, 0, leftBracket); _reader.ImportCellNames.TryGetValue(target, out string targetName); if (_options.Naked) { if (targetName != null) { translated.AppendFormat("[{0}]", targetName); } else { translated.AppendFormat("[0x{0:x4}]", target); } translated.Append(instruction, rightBracketPlusOne, instruction.Length - rightBracketPlusOne); } else { translated.AppendFormat("[0x{0:x4}]", target); translated.Append(instruction, rightBracketPlusOne, instruction.Length - rightBracketPlusOne); if (targetName != null) { AppendComment(translated, targetName); } } translated.Append(instruction, rightBracketPlusOne, instruction.Length - rightBracketPlusOne); instruction = translated.ToString(); } else { ProbeCommonIntelQuirks(rtf, imageOffset, rtfOffset, instrSize, ref instruction); } }
/// <summary> /// Dumps disassembly and register liveness /// </summary> internal override void DumpDisasm(RuntimeFunction rtf, int imageOffset) { int indent = (_options.Naked ? 11 : 32); string indentString = new string(' ', indent); int rtfOffset = 0; int codeOffset = rtf.CodeOffset; while (rtfOffset < rtf.Size) { string instr; int instrSize = _disassembler.GetInstruction(rtf, imageOffset, rtfOffset, out instr); if (_r2r.Machine == Machine.Amd64 && ((ILCompiler.Reflection.ReadyToRun.Amd64.UnwindInfo)rtf.UnwindInfo).UnwindCodes.ContainsKey(codeOffset)) { List <ILCompiler.Reflection.ReadyToRun.Amd64.UnwindCode> codes = ((ILCompiler.Reflection.ReadyToRun.Amd64.UnwindInfo)rtf.UnwindInfo).UnwindCodes[codeOffset]; foreach (ILCompiler.Reflection.ReadyToRun.Amd64.UnwindCode code in codes) { _writer.Write($"{indentString}{code.UnwindOp} {code.OpInfoStr}"); if (code.NextFrameOffset != -1) { _writer.WriteLine($"{indentString}{code.NextFrameOffset}"); } _writer.WriteLine(); } } if (rtf.Method.GcInfo?.Transitions != null && rtf.Method.GcInfo.Transitions.ContainsKey(codeOffset)) { foreach (BaseGcTransition transition in rtf.Method.GcInfo.Transitions[codeOffset]) { _writer.WriteLine($"{indentString}{transition}"); } } /* According to https://msdn.microsoft.com/en-us/library/ck9asaa9.aspx and src/vm/gcinfodecoder.cpp * UnwindCode and GcTransition CodeOffsets are encoded with a -1 adjustment (that is, it's the offset of the start of the next instruction) */ _writer.Write(instr); CoreDisTools.ClearOutputBuffer(); rtfOffset += instrSize; codeOffset += instrSize; } }
/// <summary> /// Improves disassembler output for ARM64. /// </summary> /// <param name="rtf">Runtime function</param> /// <param name="imageOffset">Offset within the image byte array</param> /// <param name="rtfOffset">Offset within the runtime function</param> /// <param name="instruction">Textual representation of the instruction</param> private void ProbeArm64Quirks(RuntimeFunction rtf, int imageOffset, int rtfOffset, ref string instruction) { const int InstructionSize = 4; // The list of PC-relative instructions: ADR, ADRP, B.cond, B, BL, CBNZ, CBZ, TBNZ, TBZ. // Handle an ADR instruction if (IsArm64AdrInstruction(imageOffset + rtfOffset, out int adrOffset)) { ReplaceRelativeOffset(ref instruction, rtf.StartAddress + rtfOffset + adrOffset, rtf); } // Handle the ADRP instruction of an ADRP+ADD pair else if (IsArm64AdrpInstruction(imageOffset + rtfOffset, out uint adrpRegister, out long pageOffset)) { int pc = rtf.StartAddress + rtfOffset; long targetPage = (pc & ~0xfff) + pageOffset; if ((0 <= targetPage) && (targetPage <= int.MaxValue) && IsArm64AddImmediate64NoShiftInstruction(imageOffset + rtfOffset + InstructionSize, out uint addSrcRegister, out uint offset) && (addSrcRegister == adrpRegister)) { int target = (int)targetPage + (int)offset; _addInstructionOffset = imageOffset + rtfOffset + 4; _addInstructionTarget = target; int hashPos = instruction.LastIndexOf('#'); var translated = new StringBuilder(); translated.Append(instruction, 0, hashPos); _reader.ImportCellNames.TryGetValue(target, out string targetName); if (_options.Naked && (targetName != null)) { translated.Append("import_hi21{").Append(targetName).Append('}'); } else { translated.AppendFormat("#0x{0:x4}", targetPage); } instruction = translated.ToString(); } }
// <summary> /// For each query in the list of queries, dump a runtime function by id. /// The method containing the runtime function gets outputted, along with the single runtime function that was searched /// </summary> /// <param name="r2r">Contains all the extracted info about the ReadyToRun image</param> /// <param name="queries">The ids to search for</param> private void QueryRuntimeFunction(ReadyToRunReader r2r, IEnumerable <string> queries) { if (queries.Any()) { _dumper.WriteDivider("Runtime Functions"); } foreach (string q in queries) { RuntimeFunction rtf = FindRuntimeFunction(r2r, ArgStringToInt(q)); if (rtf == null) { WriteWarning("Unable to find by id " + q); continue; } _dumper.DumpQueryCount(q.ToString(), "Runtime Function", 1); _dumper.DumpRuntimeFunction(rtf); } }
// <summary> /// For each query in the list of queries, search for a runtime function by id. /// The method containing the runtime function gets outputted, along with the single runtime function that was searched /// </summary> /// <param name="r2r">Contains all the extracted info about the ReadyToRun image</param> /// <param name="queries">The ids to search for</param> private void QueryRuntimeFunction(R2RReader r2r, IReadOnlyList <int> queries) { if (queries.Count > 0) { _dumper.WriteDivider("Runtime Functions"); } foreach (int q in queries) { RuntimeFunction rtf = FindRuntimeFunction(r2r, q); if (rtf == null) { WriteWarning("Unable to find by id " + q); continue; } XmlNode queryNode = _dumper.DumpQueryCount(q.ToString(), "Runtime Function", 1); _dumper.DumpRuntimeFunction(rtf, queryNode); } }
// <summary> /// For each query in the list of queries, search for a runtime function by id. /// The method containing the runtime function gets outputted, along with the single runtime function that was searched /// </summary> /// <param name="r2r">Contains all the extracted info about the ReadyToRun image</param> /// <param name="queries">The ids to search for</param> private void QueryRuntimeFunction(R2RReader r2r, IReadOnlyList <int> queries) { if (queries.Count > 0) { WriteDivider("Runtime Functions"); } foreach (int q in queries) { RuntimeFunction rtf = FindRuntimeFunction(r2r, q); if (rtf == null) { WriteWarning("Unable to find by id " + q); continue; } _writer.WriteLine(rtf.Method.SignatureString); DumpRuntimeFunction(r2r, rtf); } }
/// <summary> /// Translate RIP-relative offsets to RVA's and convert cell addresses to symbol names /// </summary> /// <param name="rtf">Runtime function</param> /// <param name="imageOffset">Offset within the image byte array</param> /// <param name="rtfOffset">Offset within the runtime function</param> /// <param name="instrSize">Instruction size</param> /// <param name="instruction">Textual representation of the instruction</param> private void ProbeX64Quirks(RuntimeFunction rtf, int imageOffset, int rtfOffset, int instrSize, ref string instruction) { int leftBracket; int rightBracketPlusOne; int displacement; if (TryParseRipRelative(instruction, out leftBracket, out rightBracketPlusOne, out displacement)) { int target = rtf.StartAddress + rtfOffset + instrSize + displacement; int newline = instruction.LastIndexOf('\n'); StringBuilder translated = new StringBuilder(); translated.Append(instruction, 0, leftBracket); if (_options.Naked) { String targetName; if (_reader.ImportCellNames.TryGetValue(target, out targetName)) { translated.AppendFormat("[{0}]", targetName); } else { translated.AppendFormat("[0x{0:x4}]", target); } } else { translated.AppendFormat("[0x{0:x4}]", target); AppendImportCellName(translated, target); } translated.Append(instruction, rightBracketPlusOne, newline - rightBracketPlusOne); translated.Append(instruction, newline, instruction.Length - newline); instruction = translated.ToString(); } else { ProbeCommonIntelQuirks(rtf, imageOffset, rtfOffset, instrSize, ref instruction); } }
internal unsafe override void DumpDisasm(IntPtr Disasm, RuntimeFunction rtf, int imageOffset, byte[] image, XmlNode parentNode = null) { int rtfOffset = 0; int codeOffset = rtf.CodeOffset; while (rtfOffset < rtf.Size) { string instr; int instrSize = CoreDisTools.GetInstruction(Disasm, rtf, imageOffset, rtfOffset, image, out instr); _writer.Write(instr); if (rtf.Method.GcInfo != null && rtf.Method.GcInfo.Transitions.ContainsKey(codeOffset)) { _writer.WriteLine($"\t\t\t\t{rtf.Method.GcInfo.Transitions[codeOffset].GetSlotState(rtf.Method.GcInfo.SlotTable)}"); } CoreDisTools.ClearOutputBuffer(); rtfOffset += instrSize; codeOffset += instrSize; } }
/// <summary> /// Probe quirks that have the same behavior for X86 and X64. /// </summary> /// <param name="rtf">Runtime function</param> /// <param name="imageOffset">Offset within the image byte array</param> /// <param name="rtfOffset">Offset within the runtime function</param> /// <param name="instrSize">Instruction size</param> /// <param name="instruction">Textual representation of the instruction</param> private void ProbeCommonIntelQuirks(RuntimeFunction rtf, int imageOffset, int rtfOffset, int instrSize, ref string instruction) { if (instrSize == 2 && IsIntelJumpInstructionWithByteOffset(imageOffset + rtfOffset)) { sbyte offset = (sbyte)_reader.Image[imageOffset + rtfOffset + 1]; int target = rtf.StartAddress + rtfOffset + instrSize + offset; ReplaceRelativeOffset(ref instruction, target, rtf); } else if (instrSize == 5 && IsIntel1ByteJumpInstructionWithIntOffset(imageOffset + rtfOffset)) { int offset = BitConverter.ToInt32(_reader.Image, imageOffset + rtfOffset + 1); int target = rtf.StartAddress + rtfOffset + instrSize + offset; ReplaceRelativeOffset(ref instruction, target, rtf); } else if (instrSize == 6 && IsIntel2ByteJumpInstructionWithIntOffset(imageOffset + rtfOffset)) { int offset = BitConverter.ToInt32(_reader.Image, imageOffset + rtfOffset + 2); int target = rtf.StartAddress + rtfOffset + instrSize + offset; ReplaceRelativeOffset(ref instruction, target, rtf); } }
/// <summary> /// Dumps disassembly and register liveness /// </summary> internal override void DumpDisasm(RuntimeFunction rtf, int imageOffset, XmlNode parentNode = null) { int rtfOffset = 0; int codeOffset = rtf.CodeOffset; while (rtfOffset < rtf.Size) { string instr; int instrSize = _disassembler.GetInstruction(rtf, imageOffset, rtfOffset, out instr); _writer.Write(instr); if (_r2r.Machine == Machine.Amd64 && ((Amd64.UnwindInfo)rtf.UnwindInfo).UnwindCodes.ContainsKey(codeOffset)) { List <Amd64.UnwindCode> codes = ((Amd64.UnwindInfo)rtf.UnwindInfo).UnwindCodes[codeOffset]; foreach (Amd64.UnwindCode code in codes) { _writer.Write($"\t\t\t\t{code.UnwindOp} {code.OpInfoStr}"); if (code.NextFrameOffset != -1) { _writer.WriteLine($" - {code.NextFrameOffset}"); } _writer.WriteLine(); } } if (rtf.Method.GcInfo != null && rtf.Method.GcInfo.Transitions.ContainsKey(codeOffset)) { foreach (BaseGcTransition transition in rtf.Method.GcInfo.Transitions[codeOffset]) { _writer.WriteLine($"\t\t\t\t{transition.ToString()}"); } } CoreDisTools.ClearOutputBuffer(); rtfOffset += instrSize; codeOffset += instrSize; } }
internal unsafe override void DumpDisasm(IntPtr Disasm, RuntimeFunction rtf, int imageOffset, byte[] image, XmlNode parentNode) { int rtfOffset = 0; int codeOffset = rtf.CodeOffset; Dictionary <int, GcInfo.GcTransition> transitions = rtf.Method.GcInfo.Transitions; GcSlotTable slotTable = rtf.Method.GcInfo.SlotTable; while (rtfOffset < rtf.Size) { string instr; int instrSize = CoreDisTools.GetInstruction(Disasm, rtf, imageOffset, rtfOffset, image, out instr); AddXMLNode("offset" + codeOffset, instr, parentNode, $"{codeOffset}"); if (transitions.ContainsKey(codeOffset)) { AddXMLNode("Transition", transitions[codeOffset].GetSlotState(slotTable), parentNode, $"{codeOffset}"); } CoreDisTools.ClearOutputBuffer(); rtfOffset += instrSize; codeOffset += instrSize; } }
/// <summary> /// Parse a single instruction and return the RVA of the next instruction. /// </summary> /// <param name="rtf">Runtime function to parse</param> /// <param name="imageOffset">Offset within the PE image byte array</param> /// <param name="rtfOffset">Instruction offset within the runtime function</param> /// <param name="instruction">Output text representation of the instruction</param> /// <returns>Instruction size in bytes - i.o.w. the next instruction starts at rtfOffset + (the return value)</returns> public int GetInstruction(RuntimeFunction rtf, int imageOffset, int rtfOffset, out string instruction) { if (_disasm == IntPtr.Zero) { instruction = ""; return(rtf.Size); } int instrSize = CoreDisTools.GetInstruction(_disasm, rtf, imageOffset, rtfOffset, _reader.Image, out instruction); instruction = instruction.Replace('\t', ' '); switch (_reader.Machine) { case Machine.Amd64: case Machine.IA64: ProbeX64Quirks(rtf, imageOffset, rtfOffset, instrSize, ref instruction); break; case Machine.I386: ProbeX86Quirks(rtf, imageOffset, rtfOffset, instrSize, ref instruction); break; case Machine.ArmThumb2: case Machine.Thumb: break; case Machine.Arm64: break; default: throw new NotImplementedException(); } instruction = instruction.Replace("\n", Environment.NewLine); return(instrSize); }
/// <summary> /// Dumps disassembly and register liveness /// </summary> internal override void DumpDisasm(RuntimeFunction rtf, int imageOffset, XmlNode parentNode = null) { int rtfOffset = 0; int codeOffset = rtf.CodeOffset; while (rtfOffset < rtf.Size) { string instr; int instrSize = _disassembler.GetInstruction(rtf, imageOffset, rtfOffset, out instr); _writer.Write(instr); if (rtf.Method.GcInfo != null && rtf.Method.GcInfo.Transitions.ContainsKey(codeOffset)) { foreach (BaseGcTransition transition in rtf.Method.GcInfo.Transitions[codeOffset]) { _writer.WriteLine($"\t\t\t\t{transition.ToString()}"); } } CoreDisTools.ClearOutputBuffer(); rtfOffset += instrSize; codeOffset += instrSize; } }
/// <summary> /// Dumps one runtime function. /// </summary> private void DumpRuntimeFunction(R2RReader r2r, RuntimeFunction rtf) { _writer.Write($"{rtf}"); if (_disasm) { _writer.Write(CoreDisTools.GetCodeBlock(_disassembler, rtf, r2r.GetOffset(rtf.StartAddress), r2r.Image)); } if (_raw) { _writer.WriteLine("Raw Bytes:"); DumpBytes(r2r, rtf.StartAddress, (uint)rtf.Size); } if (_unwind) { _writer.WriteLine("UnwindInfo:"); _writer.Write(rtf.UnwindInfo); if (_raw) { DumpBytes(r2r, rtf.UnwindRVA, (uint)rtf.UnwindInfo.Size); } } _writer.WriteLine(); }
abstract internal unsafe void DumpDisasm(IntPtr Disasm, RuntimeFunction rtf, int imageOffset, byte[] image, XmlNode parentNode = null);
abstract internal void DumpRuntimeFunction(RuntimeFunction rtf, XmlNode parentNode = null);
/// <summary> /// Get the RVAs of the runtime functions for each method /// based on <a href="https://github.com/dotnet/coreclr/blob/master/src/zap/zapcode.cpp">ZapUnwindInfo::Save</a> /// </summary> private void ParseRuntimeFunctions(bool[] isEntryPoint, int runtimeFunctionOffset, int runtimeFunctionSize) { int curOffset = 0; foreach (R2RMethod method in R2RMethods) { int runtimeFunctionId = method.EntryPointRuntimeFunctionId; if (runtimeFunctionId == -1) { continue; } curOffset = runtimeFunctionOffset + runtimeFunctionId * runtimeFunctionSize; BaseGcInfo gcInfo = null; int codeOffset = 0; do { int startRva = NativeReader.ReadInt32(Image, ref curOffset); int endRva = -1; if (Machine == Machine.Amd64) { endRva = NativeReader.ReadInt32(Image, ref curOffset); } int unwindRva = NativeReader.ReadInt32(Image, ref curOffset); int unwindOffset = GetOffset(unwindRva); BaseUnwindInfo unwindInfo = null; if (Machine == Machine.Amd64) { unwindInfo = new Amd64.UnwindInfo(Image, unwindOffset); if (isEntryPoint[runtimeFunctionId]) { gcInfo = new Amd64.GcInfo(Image, unwindOffset + unwindInfo.Size, Machine, R2RHeader.MajorVersion); } } else if (Machine == Machine.I386) { unwindInfo = new x86.UnwindInfo(Image, unwindOffset); if (isEntryPoint[runtimeFunctionId]) { gcInfo = new x86.GcInfo(Image, unwindOffset, Machine, R2RHeader.MajorVersion); } } else if (Machine == Machine.ArmThumb2) { unwindInfo = new Arm.UnwindInfo(Image, unwindOffset); if (isEntryPoint[runtimeFunctionId]) { gcInfo = new Amd64.GcInfo(Image, unwindOffset + unwindInfo.Size, Machine, R2RHeader.MajorVersion); // Arm and Arm64 use the same GcInfo format as x64 } } else if (Machine == Machine.Arm64) { unwindInfo = new Arm64.UnwindInfo(Image, unwindOffset); if (isEntryPoint[runtimeFunctionId]) { gcInfo = new Amd64.GcInfo(Image, unwindOffset + unwindInfo.Size, Machine, R2RHeader.MajorVersion); } } EHInfo ehInfo = null; EHInfoLocation ehInfoLocation; if (EHLookupTable != null && EHLookupTable.RuntimeFunctionToEHInfoMap.TryGetValue(startRva, out ehInfoLocation)) { ehInfo = new EHInfo(this, ehInfoLocation.EHInfoRVA, startRva, GetOffset(ehInfoLocation.EHInfoRVA), ehInfoLocation.ClauseCount); } RuntimeFunction rtf = new RuntimeFunction( runtimeFunctionId, startRva, endRva, unwindRva, codeOffset, method, unwindInfo, gcInfo, ehInfo, _runtimeFunctionToDebugInfo.GetValueOrDefault(runtimeFunctionId)); method.RuntimeFunctions.Add(rtf); runtimeFunctionId++; codeOffset += rtf.Size; }while (runtimeFunctionId < isEntryPoint.Length && !isEntryPoint[runtimeFunctionId]); } }
/// <summary> /// Parse and dump a single instruction and return its size in bytes. /// </summary> /// <param name="rtf">Runtime function to parse</param> /// <param name="imageOffset">Offset within the PE image byte array</param> /// <param name="rtfOffset">Instruction offset within the runtime function</param> /// <param name="instruction">Output text representation of the instruction</param> /// <returns>Instruction size in bytes - i.o.w. the next instruction starts at rtfOffset + (the return value)</returns> public int GetInstruction(RuntimeFunction rtf, int imageOffset, int rtfOffset, out string instruction) { if (_disasm == IntPtr.Zero) { instruction = ""; return(rtf.Size); } int instrSize = CoreDisTools.GetInstruction(_disasm, rtf, imageOffset, rtfOffset, _reader.Image, out instruction); // CoreDisTools dumps instructions in the following format: // // address: bytes [padding] \t mnemonic [\t operands] \n // // However, due to an LLVM issue regarding instruction prefixes (https://bugs.llvm.org/show_bug.cgi?id=7709), // multiple lines may be returned for a single x86/x64 instruction. var builder = new StringBuilder(); int lineNum = 0; // The start index of the last line in builder int lineStartIndex = 0; // Remove this foreach wrapper and line* variables after the aforementioned LLVM issue is fixed foreach (string line in instruction.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries)) { int colonIndex = line.IndexOf(':'); int tab1Index = line.IndexOf('\t'); if ((0 < colonIndex) && (colonIndex < tab1Index)) { // First handle the address and the byte dump if (_options.Naked) { if (!_options.HideOffsets) { // All lines but the last one must represent single-byte prefixes, so add lineNum to the offset builder.Append($"{rtf.CodeOffset + rtfOffset + lineNum,8:x4}:"); } } else { if (_reader.Machine == Machine.Arm64) { // Replace " hh hh hh hh " byte dump with " hhhhhhhh ". // CoreDisTools should be fixed to dump bytes this way for ARM64. uint instructionBytes = BitConverter.ToUInt32(_reader.Image, imageOffset + rtfOffset); builder.Append(line, 0, colonIndex + 1); builder.Append(' '); builder.Append(instructionBytes.ToString("x8")); } else { // Copy the offset and the byte dump int byteDumpEndIndex = tab1Index; do { byteDumpEndIndex--; }while (line[byteDumpEndIndex] == ' '); builder.Append(line, 0, byteDumpEndIndex + 1); } builder.Append(' '); } // Now handle the mnemonic and operands. Ensure proper indentation for the mnemonic. EnsureIndentation(builder, lineStartIndex, MnemonicIndentation); int tab2Index = line.IndexOf('\t', tab1Index + 1); if (tab2Index >= 0) { // Copy everything between the first and the second tabs builder.Append(line, tab1Index + 1, tab2Index - tab1Index - 1); // Ensure proper indentation for the operands EnsureIndentation(builder, lineStartIndex, OperandsIndentation); int afterTab2Index = tab2Index + 1; // Work around an LLVM issue causing an extra space to be output before operands; // see https://reviews.llvm.org/D35946. if ((afterTab2Index < line.Length) && ((line[afterTab2Index] == ' ') || (line[afterTab2Index] == '\t'))) { afterTab2Index++; } // Copy everything after the second tab int savedLength = builder.Length; builder.Append(line, afterTab2Index, line.Length - afterTab2Index); // There should be no extra tabs. Should we encounter them, replace them with a single space. if (line.IndexOf('\t', afterTab2Index) >= 0) { builder.Replace('\t', ' ', savedLength, builder.Length - savedLength); } } else { // Copy everything after the first tab builder.Append(line, tab1Index + 1, line.Length - tab1Index - 1); } } else { // Should not happen. Just replace tabs with spaces. builder.Append(line.Replace('\t', ' ')); } string translatedLine = builder.ToString(lineStartIndex, builder.Length - lineStartIndex); string fixedTranslatedLine = translatedLine; switch (_reader.Machine) { case Machine.Amd64: ProbeX64Quirks(rtf, imageOffset, rtfOffset, instrSize, ref fixedTranslatedLine); break; case Machine.I386: ProbeX86Quirks(rtf, imageOffset, rtfOffset, instrSize, ref fixedTranslatedLine); break; case Machine.Arm64: ProbeArm64Quirks(rtf, imageOffset, rtfOffset, ref fixedTranslatedLine); break; case Machine.ArmThumb2: break; default: break; } // If the translated line has been changed, replace it in the builder if (!object.ReferenceEquals(fixedTranslatedLine, translatedLine)) { builder.Length = lineStartIndex; builder.Append(fixedTranslatedLine); } builder.Append(Environment.NewLine); lineNum++; lineStartIndex = builder.Length; } instruction = builder.ToString(); return(instrSize); }
abstract internal void DumpDisasm(RuntimeFunction rtf, int imageOffset, XmlNode parentNode = null);
abstract internal void DumpDisasm(RuntimeFunction rtf, int imageOffset);
abstract internal void DumpRuntimeFunction(RuntimeFunction rtf);
/// <summary> /// Parse a single instruction and return the RVA of the next instruction. /// </summary> /// <param name="rtf">Runtime function to parse</param> /// <param name="imageOffset">Offset within the PE image byte array</param> /// <param name="rtfOffset">Instruction offset within the runtime function</param> /// <param name="instruction">Output text representation of the instruction</param> /// <returns>Instruction size in bytes - i.o.w. the next instruction starts at rtfOffset + (the return value)</returns> public int GetInstruction(RuntimeFunction rtf, int imageOffset, int rtfOffset, out string instruction) { if (_disasm == IntPtr.Zero) { instruction = ""; return(rtf.Size); } int instrSize = CoreDisTools.GetInstruction(_disasm, rtf, imageOffset, rtfOffset, _reader.Image, out instruction); CoreDisTools.ClearOutputBuffer(); instruction = instruction.Replace('\t', ' '); if (_options.Naked) { StringBuilder nakedInstruction = new StringBuilder(); foreach (string line in instruction.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries)) { int colon = line.IndexOf(':'); if (colon >= 0) { colon += 2; while (colon + 3 <= line.Length && IsXDigit(line[colon]) && IsXDigit(line[colon + 1]) && line[colon + 2] == ' ') { colon += 3; } if (!_options.HideOffsets) { nakedInstruction.Append($"{(rtfOffset + rtf.CodeOffset),8:x4}:"); nakedInstruction.Append(" "); } else { nakedInstruction.Append(" "); } nakedInstruction.Append(line.Substring(colon).TrimStart()); nakedInstruction.Append('\n'); } else { nakedInstruction.Append(' ', 7); nakedInstruction.Append(line.TrimStart()); nakedInstruction.Append('\n'); } } instruction = nakedInstruction.ToString(); } switch (_reader.Machine) { case Machine.Amd64: ProbeX64Quirks(rtf, imageOffset, rtfOffset, instrSize, ref instruction); break; case Machine.I386: ProbeX86Quirks(rtf, imageOffset, rtfOffset, instrSize, ref instruction); break; case Machine.ArmThumb2: case Machine.Thumb: break; case Machine.Arm64: break; default: throw new NotImplementedException(); } instruction = instruction.Replace("\n", Environment.NewLine); return(instrSize); }
/// <summary> /// Replace relative offset in the disassembled instruction with an arbitrary string. /// </summary> /// <param name="instruction">Disassembled instruction to modify</param> /// <param name="replacementString">String to replace offset with</param> /// <param name="rtf">Runtime function being disassembled</param> private void ReplaceRelativeOffset(ref string instruction, string replacementString, RuntimeFunction rtf) { int numberEnd = instruction.IndexOf('\n'); int number = numberEnd; while (number > 0) { char c = instruction[number - 1]; if (c >= ' ' && !Char.IsDigit(c) && c != '-') { break; } number--; } StringBuilder translated = new StringBuilder(); translated.Append(instruction, 0, number); translated.Append(replacementString); translated.Append(instruction, numberEnd, instruction.Length - numberEnd); instruction = translated.ToString(); }
/// <summary> /// Probe quirks that have the same behavior for X86 and X64. /// </summary> /// <param name="rtf">Runtime function</param> /// <param name="imageOffset">Offset within the image byte array</param> /// <param name="rtfOffset">Offset within the runtime function</param> /// <param name="instrSize">Instruction size</param> /// <param name="instruction">Textual representation of the instruction</param> private void ProbeCommonIntelQuirks(RuntimeFunction rtf, int imageOffset, int rtfOffset, int instrSize, ref string instruction) { int instructionRVA = rtf.StartAddress + rtfOffset; int nextInstructionRVA = instructionRVA + instrSize; if (instrSize == 2 && IsIntelJumpInstructionWithByteOffset(imageOffset + rtfOffset)) { sbyte offset = (sbyte)_reader.Image[imageOffset + rtfOffset + 1]; ReplaceRelativeOffset(ref instruction, nextInstructionRVA + offset, rtf); } else if (instrSize == 5 && IsIntel1ByteJumpInstructionWithIntOffset(imageOffset + rtfOffset)) { int offset = BitConverter.ToInt32(_reader.Image, imageOffset + rtfOffset + 1); ReplaceRelativeOffset(ref instruction, nextInstructionRVA + offset, rtf); } else if (instrSize == 5 && IsIntelCallInstructionWithIntOffset(imageOffset + rtfOffset)) { int offset = BitConverter.ToInt32(_reader.Image, imageOffset + rtfOffset + 1); int targetRVA = nextInstructionRVA + offset; int targetImageOffset = _reader.GetOffset(targetRVA); bool pointsOutsideRuntimeFunction = (targetRVA < rtf.StartAddress || targetRVA >= rtf.StartAddress + rtf.Size); if (pointsOutsideRuntimeFunction && IsIntel2ByteIndirectJumpPCRelativeInstruction(targetImageOffset, out int instructionRelativeOffset)) { int thunkTargetRVA = targetRVA + instructionRelativeOffset; bool haveImportCell = _reader.ImportCellNames.TryGetValue(thunkTargetRVA, out string importCellName); if (_options.Naked && haveImportCell) { ReplaceRelativeOffset(ref instruction, $@"qword ptr [{importCellName}]", rtf); } else { ReplaceRelativeOffset(ref instruction, targetRVA, rtf); if (haveImportCell) { int instructionEnd = instruction.IndexOf('\n'); StringBuilder builder = new StringBuilder(instruction, 0, instructionEnd, capacity: 256); AppendComment(builder, @$ "JMP [0x{thunkTargetRVA:X4}]: {importCellName}"); builder.AppendLine(); instruction = builder.ToString(); } } } else if (pointsOutsideRuntimeFunction && IsAnotherRuntimeFunctionWithinMethod(targetRVA, rtf, out int runtimeFunctionIndex)) { string runtimeFunctionName = string.Format("RUNTIME_FUNCTION[{0}]", runtimeFunctionIndex); if (_options.Naked) { ReplaceRelativeOffset(ref instruction, runtimeFunctionName, rtf); } else { ReplaceRelativeOffset(ref instruction, targetRVA, rtf); int instructionEnd = instruction.IndexOf('\n'); StringBuilder builder = new StringBuilder(instruction, 0, instructionEnd, capacity: 256); AppendComment(builder, runtimeFunctionName); builder.AppendLine(); instruction = builder.ToString(); } } else { ReplaceRelativeOffset(ref instruction, nextInstructionRVA + offset, rtf); } } else if (instrSize == 6 && IsIntel2ByteJumpInstructionWithIntOffset(imageOffset + rtfOffset)) { int offset = BitConverter.ToInt32(_reader.Image, imageOffset + rtfOffset + 2); ReplaceRelativeOffset(ref instruction, nextInstructionRVA + offset, rtf); } }