// 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.IO;
using System.Reflection.PortableExecutable;
using System.Text;
namespace R2RDump
{
///
/// Represents the debug information for a single method in the ready-to-run image.
/// See src\inc\cordebuginfo.h for
/// the fundamental types this is based on.
///
public class DebugInfo
{
private List _boundsList = new List();
private List _variablesList = new List();
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 void WriteTo(TextWriter writer, DumpOptions dumpOptions)
{
if (_boundsList.Count > 0)
writer.WriteLine("Debug Info");
writer.WriteLine("\tBounds:");
for (int i = 0; i < _boundsList.Count; ++i)
{
writer.Write('\t');
if (!dumpOptions.Naked)
{
writer.Write($"Native Offset: 0x{_boundsList[i].NativeOffset:X}, ");
}
writer.WriteLine($"IL Offset: 0x{_boundsList[i].ILOffset:X}, Source Types: {_boundsList[i].SourceTypes}");
}
writer.WriteLine("");
if (_variablesList.Count > 0)
writer.WriteLine("\tVariable Locations:");
for (int i = 0; i < _variablesList.Count; ++i)
{
var varLoc = _variablesList[i];
writer.WriteLine($"\tVariable Number: {varLoc.VariableNumber}");
writer.WriteLine($"\tStart Offset: 0x{varLoc.StartOffset:X}");
writer.WriteLine($"\tEnd Offset: 0x{varLoc.EndOffset:X}");
writer.WriteLine($"\tLoc Type: {varLoc.VariableLocation.VarLocType}");
switch (varLoc.VariableLocation.VarLocType)
{
case VarLocType.VLT_REG:
case VarLocType.VLT_REG_FP:
case VarLocType.VLT_REG_BYREF:
writer.WriteLine($"\tRegister: {GetPlatformSpecificRegister(_machine, varLoc.VariableLocation.Data1)}");
break;
case VarLocType.VLT_STK:
case VarLocType.VLT_STK_BYREF:
writer.WriteLine($"\tBase Register: {GetPlatformSpecificRegister(_machine, varLoc.VariableLocation.Data1)}");
writer.WriteLine($"\tStack Offset: {varLoc.VariableLocation.Data2}");
break;
case VarLocType.VLT_REG_REG:
writer.WriteLine($"\tRegister 1: {GetPlatformSpecificRegister(_machine, varLoc.VariableLocation.Data1)}");
writer.WriteLine($"\tRegister 2: {GetPlatformSpecificRegister(_machine, varLoc.VariableLocation.Data2)}");
break;
case VarLocType.VLT_REG_STK:
writer.WriteLine($"\tRegister: {GetPlatformSpecificRegister(_machine, varLoc.VariableLocation.Data1)}");
writer.WriteLine($"\tBase Register: {GetPlatformSpecificRegister(_machine, varLoc.VariableLocation.Data2)}");
writer.WriteLine($"\tStack Offset: {varLoc.VariableLocation.Data3}");
break;
case VarLocType.VLT_STK_REG:
writer.WriteLine($"\tStack Offset: {varLoc.VariableLocation.Data1}");
writer.WriteLine($"\tBase Register: {GetPlatformSpecificRegister(_machine, varLoc.VariableLocation.Data2)}");
writer.WriteLine($"\tRegister: {GetPlatformSpecificRegister(_machine, varLoc.VariableLocation.Data3)}");
break;
case VarLocType.VLT_STK2:
writer.WriteLine($"\tBase Register: {GetPlatformSpecificRegister(_machine, varLoc.VariableLocation.Data1)}");
writer.WriteLine($"\tStack Offset: {varLoc.VariableLocation.Data2}");
break;
case VarLocType.VLT_FPSTK:
writer.WriteLine($"\tOffset: {GetPlatformSpecificRegister(_machine, varLoc.VariableLocation.Data1)}");
break;
case VarLocType.VLT_FIXED_VA:
writer.WriteLine($"\tOffset: {GetPlatformSpecificRegister(_machine, varLoc.VariableLocation.Data1)}");
break;
default:
throw new BadImageFormatException("Unexpected var loc type");
}
writer.WriteLine("");
}
}
///
/// Convert a register number in debug info into a machine-specific register
///
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;
}
}
}