summaryrefslogtreecommitdiff
path: root/src/tools
diff options
context:
space:
mode:
authorSimon Nattress <simonn@microsoft.com>2018-09-07 13:41:30 -0700
committerSimon Nattress <simonn@microsoft.com>2018-09-08 10:31:16 -0700
commitb5cbf4dd84bcf22950d4f6d665723c7d23981272 (patch)
tree5bda9425e9e75b46c4f5b2546554863be51e90d8 /src/tools
parent51c3dc3bbd5e7515cbc03249c5e34b239a87b281 (diff)
downloadcoreclr-b5cbf4dd84bcf22950d4f6d665723c7d23981272.tar.gz
coreclr-b5cbf4dd84bcf22950d4f6d665723c7d23981272.tar.bz2
coreclr-b5cbf4dd84bcf22950d4f6d665723c7d23981272.zip
Add DebugInfo dumping support to r2rdump
Display formatted debug info for each runtime function
Diffstat (limited to 'src/tools')
-rw-r--r--src/tools/r2rdump/DebugInfo.cs245
-rw-r--r--src/tools/r2rdump/DebugInfoTypes.cs97
-rw-r--r--src/tools/r2rdump/NibbleReader.cs5
-rw-r--r--src/tools/r2rdump/R2RMethod.cs11
-rw-r--r--src/tools/r2rdump/R2RReader.cs30
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>