Initial implementation of method fixup parser (dotnet/coreclr#18749)
authorTomáš Rylek <trylek@microsoft.com>
Tue, 10 Jul 2018 21:48:25 +0000 (23:48 +0200)
committerGitHub <noreply@github.com>
Tue, 10 Jul 2018 21:48:25 +0000 (23:48 +0200)
* 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 [new file with mode: 0644]
src/coreclr/src/tools/r2rdump/R2RImportSection.cs
src/coreclr/src/tools/r2rdump/R2RMethod.cs
src/coreclr/src/tools/r2rdump/R2RReader.cs
src/coreclr/src/tools/r2rdump/TextDumper.cs

diff --git a/src/coreclr/src/tools/r2rdump/NibbleReader.cs b/src/coreclr/src/tools/r2rdump/NibbleReader.cs
new file mode 100644 (file)
index 0000000..341c1d0
--- /dev/null
@@ -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
+{
+    /// <summary>
+    /// 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.
+    /// </summary>
+    class NibbleReader
+    {
+        /// <summary>
+        /// Special value in _nextNibble saying there's no next nibble and the next byte
+        /// must be read from the image.
+        /// </summary>
+        private const byte NoNextNibble = 0xFF;
+
+        /// <summary>
+        /// Byte array representing the PE file.
+        /// </summary>
+        private byte[] _image;
+
+        /// <summary>
+        /// Offset within the image.
+        /// </summary>
+        private int _offset;
+
+        /// <summary>
+        /// Value of the next nibble or 0xFF when there's no cached next nibble.
+        /// </summary>
+        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;
+        }
+
+        /// <summary>
+        /// Read an unsigned int that was encoded via variable length nibble encoding
+        /// from CoreCLR NibbleWriter::WriteEncodedU32.
+        /// </summary>
+        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;
+        }
+
+        /// <summary>
+        /// 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.
+        /// </summary>
+        public int ReadInt()
+        {
+            uint unsignedValue = ReadUInt();
+            int signedValue = (int)(unsignedValue >> 1);
+            return ((unsignedValue & 1) != 0 ? -signedValue : signedValue);
+        }
+    }
+}
index 4f591df..981501b 100644 (file)
@@ -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();
             }
         }
index 20d4870..a051e90 100644 (file)
@@ -143,6 +143,8 @@ namespace R2RDump
         [XmlIgnore]
         public GcInfo GcInfo { get; set; }
 
+        public FixupCell[] Fixups { get; set; }
+
         /// <summary>
         /// Maps all the generic parameters to the type in the instance
         /// </summary>
@@ -188,7 +190,7 @@ namespace R2RDump
         /// <summary>
         /// Extracts the method signature from the metadata by rid
         /// </summary>
-        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();
         }
index d365f87..a2f2a97 100644 (file)
@@ -32,6 +32,32 @@ namespace R2RDump
         E15 = 15,
     }
 
+    /// <summary>
+    /// 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.
+    /// </summary>
+    public struct FixupCell
+    {
+        /// <summary>
+        /// Zero-based index of the import table within the import tables section.
+        /// </summary>
+        public uint TableIndex;
+
+        /// <summary>
+        /// Zero-based offset of the entry in the import table; it must be a multiple
+        /// of the target architecture pointer size.
+        /// </summary>
+        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
         /// <summary>
         /// Reads the method entrypoint from the offset. Used for non-generic methods
         /// </summary>
-        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<FixupCell> cells = new List<FixupCell>();
+            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();
         }
     }
 }
index 303cb90..e393220 100644 (file)
@@ -289,7 +289,6 @@ namespace R2RDump
                         }
                         foreach (R2RImportSection.ImportSectionEntry entry in importSection.Entries)
                         {
-                            _writer.WriteLine();
                             _writer.WriteLine(entry.ToString());
                         }
                         _writer.WriteLine();