From 454b13e6359787e448b5c53cd8a159d73e11ec0c Mon Sep 17 00:00:00 2001 From: =?utf8?q?Tom=C3=A1=C5=A1=20Rylek?= Date: Tue, 10 Jul 2018 23:48:25 +0200 Subject: [PATCH] Initial implementation of method fixup parser (dotnet/coreclr#18749) * Initial implementation of method fixup parser I'm working on this right now in the CoreRT R2R compiler so I'm adding the relevant structures and algorithms to the dumper to let me analyze various R2R files and their method fixups. I have locally verified that this shows reasonable fixup info for my Hello World repro compiled both using the CoreRT R2R compiled and using crossgen. Thanks Tomas * Improvements in signature dumping Signature dumping was slightly imprecise - the signature field was named "Section" in the dump and it was displaying a dword, however signatures are logically arbitrary byte streams. Sadly I'm aware of no easy way to detect the length of an arbitrary signature without adding a multitude of dedicated parsers for the different signature types used by different fixup / helper types so for now I'm displaying an 8-byte sample. I have also made the formatting of the import section entries more compact to make the list easier to read in the presence of a larger number of fixups and I added support for displaying the entry offset (as this is what the method fixups indirectly refer to via the fixup indices). Thanks Tomas * Move NibbleReader into a separate source file Based on Amy's PR feedback I'm moving NibbleReader into a separate source file. After all, I have a counterpart NibbleWriter.cs in the R2R compiler codebase. Thanks Tomas * Remove unnecessary usings from the new NibbleReader.cs file As an additional bit of cleanup I used the VS refactoring tool to remove unnecessary usings from the new NibbleReader source file. Thanks Tomas Commit migrated from https://github.com/dotnet/coreclr/commit/f39809b6e560450daa299d7566ca6d861cb2c270 --- src/coreclr/src/tools/r2rdump/NibbleReader.cs | 90 +++++++++++++++++++++ src/coreclr/src/tools/r2rdump/R2RImportSection.cs | 17 ++-- src/coreclr/src/tools/r2rdump/R2RMethod.cs | 16 +++- src/coreclr/src/tools/r2rdump/R2RReader.cs | 99 ++++++++++++++++++++--- src/coreclr/src/tools/r2rdump/TextDumper.cs | 1 - 5 files changed, 204 insertions(+), 19 deletions(-) create mode 100644 src/coreclr/src/tools/r2rdump/NibbleReader.cs diff --git a/src/coreclr/src/tools/r2rdump/NibbleReader.cs b/src/coreclr/src/tools/r2rdump/NibbleReader.cs new file mode 100644 index 0000000..341c1d0 --- /dev/null +++ b/src/coreclr/src/tools/r2rdump/NibbleReader.cs @@ -0,0 +1,90 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace R2RDump +{ + /// + /// Helper to read memory by 4-bit (half-byte) nibbles as is used for encoding + /// method fixups. More or less ported over from CoreCLR src\inc\nibblestream.h. + /// + class NibbleReader + { + /// + /// Special value in _nextNibble saying there's no next nibble and the next byte + /// must be read from the image. + /// + private const byte NoNextNibble = 0xFF; + + /// + /// Byte array representing the PE file. + /// + private byte[] _image; + + /// + /// Offset within the image. + /// + private int _offset; + + /// + /// Value of the next nibble or 0xFF when there's no cached next nibble. + /// + private byte _nextNibble; + + public NibbleReader(byte[] image, int offset) + { + _image = image; + _offset = offset; + _nextNibble = NoNextNibble; + } + + public byte ReadNibble() + { + byte result; + if (_nextNibble != NoNextNibble) + { + result = _nextNibble; + _nextNibble = NoNextNibble; + } + else + { + _nextNibble = _image[_offset++]; + result = (byte)(_nextNibble & 0x0F); + _nextNibble >>= 4; + } + return result; + } + + /// + /// Read an unsigned int that was encoded via variable length nibble encoding + /// from CoreCLR NibbleWriter::WriteEncodedU32. + /// + public uint ReadUInt() + { + uint value = 0; + + // The encoding is variably lengthed, with the high-bit of every nibble indicating whether + // there is another nibble in the value. Each nibble contributes 3 bits to the value. + uint nibble; + do + { + nibble = ReadNibble(); + value = (value << 3) + (nibble & 0x7); + } + while ((nibble & 0x8) != 0); + + return value; + } + + /// + /// Read an encoded signed integer from the nibble reader. This uses the same unsigned + /// encoding, just left shifting the absolute value by one and filling in bit #0 with the sign bit. + /// + public int ReadInt() + { + uint unsignedValue = ReadUInt(); + int signedValue = (int)(unsignedValue >> 1); + return ((unsignedValue & 1) != 0 ? -signedValue : signedValue); + } + } +} diff --git a/src/coreclr/src/tools/r2rdump/R2RImportSection.cs b/src/coreclr/src/tools/r2rdump/R2RImportSection.cs index 4f591df..981501b 100644 --- a/src/coreclr/src/tools/r2rdump/R2RImportSection.cs +++ b/src/coreclr/src/tools/r2rdump/R2RImportSection.cs @@ -32,22 +32,27 @@ namespace R2RDump public struct ImportSectionEntry { + public int StartOffset { get; set; } public long Section { get; set; } public uint SignatureRVA { get; set; } - public uint Signature { get; set; } - public ImportSectionEntry(long section, uint signatureRVA, uint signature) + public byte[] SignatureSample { get; set; } + public ImportSectionEntry(int startOffset, long section, uint signatureRVA, byte[] signatureSample) { + StartOffset = startOffset; Section = section; SignatureRVA = signatureRVA; - Signature = signature; + SignatureSample = signatureSample; } public override string ToString() { StringBuilder sb = new StringBuilder(); - sb.AppendLine($"\tSection: 0x{Section:X8} ({Section})"); - sb.AppendLine($"\tSignatureRVA: 0x{SignatureRVA:X8} ({SignatureRVA})"); - sb.AppendLine($"\tSection: 0x{Signature:X8} ({Signature})"); + sb.Append($@"+{StartOffset:X4} Section: 0x{Section:X8} SignatureRVA: 0x{SignatureRVA:X8} "); + foreach (byte b in SignatureSample) + { + sb.AppendFormat("{0:X2} ", b); + } + sb.Append("..."); return sb.ToString(); } } diff --git a/src/coreclr/src/tools/r2rdump/R2RMethod.cs b/src/coreclr/src/tools/r2rdump/R2RMethod.cs index 20d4870..a051e90 100644 --- a/src/coreclr/src/tools/r2rdump/R2RMethod.cs +++ b/src/coreclr/src/tools/r2rdump/R2RMethod.cs @@ -143,6 +143,8 @@ namespace R2RDump [XmlIgnore] public GcInfo GcInfo { get; set; } + public FixupCell[] Fixups { get; set; } + /// /// Maps all the generic parameters to the type in the instance /// @@ -188,7 +190,7 @@ namespace R2RDump /// /// Extracts the method signature from the metadata by rid /// - public R2RMethod(MetadataReader mdReader, uint rid, int entryPointId, GenericElementTypes[] instanceArgs, uint[] tok) + public R2RMethod(MetadataReader mdReader, uint rid, int entryPointId, GenericElementTypes[] instanceArgs, uint[] tok, FixupCell[] fixups) { Token = _mdtMethodDef | rid; Rid = rid; @@ -217,12 +219,14 @@ namespace R2RDump argCount = signatureReader.ReadCompressedInteger(); } + Fixups = fixups; + DisassemblingTypeProvider provider = new DisassemblingTypeProvider(); if (IsGeneric && instanceArgs != null && tok != null) { InitGenericInstances(genericParams, instanceArgs, tok); } - + DisassemblingGenericContext genericContext = new DisassemblingGenericContext(new string[0], _genericParamInstanceMap.Values.ToArray()); Signature = _methodDef.DecodeSignature(provider, genericContext); @@ -296,6 +300,14 @@ namespace R2RDump sb.AppendLine($"Rid: {Rid}"); sb.AppendLine($"EntryPointRuntimeFunctionId: {EntryPointRuntimeFunctionId}"); sb.AppendLine($"Number of RuntimeFunctions: {RuntimeFunctions.Count}"); + if (Fixups != null) + { + sb.AppendLine($"Number of fixups: {Fixups.Count()}"); + foreach (FixupCell cell in Fixups) + { + sb.AppendLine($" TableIndex {cell.TableIndex}, Offset {cell.CellOffset:X4}"); + } + } return sb.ToString(); } diff --git a/src/coreclr/src/tools/r2rdump/R2RReader.cs b/src/coreclr/src/tools/r2rdump/R2RReader.cs index d365f87..a2f2a97 100644 --- a/src/coreclr/src/tools/r2rdump/R2RReader.cs +++ b/src/coreclr/src/tools/r2rdump/R2RReader.cs @@ -32,6 +32,32 @@ namespace R2RDump E15 = 15, } + /// + /// This structure represents a single precode fixup cell decoded from the + /// nibble-oriented per-method fixup blob. Each method entrypoint fixup + /// represents an array of cells that must be fixed up before the method + /// can start executing. + /// + public struct FixupCell + { + /// + /// Zero-based index of the import table within the import tables section. + /// + public uint TableIndex; + + /// + /// Zero-based offset of the entry in the import table; it must be a multiple + /// of the target architecture pointer size. + /// + public uint CellOffset; + + public FixupCell(uint tableIndex, uint cellOffset) + { + TableIndex = tableIndex; + CellOffset = cellOffset; + } + } + public class R2RReader { private readonly PEReader _peReader; @@ -181,7 +207,10 @@ namespace R2RDump int offset = 0; if (methodEntryPoints.TryGetAt(Image, rid - 1, ref offset)) { - R2RMethod method = new R2RMethod(_mdReader, rid, GetEntryPointIdFromOffset(offset), null, null); + int runtimeFunctionId; + FixupCell[] fixups; + GetEntryPointInfoFromOffset(offset, out runtimeFunctionId, out fixups); + R2RMethod method = new R2RMethod(_mdReader, rid, runtimeFunctionId, null, null, fixups); if (method.EntryPointRuntimeFunctionId < 0 || method.EntryPointRuntimeFunctionId >= isEntryPoint.Length) { @@ -227,8 +256,10 @@ namespace R2RDump } } - int id = GetEntryPointIdFromOffset((int)curParser.Offset); - R2RMethod method = new R2RMethod(_mdReader, rid, id, args, tokens); + int runtimeFunctionId; + FixupCell[] fixups; + GetEntryPointInfoFromOffset((int)curParser.Offset, out runtimeFunctionId, out fixups); + R2RMethod method = new R2RMethod(_mdReader, rid, runtimeFunctionId, args, tokens, fixups); if (method.EntryPointRuntimeFunctionId >= 0 && method.EntryPointRuntimeFunctionId < isEntryPoint.Length) { isEntryPoint[method.EntryPointRuntimeFunctionId] = true; @@ -331,6 +362,7 @@ namespace R2RDump { int rva = NativeReader.ReadInt32(Image, ref offset); int sectionOffset = GetOffset(rva); + int startOffset = sectionOffset; int size = NativeReader.ReadInt32(Image, ref offset); R2RImportSection.CorCompileImportFlags flags = (R2RImportSection.CorCompileImportFlags)NativeReader.ReadUInt16(Image, ref offset); byte type = NativeReader.ReadByte(Image, ref offset); @@ -357,11 +389,14 @@ namespace R2RDump uint sigRva = 0; while (sigRva != firstSigRva) { + int entryOffset = sectionOffset - startOffset; sigRva = NativeReader.ReadUInt32(Image, ref signatureOffset); long section = NativeReader.ReadInt64(Image, ref sectionOffset); int sigOff = GetOffset((int)sigRva); - uint signature = NativeReader.ReadUInt32(Image, ref sigOff); - entries.Add(new R2RImportSection.ImportSectionEntry(section, sigRva, signature)); + int sigSampleLength = Math.Min(8, Image.Length - sigOff); + byte[] signatureSample = new byte[sigSampleLength]; + Array.Copy(Image, sigOff, signatureSample, 0, sigSampleLength); + entries.Add(new R2RImportSection.ImportSectionEntry(entryOffset, section, sigRva, signatureSample)); } } break; @@ -369,11 +404,14 @@ namespace R2RDump case R2RImportSection.CorCompileImportFlags.CORCOMPILE_IMPORT_FLAGS_PCODE: for (int i = 0; i < entryCount; i++) { + int entryOffset = sectionOffset - startOffset; long section = NativeReader.ReadInt64(Image, ref sectionOffset); uint sigRva = NativeReader.ReadUInt32(Image, ref signatureOffset); int sigOff = GetOffset((int)sigRva); - uint signature = NativeReader.ReadUInt32(Image, ref sigOff); - entries.Add(new R2RImportSection.ImportSectionEntry(section, sigRva, signature)); + int sigSampleLength = Math.Min(8, Image.Length - sigOff); + byte[] signatureSample = new byte[sigSampleLength]; + Array.Copy(Image, sigOff, signatureSample, 0, sigSampleLength); + entries.Add(new R2RImportSection.ImportSectionEntry(entryOffset, section, sigRva, signatureSample)); } break; } @@ -424,8 +462,10 @@ namespace R2RDump /// /// Reads the method entrypoint from the offset. Used for non-generic methods /// - private int GetEntryPointIdFromOffset(int offset) + private void GetEntryPointInfoFromOffset(int offset, out int runtimeFunctionIndex, out FixupCell[] fixupCells) { + fixupCells = null; + // get the id of the entry point runtime function from the MethodEntryPoints NativeArray uint id = 0; // the RUNTIME_FUNCTIONS index offset = (int)NativeReader.DecodeUnsigned(Image, (uint)offset, ref id); @@ -437,7 +477,8 @@ namespace R2RDump NativeReader.DecodeUnsigned(Image, (uint)offset, ref val); offset -= (int)val; } - // TODO: Dump fixups + + fixupCells = DecodeFixupCells(offset); id >>= 2; } @@ -446,7 +487,45 @@ namespace R2RDump id >>= 1; } - return (int)id; + runtimeFunctionIndex = (int)id; + } + + private FixupCell[] DecodeFixupCells(int offset) + { + List cells = new List(); + NibbleReader reader = new NibbleReader(Image, offset); + + // The following algorithm has been loosely ported from CoreCLR, + // src\vm\ceeload.inl, BOOL Module::FixupDelayListAux + uint curTableIndex = reader.ReadUInt(); + + while (true) + { + uint fixupIndex = reader.ReadUInt(); // Accumulate the real rva from the delta encoded rva + + while (true) + { + cells.Add(new FixupCell(curTableIndex, fixupIndex)); + + uint delta = reader.ReadUInt(); + + // Delta of 0 means end of entries in this table + if (delta == 0) + break; + + fixupIndex += delta; + } + + uint tableIndex = reader.ReadUInt(); + + if (tableIndex == 0) + break; + + curTableIndex = curTableIndex + tableIndex; + + } // Done with all entries in this table + + return cells.ToArray(); } } } diff --git a/src/coreclr/src/tools/r2rdump/TextDumper.cs b/src/coreclr/src/tools/r2rdump/TextDumper.cs index 303cb90..e393220 100644 --- a/src/coreclr/src/tools/r2rdump/TextDumper.cs +++ b/src/coreclr/src/tools/r2rdump/TextDumper.cs @@ -289,7 +289,6 @@ namespace R2RDump } foreach (R2RImportSection.ImportSectionEntry entry in importSection.Entries) { - _writer.WriteLine(); _writer.WriteLine(entry.ToString()); } _writer.WriteLine(); -- 2.7.4