From: Amy Date: Fri, 17 Aug 2018 00:05:23 +0000 (-0700) Subject: R2RDump - Documentation (#19497) X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=07330cad0eb3f2583283feb34ccf8c5ca3dfde39;p=platform%2Fupstream%2Fcoreclr.git R2RDump - Documentation (#19497) * Add comments * Add README * Use href links, disable disasm --- diff --git a/src/tools/r2rdump/Amd64/GcInfo.cs b/src/tools/r2rdump/Amd64/GcInfo.cs index df4b48f9a9..acc160566d 100644 --- a/src/tools/r2rdump/Amd64/GcInfo.cs +++ b/src/tools/r2rdump/Amd64/GcInfo.cs @@ -12,6 +12,9 @@ namespace R2RDump.Amd64 { public class GcInfo : BaseGcInfo { + /// + /// based on src/inc/gcinfodecoder.h GcInfoHeaderFlags + /// private enum GcInfoHeaderFlags { GC_INFO_IS_VARARG = 0x1, @@ -81,6 +84,9 @@ namespace R2RDump.Amd64 public GcInfo() { } + /// + /// based on GcInfoDecoder::GcInfoDecoder + /// public GcInfo(byte[] image, int offset, Machine machine, ushort majorVersion) { Offset = offset; @@ -253,19 +259,14 @@ namespace R2RDump.Amd64 } sb.AppendLine($"\tSlotTable:"); sb.Append(SlotTable.ToString()); - sb.AppendLine($"\tTransitions:"); - foreach (List transList in Transitions.Values) - { - foreach (GcTransition trans in transList) - { - sb.AppendLine("\t\t" + trans.ToString()); - } - } sb.AppendLine($"\tSize: {Size} bytes"); return sb.ToString(); } + /// + /// based on GcInfoDecoder::GcInfoDecoder + /// private void ParseHeaderFlags(byte[] image, ref int bitOffset) { GcInfoHeaderFlags headerFlags; @@ -305,6 +306,9 @@ namespace R2RDump.Amd64 return safePoints; } + /// + /// based on beginning of GcInfoDecoder::EnumerateLiveSlots + /// private List EnumerateInterruptibleRanges(byte[] image, int interruptibleRangeDelta1EncBase, int interruptibleRangeDelta2EncBase, ref int bitOffset) { List ranges = new List(); @@ -333,6 +337,9 @@ namespace R2RDump.Amd64 return (readyToRunMajorVersion == 1) ? 1 : GCINFO_VERSION; } + /// + /// based on end of GcInfoDecoder::EnumerateLiveSlots and GcInfoEncoder::Build + /// public Dictionary> GetTranstions(byte[] image, ref int bitOffset) { int totalInterruptibleLength = 0; @@ -348,22 +355,25 @@ namespace R2RDump.Amd64 } } - int numChunks = (totalInterruptibleLength + _gcInfoTypes.NUM_NORM_CODE_OFFSETS_PER_CHUNK - 1) / _gcInfoTypes.NUM_NORM_CODE_OFFSETS_PER_CHUNK; //=2 + int numChunks = (totalInterruptibleLength + _gcInfoTypes.NUM_NORM_CODE_OFFSETS_PER_CHUNK - 1) / _gcInfoTypes.NUM_NORM_CODE_OFFSETS_PER_CHUNK; int numBitsPerPointer = (int)NativeReader.DecodeVarLengthUnsigned(image, _gcInfoTypes.POINTER_SIZE_ENCBASE, ref bitOffset); if (numBitsPerPointer == 0) { return new Dictionary>(); } + // get offsets of each chunk int[] chunkPointers = new int[numChunks]; for (int i = 0; i < numChunks; i++) { chunkPointers[i] = NativeReader.ReadBits(image, numBitsPerPointer, ref bitOffset); } + + // Offset to m_Info2 containing all the info on register liveness int info2Offset = (int)Math.Ceiling(bitOffset / 8.0) * 8; List transitions = new List(); - bool[] liveAtEnd = new bool[SlotTable.GcSlots.Count - SlotTable.NumUntracked]; + bool[] liveAtEnd = new bool[SlotTable.GcSlots.Count - SlotTable.NumUntracked]; // true if slot is live at the end of the chunk for (int currentChunk = 0; currentChunk < numChunks; currentChunk++) { if (chunkPointers[currentChunk] == 0) @@ -375,7 +385,7 @@ namespace R2RDump.Amd64 bitOffset = info2Offset + chunkPointers[currentChunk] - 1; } - int couldBeLiveOffset = bitOffset; + int couldBeLiveOffset = bitOffset; // points to the couldBeLive bit array (array of bits indicating the slot changed state in the chunk) int slotId = 0; bool fSimple = (NativeReader.ReadBits(image, 1, ref couldBeLiveOffset) == 0); bool fSkipFirst = false; @@ -386,20 +396,22 @@ namespace R2RDump.Amd64 slotId = -1; } - uint numCouldBeLiveSlots = GetNumCouldBeLiveSlots(image, ref bitOffset); + uint numCouldBeLiveSlots = GetNumCouldBeLiveSlots(image, ref bitOffset); // count the number of set bits in the couldBeLive array - int finalStateOffset = bitOffset; - bitOffset += (int)numCouldBeLiveSlots; + int finalStateOffset = bitOffset; // points to the finalState bit array (array of bits indicating if the slot is live at the end of the chunk) + bitOffset += (int)numCouldBeLiveSlots; // points to the array of code offsets - int normChunkBaseCodeOffset = currentChunk * _gcInfoTypes.NUM_NORM_CODE_OFFSETS_PER_CHUNK; + int normChunkBaseCodeOffset = currentChunk * _gcInfoTypes.NUM_NORM_CODE_OFFSETS_PER_CHUNK; // the sum of the sizes of all preceeding chunks for (int i = 0; i < numCouldBeLiveSlots; i++) { + // get the index of the next couldBeLive slot slotId = GetNextSlotId(image, fSimple, fSkipFirst, slotId, ref couldBeLiveCnt, ref couldBeLiveOffset); + // set the liveAtEnd for the slot at slotId bool isLive = !liveAtEnd[slotId]; liveAtEnd[slotId] = (NativeReader.ReadBits(image, 1, ref finalStateOffset) != 0); - // Read transitions + // Read all the code offsets where the slot at slotId changed state while (NativeReader.ReadBits(image, 1, ref bitOffset) != 0) { int transitionOffset = NativeReader.ReadBits(image, _gcInfoTypes.NUM_NORM_CODE_OFFSETS_PER_CHUNK_LOG2, ref bitOffset) + normChunkBaseCodeOffset; @@ -410,8 +422,8 @@ namespace R2RDump.Amd64 } } + // convert normCodeOffsetDelta to the actual CodeOffset transitions.Sort((s1, s2) => s1.CodeOffset.CompareTo(s2.CodeOffset)); - return UpdateTransitionCodeOffset(transitions); } @@ -440,6 +452,7 @@ namespace R2RDump.Amd64 } else { + // count the number of set bits in the couldBeLive bit array foreach (var slot in SlotTable.GcSlots) { if (slot.Flags == GcSlotFlags.GC_SLOT_UNTRACKED) @@ -456,6 +469,7 @@ namespace R2RDump.Amd64 { if (fSimple) { + // Get the slotId by iterating through the couldBeLive bit array. The slotId is the index of the next set bit while (NativeReader.ReadBits(image, 1, ref couldBeLiveOffset) == 0) slotId++; } @@ -480,10 +494,13 @@ namespace R2RDump.Amd64 return slotId; } + /// + /// convert normCodeOffsetDelta to the actual CodeOffset + /// private Dictionary> UpdateTransitionCodeOffset(List transitions) { Dictionary> updatedTransitions = new Dictionary>(); - int cumInterruptibleLength = 0; + int cumInterruptibleLength = 0; // the sum of the lengths of all preceeding interruptible ranges using (IEnumerator interruptibleRangesIter = InterruptibleRanges.GetEnumerator()) { interruptibleRangesIter.MoveNext(); diff --git a/src/tools/r2rdump/Amd64/GcSlotTable.cs b/src/tools/r2rdump/Amd64/GcSlotTable.cs index a2bec0fc4e..1a30eaec3c 100644 --- a/src/tools/r2rdump/Amd64/GcSlotTable.cs +++ b/src/tools/r2rdump/Amd64/GcSlotTable.cs @@ -64,6 +64,9 @@ namespace R2RDump.Amd64 public GcSlotTable() { } + /// + /// based on GcSlotDecoder::DecodeSlotTable + /// public GcSlotTable(byte[] image, Machine machine, GcInfoTypes gcInfoTypes, ref int bitOffset) { if (NativeReader.ReadBits(image, 1, ref bitOffset) != 0) diff --git a/src/tools/r2rdump/Amd64/Registers.cs b/src/tools/r2rdump/Amd64/Registers.cs index e55bef83b4..349d3d05f8 100644 --- a/src/tools/r2rdump/Amd64/Registers.cs +++ b/src/tools/r2rdump/Amd64/Registers.cs @@ -8,6 +8,9 @@ using System.Text; namespace R2RDump.Amd64 { + /// + /// based on src\gcdump\gcdumpnonx86.cpp GetRegName + /// public enum Registers { EAX = 0, diff --git a/src/tools/r2rdump/Amd64/UnwindInfo.cs b/src/tools/r2rdump/Amd64/UnwindInfo.cs index 6c4755d8bb..c595bab68c 100644 --- a/src/tools/r2rdump/Amd64/UnwindInfo.cs +++ b/src/tools/r2rdump/Amd64/UnwindInfo.cs @@ -7,6 +7,9 @@ using System.Xml.Serialization; namespace R2RDump.Amd64 { + /// + /// based on src\inc\win64unwind.h _UNWIND_OP_CODES + /// public enum UnwindOpCodes { UWOP_PUSH_NONVOL = 0, @@ -23,6 +26,9 @@ namespace R2RDump.Amd64 UWOP_SET_FPREG_LARGE, } + /// + /// based on src\inc\win64unwind.h _UNWIND_OP_CODES + /// public enum UnwindFlags { UNW_FLAG_NHANDLER = 0x0, @@ -31,6 +37,9 @@ namespace R2RDump.Amd64 UNW_FLAG_CHAININFO = 0x4, } + /// + /// based on src\inc\win64unwind.h _UNWIND_CODE + /// public struct UnwindCode { [XmlAttribute("Index")] @@ -62,6 +71,9 @@ namespace R2RDump.Amd64 } } + /// + /// based on src\inc\win64unwind.h _UNWIND_INFO + /// public class UnwindInfo : BaseUnwindInfo { private const int _sizeofUnwindCode = 2; @@ -78,6 +90,9 @@ namespace R2RDump.Amd64 public UnwindInfo() { } + /// + /// based on ZapUnwindData::Save + /// public UnwindInfo(byte[] image, int offset) { byte versionAndFlags = NativeReader.ReadByte(image, ref offset); @@ -140,6 +155,9 @@ namespace R2RDump.Amd64 return sb.ToString(); } + /// + /// based on src\jit\unwindamd64.cpp DumpUnwindInfo + /// private string GetUnwindCode(ref int i) { StringBuilder sb = new StringBuilder(); diff --git a/src/tools/r2rdump/GCInfoTypes.cs b/src/tools/r2rdump/GCInfoTypes.cs index 0cc8513952..d42fd37422 100644 --- a/src/tools/r2rdump/GCInfoTypes.cs +++ b/src/tools/r2rdump/GCInfoTypes.cs @@ -8,6 +8,9 @@ using System.Text; namespace R2RDump { + /// + /// based on src\inc\gcinfotypes.h infoHdrAdjustConstants + /// enum InfoHdrAdjustConstants { // Constants @@ -22,9 +25,10 @@ namespace R2RDump MORE_BYTES_TO_FOLLOW = 0x80 }; - // - // Enum to define codes that are used to incrementally adjust the InfoHdr structure. - // First set of opcodes + /// + /// Enum to define codes that are used to incrementally adjust the InfoHdr structure. + /// based on src\inc\gcinfotypes.h infoHdrAdjustConstants + /// enum InfoHdrAdjust { SET_FRAMESIZE = 0, // 0x00 @@ -64,6 +68,9 @@ namespace R2RDump NEXT_THREE_EPILOGSIZE = 0x78 }; + /// + /// based on macros defined in src\inc\gcinfotypes.h + /// public class GcInfoTypes { private Machine _target; diff --git a/src/tools/r2rdump/NativeArray.cs b/src/tools/r2rdump/NativeArray.cs index bb0c746f6e..0e620858df 100644 --- a/src/tools/r2rdump/NativeArray.cs +++ b/src/tools/r2rdump/NativeArray.cs @@ -6,6 +6,9 @@ using System.Text; namespace R2RDump { + /// + /// based on NativeFormat::NativeArray + /// class NativeArray { private const int _blockSize = 16; diff --git a/src/tools/r2rdump/NativeHashtable.cs b/src/tools/r2rdump/NativeHashtable.cs index acb4f6dfbe..830805eee5 100644 --- a/src/tools/r2rdump/NativeHashtable.cs +++ b/src/tools/r2rdump/NativeHashtable.cs @@ -7,6 +7,9 @@ using System.Text; namespace R2RDump { + /// + /// based on NativeFormat::NativeParser + /// struct NativeParser { /// @@ -76,6 +79,9 @@ namespace R2RDump } } + /// + /// based on NativeFormat::NativeHashtable + /// struct NativeHashtable { private byte[] _image; diff --git a/src/tools/r2rdump/NativeReader.cs b/src/tools/r2rdump/NativeReader.cs index 654fe843f0..4c09256bd3 100644 --- a/src/tools/r2rdump/NativeReader.cs +++ b/src/tools/r2rdump/NativeReader.cs @@ -127,6 +127,7 @@ namespace R2RDump // /// Decode variable length numbers + /// based on src\inc\gcinfodecoder.h DecodeVarLengthUnsigned /// /// PE image /// Number of bits to read @@ -150,6 +151,9 @@ namespace R2RDump } } + // + /// based on src\inc\gcinfodecoder.h DecodeVarLengthSigned + /// public static int DecodeVarLengthSigned(byte[] image, int len, ref int bitOffset) { int numEncodings = (1 << len); @@ -169,6 +173,9 @@ namespace R2RDump } } + // + /// based on src\vm\nativeformatreader.h DecodeUnsigned + /// public static uint DecodeUnsigned(byte[] image, uint offset, ref uint pValue) { if (offset >= image.Length) @@ -225,6 +232,9 @@ namespace R2RDump return offset; } + // + /// based on src\vm\nativeformatreader.h DecodeSigned + /// public static uint DecodeSigned(byte[] image, uint offset, ref int pValue) { if (offset >= image.Length) @@ -281,6 +291,9 @@ namespace R2RDump return offset; } + // + /// based on src\debug\daccess\nidump.cpp DacSigUncompressData and DacSigUncompressBigData + /// public static uint ReadCompressedData(byte[] image, ref int start) { int off = start; @@ -306,7 +319,7 @@ namespace R2RDump } /// - /// Based on decodeUnsigned in gcdecoder.cpp, used by the x86 gcdump and unwindinfo + /// based on src\inc\gcdecoder.cpp decodeUnsigned /// public static uint DecodeUnsignedGc(byte[] image, ref int start) { @@ -323,7 +336,10 @@ namespace R2RDump return value; } - public static int DecodeSigned(byte[] image, ref int start) + /// + /// based on src\inc\gcdecoder.cpp decodeSigned + /// + public static int DecodeSignedGc(byte[] image, ref int start) { int size = 1; byte data = image[start++]; @@ -342,6 +358,9 @@ namespace R2RDump return value; } + /// + /// based on src\inc\gcdecoder.cpp decodeUDelta + /// public static uint DecodeUDelta(byte[] image, ref int start, uint lastValue) { uint delta = DecodeUnsignedGc(image, ref start); diff --git a/src/tools/r2rdump/R2RDump.cs b/src/tools/r2rdump/R2RDump.cs index 8034956078..9c57f71fa4 100644 --- a/src/tools/r2rdump/R2RDump.cs +++ b/src/tools/r2rdump/R2RDump.cs @@ -395,6 +395,7 @@ namespace R2RDump foreach (string filename in _inputFilenames) { + // parse the ReadyToRun image R2RReader r2r = new R2RReader(filename); if (_disasm) @@ -411,6 +412,7 @@ namespace R2RDump _dumper = new TextDumper(r2r, _writer, _raw, _header, _disasm, disassembler, _unwind, _gc, _sectionContents); } + // output the ReadyToRun info Dump(r2r); } } diff --git a/src/tools/r2rdump/R2RHeader.cs b/src/tools/r2rdump/R2RHeader.cs index 7212d3eff7..55745f664f 100644 --- a/src/tools/r2rdump/R2RHeader.cs +++ b/src/tools/r2rdump/R2RHeader.cs @@ -8,6 +8,9 @@ using System.Text; namespace R2RDump { + /// + /// based on src/inc/readytorun.h READYTORUN_HEADER + /// public class R2RHeader { [Flags] diff --git a/src/tools/r2rdump/R2RImportSection.cs b/src/tools/r2rdump/R2RImportSection.cs index 065c092ff4..2b278d8de2 100644 --- a/src/tools/r2rdump/R2RImportSection.cs +++ b/src/tools/r2rdump/R2RImportSection.cs @@ -10,8 +10,14 @@ using System.Xml.Serialization; namespace R2RDump { + /// + /// based on src/inc/readytorun.h READYTORUN_IMPORT_SECTION + /// public struct R2RImportSection { + /// + /// based on src/inc/corcompile.h CorCompileImportType + /// public enum CorCompileImportType { CORCOMPILE_IMPORT_TYPE_UNKNOWN = 0, @@ -23,6 +29,9 @@ namespace R2RDump CORCOMPILE_IMPORT_TYPE_VIRTUAL_METHOD = 6, }; + /// + /// based on src/inc/corcompile.h CorCompileImportFlags + /// public enum CorCompileImportFlags { CORCOMPILE_IMPORT_FLAGS_UNKNOWN = 0x0000, diff --git a/src/tools/r2rdump/R2RMethod.cs b/src/tools/r2rdump/R2RMethod.cs index 3650e03346..de66550e18 100644 --- a/src/tools/r2rdump/R2RMethod.cs +++ b/src/tools/r2rdump/R2RMethod.cs @@ -40,6 +40,9 @@ namespace R2RDump public Dictionary> Transitions { get; set; } } + /// + /// based on src/pal/inc/pal.h _RUNTIME_FUNCTION + /// public class RuntimeFunction { /// @@ -67,6 +70,9 @@ namespace R2RDump /// public int UnwindRVA { get; set; } + /// + /// The start offset of the runtime function with is non-zero for methods with multiple runtime functions + /// public int CodeOffset { get; set; } /// @@ -267,6 +273,9 @@ namespace R2RDump SignatureString = GetSignature(); } + /// + /// Initialize map of generic parameters names to the type in the instance + /// private void InitGenericInstances(GenericParameterHandleCollection genericParams, GenericElementTypes[] instanceArgs, uint[] tok) { if (instanceArgs.Length != genericParams.Count || tok.Length != genericParams.Count) @@ -276,18 +285,21 @@ namespace R2RDump for (int i = 0; i < instanceArgs.Length; i++) { - string key = _mdReader.GetString(_mdReader.GetGenericParameter(genericParams.ElementAt(i)).Name); - string name = instanceArgs[i].ToString(); + string key = _mdReader.GetString(_mdReader.GetGenericParameter(genericParams.ElementAt(i)).Name); // name of the generic param, eg. "T" + string type = instanceArgs[i].ToString(); // type of the generic param instance if (instanceArgs[i] == GenericElementTypes.ValueType) { var t = _mdReader.GetTypeDefinition(MetadataTokens.TypeDefinitionHandle((int)tok[i])); - name = _mdReader.GetString(t.Name); + type = _mdReader.GetString(t.Name); // name of the struct } - _genericParamInstanceMap[key] = name; + _genericParamInstanceMap[key] = type; } } + /// + /// Returns a string with format DeclaringType.Name(ArgTypes,...) + /// private string GetSignature() { StringBuilder sb = new StringBuilder(); diff --git a/src/tools/r2rdump/R2RReader.cs b/src/tools/r2rdump/R2RReader.cs index 0251568d3f..18f8b370a4 100644 --- a/src/tools/r2rdump/R2RReader.cs +++ b/src/tools/r2rdump/R2RReader.cs @@ -199,7 +199,7 @@ namespace R2RDump { int runtimeFunctionId; FixupCell[] fixups; - GetEntryPointInfoFromOffset(offset, out runtimeFunctionId, out fixups); + GetRuntimeFunctionIndexFromOffset(offset, out runtimeFunctionId, out fixups); R2RMethod method = new R2RMethod(R2RMethods.Count, _mdReader, rid, runtimeFunctionId, null, null, fixups); if (method.EntryPointRuntimeFunctionId < 0 || method.EntryPointRuntimeFunctionId >= isEntryPoint.Length) @@ -248,7 +248,7 @@ namespace R2RDump int runtimeFunctionId; FixupCell[] fixups; - GetEntryPointInfoFromOffset((int)curParser.Offset, out runtimeFunctionId, out fixups); + GetRuntimeFunctionIndexFromOffset((int)curParser.Offset, out runtimeFunctionId, out fixups); R2RMethod method = new R2RMethod(R2RMethods.Count, _mdReader, rid, runtimeFunctionId, args, tokens, fixups); if (method.EntryPointRuntimeFunctionId >= 0 && method.EntryPointRuntimeFunctionId < isEntryPoint.Length) { @@ -262,6 +262,7 @@ namespace R2RDump /// /// Get the RVAs of the runtime functions for each method + /// based on ZapUnwindInfo::Save /// private void ParseRuntimeFunctions(bool[] isEntryPoint, int runtimeFunctionOffset, int runtimeFunctionSize) { @@ -312,6 +313,9 @@ namespace R2RDump } } + /// + /// Iterates through a native hashtable to get all RIDs + /// private void ParseAvailableTypes() { if (!R2RHeader.Sections.ContainsKey(R2RSection.SectionType.READYTORUN_SECTION_AVAILABLE_TYPES)) @@ -334,6 +338,9 @@ namespace R2RDump } } + /// + /// Converts the bytes in the compiler identifier section to characters in a string + /// private string ParseCompilerIdentifier() { if (!R2RHeader.Sections.ContainsKey(R2RSection.SectionType.READYTORUN_SECTION_COMPILER_IDENTIFIER)) @@ -347,6 +354,9 @@ namespace R2RDump return Encoding.UTF8.GetString(identifier); } + /// + /// based on ZapImportSectionsTable::Save + /// private void ParseImportSections() { if (!R2RHeader.Sections.ContainsKey(R2RSection.SectionType.READYTORUN_SECTION_IMPORT_SECTIONS)) @@ -459,8 +469,9 @@ namespace R2RDump /// /// Reads the method entrypoint from the offset. Used for non-generic methods + /// based on NativeImageDumper::DumpReadyToRunMethods /// - private void GetEntryPointInfoFromOffset(int offset, out int runtimeFunctionIndex, out FixupCell[] fixupCells) + private void GetRuntimeFunctionIndexFromOffset(int offset, out int runtimeFunctionIndex, out FixupCell[] fixupCells) { fixupCells = null; diff --git a/src/tools/r2rdump/R2RSection.cs b/src/tools/r2rdump/R2RSection.cs index ff39c81b19..125d040657 100644 --- a/src/tools/r2rdump/R2RSection.cs +++ b/src/tools/r2rdump/R2RSection.cs @@ -11,6 +11,9 @@ namespace R2RDump { public struct R2RSection { + /// + /// based on src/inc/readytorun.h ReadyToRunSectionType + /// public enum SectionType { READYTORUN_SECTION_COMPILER_IDENTIFIER = 100, diff --git a/src/tools/r2rdump/README.md b/src/tools/r2rdump/README.md new file mode 100644 index 0000000000..ced835bcd2 --- /dev/null +++ b/src/tools/r2rdump/README.md @@ -0,0 +1,106 @@ +# R2RDump + +Parses and outputs the contents of a ReadyToRun image + +## Usage + +dotnet R2RDump.dll --in <path to ReadyToRun image> + +* -o, --out <arg> + - Output file path. Dumps everything to the specified file except help message and error messages +* -x, --xml + - Output in XML format +* --raw + - Dump the raw bytes of each section or runtime function +* --header + - Dump R2R header +* -d, --disasm + - Show disassembly of methods or runtime functions +* -q, --query <arg>... + - Query method by exact name, signature, row id or token +* -k, --keyword <arg>... + - Search method by keyword +* -r, --runtimefunction <arg>... + - Get one runtime function by id or relative virtual address +* -s, --section <arg>... + - Get section by keyword +* --unwind + - Dump unwindInfo +* --gc + - Dump gcInfo and slot table +* --sc + - Dump section contents +* -v, --verbose + - Dump raw bytes, disassembly, unwindInfo, gcInfo and section contents + +## ReadyToRun Format + +### READYTORUN_SECTION_COMPILER_IDENTIFIER + +A string describing the compiler. + +Eg. "CoreCLR 4.5.30319.0 __BUILDMACHINE__" + +### READYTORUN_SECTION_IMPORT_SECTIONS + +A struct described in [READYTORUN_IMPORT_SECTION](../../inc/readytorun.h) + +### READYTORUN_SECTION_RUNTIME_FUNCTIONS + +A array of RVAs. For x64, each RuntimeFunction has RVAs to the start of the assembly, end of the assembly, and start of the UnwindInfo. For x86, each RuntimeFunction has RVAs to the start of the assembly, and start of the UnwindInfo. + +### READYTORUN_SECTION_METHODDEF_ENTRYPOINTS + +A [NativeArray](NativeArray.cs) used for finding the index of the entrypoint RuntimeFunction for each method. The NativeArray is index by is the rowId-1 of a method. Each element in the NativeArray is an offset pointing to the RuntimeFunction index. + +### READYTORUN_SECTION_AVAILABLE_TYPES + +A [NativeHashtable](NativeHashtable.cs) mapping type hashcodes of types defined in the program to the rowIds. The hashcode is calculated with [ComputeNameHashCode](../../vm/typehashingalgorithms.h)(namespace) ^ [ComputeNameHashCode](../../vm/typehashingalgorithms.h)(name) + +### READYTORUN_SECTION_INSTANCE_METHOD_ENTRYPOINTS + +A [NativeHashtable](NativeHashtable.cs) mapping type hashcodes of generic instances to the (methodFlags, methodRowId, list of types, runtimeFunctionId). Each type in the list of types corresponds to a generic type in the method. + +Eg. GenericMethod<S, T>(T arg1, S arg2) instantiated for <int, UserDefinedStruct> is in the hashtable as: + +(hashcode) -> (methodFlags) (methodRowId) (number of generic types) (Int32) (ValueType) (RowId of UserDefinedStruct) (offset to RuntimeFunctionId) + +### UnwindInfo + +A struct described in [_UNWIND_INFO](../../inc/win64unwind.h). Each RuntimeFunction has its own UnwindInfo. + +For x86, it contains only an encoded function length + +For x64, it contains a bit field followed by an array of unwind codes ([_UNWIND_CODE](../../inc/win64unwind.h)) and finally padding to make it byte aligned + +### GcInfo + +Written into the ReadyToRun image right after UnwindInfo. Contains a header, GcSlots and GcTransitions (register liveness). + +The x64 GcInfo is written in crossgen by [GcInfoEncoder::Build](../../gcinfo/gcinfoencoder.cpp) and decoded similar to [GcInfoDecoder::EnumerateLiveSlots](../../vm/gcinfodecoder.cpp). The x86 gcInfo is written by [GCInfo::gcMakeRegPtrTable](../../jit/gcencode.cpp) and decoded similar to [GCDump::DumpGCTable](../../gcdump/i386/gcdumpx86.cpp) + +Contains the code length followed by the header, GcSlots, and finally GcTransitions + +The header contains flags indicating which properties are in the GcInfo. GcSlots gives details on the registers or stack pointer offsets are used in the method. GcTransitions give the CodeOffsets (which line in the assembly code) where GcSlots (excluding untracked slots) become live or dead + +In x64, GcTransitions are grouped into chunks where each chunk covers NUM_NORM_CODE_OFFSETS_PER_CHUNK lines of assembly code. The following format is used: +> Array of offsets pointing to each chunk + +> Padding to make it byte aligned + +> For each chunk: +>> 1 bit indicating if it's RLE encoded + +>> Array of bits indicating if each slot changed state in the chunk (ie. false if the slot is not used in the chunk). R2RDump uses this to calculate NumCouldBeLiveSlots and obtain slotIds + +>> Array of bits indicating if each slot is live at the end of the chunk + +>> For each slot that changed state in the chunk: +>>> Array of elements consisting of a bit set to 1 and the normCodeOffsetDelta indicating all the code offsets where the slot changed state in the chunk. CodeOffset = normCodeOffsetDelta + normChunkBaseCodeOffset + currentRangeStartOffset - cumInterruptibleLength, where normChunkBaseCodeOffset is the sum of the sizes of all preceeding chunks, currentRangeStartOffset is the start offset of the interruptible range that the transition falls under and cumInterruptibleLength is the sum of the lengths of interruptible ranges that came before it + + +# Todo + +* Support R2RDump on ARM and ARM64 (https://github.com/dotnet/coreclr/issues/19089) + +* Parse R2RSections: READYTORUN_SECTION_EXCEPTION_INFO, READYTORUN_SECTION_DEBUG_INFO, READYTORUN_SECTION_DELAYLOAD_METHODCALL_THUNKS, READYTORUN_SECTION_INLINING_INFO, READYTORUN_SECTION_PROFILEDATA_INFO diff --git a/src/tools/r2rdump/TextDumper.cs b/src/tools/r2rdump/TextDumper.cs index cf3d942d4f..1ecaec882f 100644 --- a/src/tools/r2rdump/TextDumper.cs +++ b/src/tools/r2rdump/TextDumper.cs @@ -168,6 +168,9 @@ namespace R2RDump SkipLine(); } + /// + /// Dumps disassembly and register liveness + /// internal override void DumpDisasm(RuntimeFunction rtf, int imageOffset, XmlNode parentNode = null) { int rtfOffset = 0; @@ -244,7 +247,7 @@ namespace R2RDump } break; case R2RSection.SectionType.READYTORUN_SECTION_METHODDEF_ENTRYPOINTS: - NativeArray methodEntryPoints = new NativeArray(_r2r.Image, (uint)_r2r.GetOffset(section.RelativeVirtualAddress)); + NativeArray methodEntryPoints = new NativeArray(_r2r.Image, (uint)_r2r.GetOffset(section.RelativeVirtualAddress)); _writer.Write(methodEntryPoints.ToString()); break; case R2RSection.SectionType.READYTORUN_SECTION_INSTANCE_METHOD_ENTRYPOINTS: @@ -259,8 +262,18 @@ namespace R2RDump int rtfIndex = 0; while (rtfOffset < rtfEndOffset) { - uint rva = NativeReader.ReadUInt32(_r2r.Image, ref rtfOffset); - _writer.WriteLine($"{rtfIndex}: 0x{rva:X8}"); + int startRva = NativeReader.ReadInt32(_r2r.Image, ref rtfOffset); + int endRva = -1; + if (_r2r.Machine == Machine.Amd64) + { + endRva = NativeReader.ReadInt32(_r2r.Image, ref rtfOffset); + } + int unwindRva = NativeReader.ReadInt32(_r2r.Image, ref rtfOffset); + _writer.WriteLine($"Index: {rtfIndex}"); + _writer.WriteLine($"\tStartRva: 0x{startRva:X8}"); + if (endRva != -1) + _writer.WriteLine($"\tEndRva: 0x{endRva:X8}"); + _writer.WriteLine($"\tUnwindRva: 0x{unwindRva:X8}"); rtfIndex++; } break; diff --git a/src/tools/r2rdump/XmlDumper.cs b/src/tools/r2rdump/XmlDumper.cs index 10cde54277..f260000e1f 100644 --- a/src/tools/r2rdump/XmlDumper.cs +++ b/src/tools/r2rdump/XmlDumper.cs @@ -215,6 +215,9 @@ namespace R2RDump } } + /// + /// Dumps disassembly and register liveness + /// internal override void DumpDisasm(RuntimeFunction rtf, int imageOffset, XmlNode parentNode) { int rtfOffset = 0; diff --git a/src/tools/r2rdump/x86/CallPattern.cs b/src/tools/r2rdump/x86/CallPattern.cs index cc8a3c30cd..0a8cb1814a 100644 --- a/src/tools/r2rdump/x86/CallPattern.cs +++ b/src/tools/r2rdump/x86/CallPattern.cs @@ -6,6 +6,10 @@ namespace R2RDump.x86 { class CallPattern { + + /// + /// based on src\inc\gcdecoder.cpp decodeCallPattern + /// public static void DecodeCallPattern(uint pattern, out uint argCnt, out uint regMask, out uint argMask, out uint codeDelta) { uint val = callPatternTable[pattern]; @@ -16,8 +20,14 @@ namespace R2RDump.x86 codeDelta = fld[3]; } + /// + /// based on src\inc\gcdecoder.cpp callCommonDelta + /// public static uint[] callCommonDelta = { 6, 8, 10, 12 }; + /// + /// based on src\inc\gcdecoder.cpp callPatternTable + /// private static uint[] callPatternTable = { 0x0a000200, // 30109 diff --git a/src/tools/r2rdump/x86/GcInfo.cs b/src/tools/r2rdump/x86/GcInfo.cs index e0896510fd..da1f074eca 100644 --- a/src/tools/r2rdump/x86/GcInfo.cs +++ b/src/tools/r2rdump/x86/GcInfo.cs @@ -19,6 +19,9 @@ namespace R2RDump.x86 public GcInfo() { } + /// + /// based on GCDump::DumpGCTable + /// public GcInfo(byte[] image, int offset, Machine machine, ushort majorVersion) { Offset = offset; @@ -88,6 +91,9 @@ namespace R2RDump.x86 } } + /// + /// based on GCDump::DumpGCTable + /// private void GetTransitionsFullyInterruptible(byte[] image, ref int offset) { uint argCnt = 0; @@ -187,6 +193,9 @@ namespace R2RDump.x86 } } + /// + /// based on GCDump::DumpGCTable + /// private void GetTransitionsEbpFrame(byte[] image, ref int offset) { while (true) @@ -327,6 +336,9 @@ namespace R2RDump.x86 } } + /// + /// based on GCDump::DumpGCTable + /// private void SaveCallTransition(byte[] image, ref int offset, uint val, uint curOffs, uint callRegMask, bool callPndTab, uint callPndTabCnt, uint callPndMask, uint lastSkip, ref uint imask) { uint iregMask, iargMask; diff --git a/src/tools/r2rdump/x86/GcSlotTable.cs b/src/tools/r2rdump/x86/GcSlotTable.cs index d5486d8142..fd72da57eb 100644 --- a/src/tools/r2rdump/x86/GcSlotTable.cs +++ b/src/tools/r2rdump/x86/GcSlotTable.cs @@ -124,6 +124,9 @@ namespace R2RDump.x86 return sb.ToString(); } + /// + /// based on GCDump::DumpGCTable + /// private void DecodeUntracked(byte[] image, InfoHdrSmall header, ref int offset) { uint calleeSavedRegs = 0; @@ -144,7 +147,7 @@ namespace R2RDump.x86 char reg = header.EbpFrame ? 'B' : 'S'; - stkOffsDelta = NativeReader.DecodeSigned(image, ref offset); + stkOffsDelta = NativeReader.DecodeSignedGc(image, ref offset); int stkOffs = lastStkOffs - stkOffsDelta; lastStkOffs = stkOffs; @@ -162,6 +165,9 @@ namespace R2RDump.x86 } } + /// + /// based on GCDump::DumpGCTable + /// private void DecodeFrameVariableLifetimeTable(byte[] image, InfoHdrSmall header, ref int offset) { uint count = header.VarPtrTableSize; diff --git a/src/tools/r2rdump/x86/InfoHdr.cs b/src/tools/r2rdump/x86/InfoHdr.cs index 000e167e2a..7bb105254d 100644 --- a/src/tools/r2rdump/x86/InfoHdr.cs +++ b/src/tools/r2rdump/x86/InfoHdr.cs @@ -9,6 +9,9 @@ using System.Xml.Serialization; namespace R2RDump.x86 { + /// + /// based on src\inc\gcinfotypes.h InfoHdrSmall + /// public struct InfoHdrSmall { private const uint INVALID_GS_COOKIE_OFFSET = 0; @@ -160,11 +163,18 @@ namespace R2RDump.x86 private const uint HAS_REV_PINVOKE_FRAME_OFFSET = 0xFFFFFFFF; private const uint YES = HAS_VARPTR; + /// + /// based on src\inc\gcinfotypes.h GetInfoHdr + /// public static InfoHdrSmall GetInfoHdr(byte encoding) { return _infoHdrShortcut[encoding]; } + /// + /// Initialize the GcInfo header + /// based on src\inc\gcdecoder.cpp DecodeHeader and GCDump::DumpInfoHdr + /// public static InfoHdrSmall DecodeHeader(byte[] image, ref int offset, int codeLength) { byte nextByte = image[offset++]; @@ -369,6 +379,9 @@ namespace R2RDump.x86 return header; } + /// + /// based on src\inc\gcdecoder.cpp infoHdrShortcut + /// private static InfoHdrSmall[] _infoHdrShortcut = { new InfoHdrSmall( 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), // 1139 00 new InfoHdrSmall( 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), // 128738 01 diff --git a/src/tools/r2rdump/x86/Registers.cs b/src/tools/r2rdump/x86/Registers.cs index 13b0d8dba2..69763b0358 100644 --- a/src/tools/r2rdump/x86/Registers.cs +++ b/src/tools/r2rdump/x86/Registers.cs @@ -8,6 +8,9 @@ using System.Text; namespace R2RDump.x86 { + /// + /// based on src\gcdump\i386\gcdumpx86.cpp RegName + /// public enum Registers { EAX = 0x00, @@ -20,6 +23,9 @@ namespace R2RDump.x86 EDI = 0x07, }; + /// + /// based on src\gcdump\i386\gcdumpx86.cpp CalleeSavedRegName + /// public enum CalleeSavedRegisters { EDI = 0x00, diff --git a/src/tools/r2rdump/x86/UnwindInfo.cs b/src/tools/r2rdump/x86/UnwindInfo.cs index 7f617f0704..60fe13dc8e 100644 --- a/src/tools/r2rdump/x86/UnwindInfo.cs +++ b/src/tools/r2rdump/x86/UnwindInfo.cs @@ -7,6 +7,9 @@ using System.Xml.Serialization; namespace R2RDump.x86 { + /// + /// based on src\inc\win64unwind.h _UNWIND_INFO + /// public class UnwindInfo : BaseUnwindInfo { public uint FunctionLength { get; set; }