diff options
author | Simon Nattress <simonn@microsoft.com> | 2018-09-07 13:41:30 -0700 |
---|---|---|
committer | Simon Nattress <simonn@microsoft.com> | 2018-09-08 10:31:16 -0700 |
commit | b5cbf4dd84bcf22950d4f6d665723c7d23981272 (patch) | |
tree | 5bda9425e9e75b46c4f5b2546554863be51e90d8 | |
parent | 51c3dc3bbd5e7515cbc03249c5e34b239a87b281 (diff) | |
download | coreclr-b5cbf4dd84bcf22950d4f6d665723c7d23981272.tar.gz coreclr-b5cbf4dd84bcf22950d4f6d665723c7d23981272.tar.bz2 coreclr-b5cbf4dd84bcf22950d4f6d665723c7d23981272.zip |
Add DebugInfo dumping support to r2rdump
Display formatted debug info for each runtime function
-rw-r--r-- | src/tools/r2rdump/DebugInfo.cs | 245 | ||||
-rw-r--r-- | src/tools/r2rdump/DebugInfoTypes.cs | 97 | ||||
-rw-r--r-- | src/tools/r2rdump/NibbleReader.cs | 5 | ||||
-rw-r--r-- | src/tools/r2rdump/R2RMethod.cs | 11 | ||||
-rw-r--r-- | src/tools/r2rdump/R2RReader.cs | 30 |
5 files changed, 386 insertions, 2 deletions
diff --git a/src/tools/r2rdump/DebugInfo.cs b/src/tools/r2rdump/DebugInfo.cs new file mode 100644 index 0000000000..eacc84856f --- /dev/null +++ b/src/tools/r2rdump/DebugInfo.cs @@ -0,0 +1,245 @@ +// 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. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection.PortableExecutable; +using System.Text; + +namespace R2RDump +{ + /// <summary> + /// Represents the debug information for a single method in the ready-to-run image. + /// See <a href="https://github.com/dotnet/coreclr/blob/master/src/inc/cordebuginfo.h">src\inc\cordebuginfo.h</a> for + /// the fundamental types this is based on. + /// </summary> + public class DebugInfo + { + private List<DebugInfoBoundsEntry> _boundsList = new List<DebugInfoBoundsEntry>(); + private List<NativeVarInfo> _variablesList = new List<NativeVarInfo>(); + private Machine _machine; + + public DebugInfo(byte[] image, int offset, Machine machine) + { + _machine = machine; + + // Get the id of the runtime function from the NativeArray + uint lookback = 0; + uint debugInfoOffset = NativeReader.DecodeUnsigned(image, (uint)offset, ref lookback); + + if (lookback != 0) + { + System.Diagnostics.Debug.Assert(0 < lookback && lookback < offset); + debugInfoOffset = (uint)offset - lookback; + } + + NibbleReader reader = new NibbleReader(image, (int)debugInfoOffset); + uint boundsByteCount = reader.ReadUInt(); + uint variablesByteCount = reader.ReadUInt(); + int boundsOffset = reader.GetNextByteOffset(); + int variablesOffset = (int)(boundsOffset + boundsByteCount); + + if (boundsByteCount > 0) + { + ParseBounds(image, boundsOffset); + } + + if (variablesByteCount > 0) + { + ParseNativeVarInfo(image, variablesOffset); + } + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + + if (_boundsList.Count > 0) + sb.AppendLine("Debug Info"); + + sb.AppendLine("\tBounds:"); + for (int i = 0; i < _boundsList.Count; ++i) + { + sb.AppendLine($"\tNative Offset: 0x{_boundsList[i].NativeOffset:X}, IL Offset: 0x{_boundsList[i].ILOffset:X}, Source Types: {_boundsList[i].SourceTypes}"); + } + + sb.AppendLine(""); + + if (_variablesList.Count > 0) + sb.AppendLine("\tVariable Locations:"); + + for (int i = 0; i < _variablesList.Count; ++i) + { + var varLoc = _variablesList[i]; + sb.AppendLine($"\tVariable Number: {varLoc.VariableNumber}"); + sb.AppendLine($"\tStart Offset: 0x{varLoc.StartOffset:X}"); + sb.AppendLine($"\tEnd Offset: 0x{varLoc.EndOffset:X}"); + sb.AppendLine($"\tLoc Type: {varLoc.VariableLocation.VarLocType}"); + + switch (varLoc.VariableLocation.VarLocType) + { + case VarLocType.VLT_REG: + case VarLocType.VLT_REG_FP: + case VarLocType.VLT_REG_BYREF: + sb.AppendLine($"\tRegister: {GetPlatformSpecificRegister(_machine, varLoc.VariableLocation.Data1)}"); + break; + case VarLocType.VLT_STK: + case VarLocType.VLT_STK_BYREF: + sb.AppendLine($"\tBase Register: {GetPlatformSpecificRegister(_machine, varLoc.VariableLocation.Data1)}"); + sb.AppendLine($"\tStack Offset: {varLoc.VariableLocation.Data2}"); + break; + case VarLocType.VLT_REG_REG: + sb.AppendLine($"\tRegister 1: {GetPlatformSpecificRegister(_machine, varLoc.VariableLocation.Data1)}"); + sb.AppendLine($"\tRegister 2: {GetPlatformSpecificRegister(_machine, varLoc.VariableLocation.Data2)}"); + break; + case VarLocType.VLT_REG_STK: + sb.AppendLine($"\tRegister: {GetPlatformSpecificRegister(_machine, varLoc.VariableLocation.Data1)}"); + sb.AppendLine($"\tBase Register: {GetPlatformSpecificRegister(_machine, varLoc.VariableLocation.Data2)}"); + sb.AppendLine($"\tStack Offset: {varLoc.VariableLocation.Data3}"); + break; + case VarLocType.VLT_STK_REG: + sb.AppendLine($"\tStack Offset: {varLoc.VariableLocation.Data1}"); + sb.AppendLine($"\tBase Register: {GetPlatformSpecificRegister(_machine, varLoc.VariableLocation.Data2)}"); + sb.AppendLine($"\tRegister: {GetPlatformSpecificRegister(_machine, varLoc.VariableLocation.Data3)}"); + break; + case VarLocType.VLT_STK2: + sb.AppendLine($"\tBase Register: {GetPlatformSpecificRegister(_machine, varLoc.VariableLocation.Data1)}"); + sb.AppendLine($"\tStack Offset: {varLoc.VariableLocation.Data2}"); + break; + case VarLocType.VLT_FPSTK: + sb.AppendLine($"\tOffset: {GetPlatformSpecificRegister(_machine, varLoc.VariableLocation.Data1)}"); + break; + case VarLocType.VLT_FIXED_VA: + sb.AppendLine($"\tOffset: {GetPlatformSpecificRegister(_machine, varLoc.VariableLocation.Data1)}"); + break; + default: + throw new BadImageFormatException("Unexpected var loc type"); + } + + sb.AppendLine(""); + } + + return sb.ToString(); + } + + /// <summary> + /// Convert a register number in debug info into a machine-specific register + /// </summary> + private static string GetPlatformSpecificRegister(Machine machine, int regnum) + { + switch (machine) + { + case Machine.I386: + return ((x86.Registers)regnum).ToString(); + case Machine.Amd64: + return ((Amd64.Registers)regnum).ToString(); + case Machine.Arm: + return ((Arm.Registers)regnum).ToString(); + case Machine.Arm64: + return ((Arm64.Registers)regnum).ToString(); + default: + throw new NotImplementedException($"No implementation for machine type {machine}."); + } + } + + private void ParseBounds(byte[] image, int offset) + { + // Bounds info contains (Native Offset, IL Offset, flags) + // - Sorted by native offset (so use a delta encoding for that). + // - IL offsets aren't sorted, but they should be close to each other (so a signed delta encoding) + // They may also include a sentinel value from MappingTypes. + // - flags is 3 indepedent bits. + NibbleReader reader = new NibbleReader(image, offset); + uint boundsEntryCount = reader.ReadUInt(); + Debug.Assert(boundsEntryCount > 0); + + uint previousNativeOffset = 0; + for (int i = 0; i < boundsEntryCount; ++i) + { + var entry = new DebugInfoBoundsEntry(); + previousNativeOffset += reader.ReadUInt(); + entry.NativeOffset = previousNativeOffset; + entry.ILOffset = (uint)(reader.ReadUInt() + (int)MappingTypes.MaxMappingValue); + entry.SourceTypes = (SourceTypes)reader.ReadUInt(); + _boundsList.Add(entry); + } + } + + private void ParseNativeVarInfo(byte[] image, int offset) + { + // Each Varinfo has a: + // - native start +End offset. We can use a delta for the end offset. + // - Il variable number. These are usually small. + // - VarLoc information. This is a tagged variant. + // The entries aren't sorted in any particular order. + NibbleReader reader = new NibbleReader(image, offset); + uint nativeVarCount = reader.ReadUInt(); + + for (int i = 0; i < nativeVarCount; ++i) + { + var entry = new NativeVarInfo(); + entry.StartOffset = reader.ReadUInt(); + entry.EndOffset = entry.StartOffset + reader.ReadUInt(); + entry.VariableNumber = (uint)(reader.ReadUInt() + (int)ImplicitILArguments.Max); + + var varLoc = new VarLoc(); + varLoc.VarLocType = (VarLocType)reader.ReadUInt(); + switch (varLoc.VarLocType) + { + case VarLocType.VLT_REG: + case VarLocType.VLT_REG_FP: + case VarLocType.VLT_REG_BYREF: + varLoc.Data1 = (int)reader.ReadUInt(); + break; + case VarLocType.VLT_STK: + case VarLocType.VLT_STK_BYREF: + varLoc.Data1 = (int)reader.ReadUInt(); + varLoc.Data2 = ReadEncodedStackOffset(reader); + break; + case VarLocType.VLT_REG_REG: + varLoc.Data1 = (int)reader.ReadUInt(); + varLoc.Data2 = (int)reader.ReadUInt(); + break; + case VarLocType.VLT_REG_STK: + varLoc.Data1 = (int)reader.ReadUInt(); + varLoc.Data2 = (int)reader.ReadUInt(); + varLoc.Data3 = ReadEncodedStackOffset(reader); + break; + case VarLocType.VLT_STK_REG: + varLoc.Data1 = ReadEncodedStackOffset(reader); + varLoc.Data2 = (int)reader.ReadUInt(); + varLoc.Data3 = (int)reader.ReadUInt(); + break; + case VarLocType.VLT_STK2: + varLoc.Data1 = (int)reader.ReadUInt(); + varLoc.Data2 = ReadEncodedStackOffset(reader); + break; + case VarLocType.VLT_FPSTK: + varLoc.Data1 = (int)reader.ReadUInt(); + break; + case VarLocType.VLT_FIXED_VA: + varLoc.Data1 = (int)reader.ReadUInt(); + break; + default: + throw new BadImageFormatException("Unexpected var loc type"); + } + + entry.VariableLocation = varLoc; + _variablesList.Add(entry); + } + } + + private int ReadEncodedStackOffset(NibbleReader reader) + { + int offset = reader.ReadInt(); + if (_machine == Machine.I386) + { + offset *= 4; // sizeof(DWORD) + } + + return offset; + } + } +} diff --git a/src/tools/r2rdump/DebugInfoTypes.cs b/src/tools/r2rdump/DebugInfoTypes.cs new file mode 100644 index 0000000000..754e080edc --- /dev/null +++ b/src/tools/r2rdump/DebugInfoTypes.cs @@ -0,0 +1,97 @@ +// 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. + +using System; + +namespace R2RDump +{ + struct DebugInfoBoundsEntry + { + public uint NativeOffset; + public uint ILOffset; + public SourceTypes SourceTypes; + } + + struct NativeVarInfo + { + public uint StartOffset; + public uint EndOffset; + public uint VariableNumber; + public VarLoc VariableLocation; + } + + [Flags] + enum SourceTypes + { + /// <summary> + /// Indicates that no other options apply + /// </summary> + SourceTypeInvalid = 0x00, + /// <summary> + /// The debugger asked for it + /// </summary> + SequencePoint = 0x01, + /// <summary> + /// The stack is empty here + /// </summary> + StackEmpty = 0x02, + /// <summary> + /// This is a call site + /// </summary> + CallSite = 0x04, + /// <summary> + /// Indicate an epilog endpoint + /// </summary> + NativeEndOffsetUnknown = 0x08, + /// <summary> + /// The actual instruction of a call + /// </summary> + CallInstruction = 0x10 + } + + enum MappingTypes : int + { + NoMapping = -1, + Prolog = -2, + Epilog = -3, + MaxMappingValue = Epilog + } + + enum ImplicitILArguments + { + VarArgsHandle = -1, + ReturnBuffer = -2, + TypeContext = -3, + Unknown = -4, + Max = Unknown + } + + enum VarLocType + { + VLT_REG, // variable is in a register + VLT_REG_BYREF, // address of the variable is in a register + VLT_REG_FP, // variable is in an fp register + VLT_STK, // variable is on the stack (memory addressed relative to the frame-pointer) + VLT_STK_BYREF, // address of the variable is on the stack (memory addressed relative to the frame-pointer) + VLT_REG_REG, // variable lives in two registers + VLT_REG_STK, // variable lives partly in a register and partly on the stack + VLT_STK_REG, // reverse of VLT_REG_STK + VLT_STK2, // variable lives in two slots on the stack + VLT_FPSTK, // variable lives on the floating-point stack + VLT_FIXED_VA, // variable is a fixed argument in a varargs function (relative to VARARGS_HANDLE) + + VLT_COUNT, + VLT_INVALID, + } + + struct VarLoc + { + public VarLocType VarLocType; + // What's stored in the Data# fields changes based on VarLocType and will be + // interpreted accordingly when the variable location information is dumped. + public int Data1; + public int Data2; + public int Data3; + } +} diff --git a/src/tools/r2rdump/NibbleReader.cs b/src/tools/r2rdump/NibbleReader.cs index 341c1d029a..b81e3946c8 100644 --- a/src/tools/r2rdump/NibbleReader.cs +++ b/src/tools/r2rdump/NibbleReader.cs @@ -86,5 +86,10 @@ namespace R2RDump int signedValue = (int)(unsignedValue >> 1); return ((unsignedValue & 1) != 0 ? -signedValue : signedValue); } + + /// <summary> + /// + /// </summary> + public int GetNextByteOffset() => _offset; } } diff --git a/src/tools/r2rdump/R2RMethod.cs b/src/tools/r2rdump/R2RMethod.cs index 7f5ae6974b..a087dc4c5c 100644 --- a/src/tools/r2rdump/R2RMethod.cs +++ b/src/tools/r2rdump/R2RMethod.cs @@ -82,15 +82,19 @@ namespace R2RDump public BaseUnwindInfo UnwindInfo { get; } + public DebugInfo DebugInfo { get; } + public RuntimeFunction() { } - public RuntimeFunction(int id, int startRva, int endRva, int unwindRva, int codeOffset, R2RMethod method, BaseUnwindInfo unwindInfo, BaseGcInfo gcInfo) + public RuntimeFunction(int id, int startRva, int endRva, int unwindRva, int codeOffset, R2RMethod method, BaseUnwindInfo unwindInfo, BaseGcInfo gcInfo, DebugInfo debugInfo) { Id = id; StartAddress = startRva; UnwindRVA = unwindRva; Method = method; UnwindInfo = unwindInfo; + DebugInfo = debugInfo; + if (endRva != -1) { Size = endRva - startRva; @@ -174,6 +178,11 @@ namespace R2RDump } sb.AppendLine(); + if (DebugInfo != null) + { + sb.AppendLine(DebugInfo.ToString()); + } + return sb.ToString(); } } diff --git a/src/tools/r2rdump/R2RReader.cs b/src/tools/r2rdump/R2RReader.cs index 83d2a80cc6..0da6e98f8d 100644 --- a/src/tools/r2rdump/R2RReader.cs +++ b/src/tools/r2rdump/R2RReader.cs @@ -127,6 +127,8 @@ namespace R2RDump /// </summary> public Dictionary<int, string> ImportCellNames { get; } + private Dictionary<int, DebugInfo> _runtimeFunctionToDebugInfo = new Dictionary<int, DebugInfo>(); + public unsafe R2RReader() { } /// <summary> @@ -180,6 +182,8 @@ namespace R2RDump { MetadataReader = PEReader.GetMetadataReader(); + ParseDebugInfo(); + R2RMethods = new List<R2RMethod>(); if (R2RHeader.Sections.ContainsKey(R2RSection.SectionType.READYTORUN_SECTION_RUNTIME_FUNCTIONS)) { @@ -376,7 +380,7 @@ namespace R2RDump } } - RuntimeFunction rtf = new RuntimeFunction(runtimeFunctionId, startRva, endRva, unwindRva, codeOffset, method, unwindInfo, gcInfo); + RuntimeFunction rtf = new RuntimeFunction(runtimeFunctionId, startRva, endRva, unwindRva, codeOffset, method, unwindInfo, gcInfo, _runtimeFunctionToDebugInfo.GetValueOrDefault(runtimeFunctionId)); method.RuntimeFunctions.Add(rtf); runtimeFunctionId++; codeOffset += rtf.Size; @@ -523,6 +527,30 @@ namespace R2RDump } } + private void ParseDebugInfo() + { + if (!R2RHeader.Sections.ContainsKey(R2RSection.SectionType.READYTORUN_SECTION_DEBUG_INFO)) + { + return; + } + + R2RSection debugInfoSection = R2RHeader.Sections[R2RSection.SectionType.READYTORUN_SECTION_DEBUG_INFO]; + int debugInfoSectionOffset = GetOffset(debugInfoSection.RelativeVirtualAddress); + + NativeArray debugInfoArray = new NativeArray(Image, (uint)debugInfoSectionOffset); + for (uint i = 0; i < debugInfoArray.GetCount(); ++i) + { + int offset = 0; + if (!debugInfoArray.TryGetAt(Image, i, ref offset)) + { + continue; + } + + var debugInfo = new DebugInfo(Image, offset, Machine); + _runtimeFunctionToDebugInfo.Add((int)i, debugInfo); + } + } + /// <summary> /// Get the index in the image byte array corresponding to the RVA /// </summary> |