summaryrefslogtreecommitdiff
path: root/src/tools
diff options
context:
space:
mode:
authorTomáš Rylek <trylek@microsoft.com>2018-09-18 14:41:05 +0200
committerGitHub <noreply@github.com>2018-09-18 14:41:05 +0200
commit9d3e96c3afe522f6f509628a856c41fe91d9da33 (patch)
tree8959d4c8eb9b86a65c0267f075de1e192d830d44 /src/tools
parentd81f2635cc2f26370ea691b9d109cc8246d86472 (diff)
downloadcoreclr-9d3e96c3afe522f6f509628a856c41fe91d9da33.tar.gz
coreclr-9d3e96c3afe522f6f509628a856c41fe91d9da33.tar.bz2
coreclr-9d3e96c3afe522f6f509628a856c41fe91d9da33.zip
Add EH info support to R2RDump (#20000)
* Add EH info support to R2RDump This change expands runtime function dump to include the exception handling info looked up via the EXCEPTION_INFO R2R header table. * Address Zach's PR feedback 1) Base EHClause.Length on sizeof(uint) instead of sizeof(int) for consistency with the asctual data types in the class. 2) Fix my overlooking that Zach spotted - in the EHLookupTable ctor, we need to assign rva2 and eh2 to rva1 and eh1 at the end of the loop block as we need to traverse consecutive pairs of CORCOMPILE_EXCEPTION_LOOKUP_TABLE_ENTRY elements. * Addressed Bruce Forstall's PR feedback 1) I modified the EH info method ctors to accept the R2RReader instead of the raw byte[] image as the reader can be used to provide textual representations of metadata tokens. 2) I changed the mask test to a switch that throws an exception if multiple of the three lowest bits are set which is illegal according to Bruce. * Addressed additional Bruce Forstall's PR feedback Remove interpretation of ClassTokenOrFilterOffset as it's not applicable in the FINALLY and FAULT cases. Thanks Tomas
Diffstat (limited to 'src/tools')
-rw-r--r--src/tools/r2rdump/EHInfo.cs260
-rw-r--r--src/tools/r2rdump/R2RMethod.cs22
-rw-r--r--src/tools/r2rdump/R2RReader.cs29
3 files changed, 309 insertions, 2 deletions
diff --git a/src/tools/r2rdump/EHInfo.cs b/src/tools/r2rdump/EHInfo.cs
new file mode 100644
index 0000000000..818b343482
--- /dev/null
+++ b/src/tools/r2rdump/EHInfo.cs
@@ -0,0 +1,260 @@
+// 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.Metadata;
+using System.Reflection.Metadata.Ecma335;
+using System.Reflection.PortableExecutable;
+using System.Text;
+
+namespace R2RDump
+{
+ /// <summary>
+ /// If COR_ILMETHOD_SECT_HEADER::Kind() = CorILMethod_Sect_EHTable then the attribute
+ /// is a list of exception handling clauses. There are two formats, fat or small
+ /// </summary>
+ [Flags]
+ public enum CorExceptionFlag
+ {
+ COR_ILEXCEPTION_CLAUSE_NONE, // This is a typed handler
+ COR_ILEXCEPTION_CLAUSE_OFFSETLEN = 0x0000, // Deprecated
+ COR_ILEXCEPTION_CLAUSE_DEPRECATED = 0x0000, // Deprecated
+ COR_ILEXCEPTION_CLAUSE_FILTER = 0x0001, // If this bit is on, then this EH entry is for a filter
+ COR_ILEXCEPTION_CLAUSE_FINALLY = 0x0002, // This clause is a finally clause
+ COR_ILEXCEPTION_CLAUSE_FAULT = 0x0004, // Fault clause (finally that is called on exception only)
+ COR_ILEXCEPTION_CLAUSE_DUPLICATED = 0x0008, // duplicated clause. This clause was duplicated to a funclet which was pulled out of line
+
+ COR_ILEXCEPTION_CLAUSE_KIND_MASK = COR_ILEXCEPTION_CLAUSE_FILTER | COR_ILEXCEPTION_CLAUSE_FINALLY | COR_ILEXCEPTION_CLAUSE_FAULT,
+ }
+
+ /// <summary>
+ /// This class represents a single exception handling clause. It basically corresponds
+ /// to IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_FAT in
+ /// <a href="https://github.com/dotnet/coreclr/blob/master/src/inc/cordebuginfo.h">src\inc\corhdr.h</a>.
+ /// </summary>
+ public class EHClause
+ {
+ /// <summary>
+ /// Length of the serialized EH clause in the PE image.
+ /// </summary>
+ public const int Length = 6 * sizeof(uint);
+
+ /// <summary>
+ /// Flags describing the exception handler.
+ /// </summary>
+ CorExceptionFlag Flags;
+
+ /// <summary>
+ /// Starting offset of the try block
+ /// </summary>
+ public uint TryOffset;
+
+ /// <summary>
+ /// End offset of the try block
+ /// </summary>
+ public uint TryEnd;
+
+ /// <summary>
+ /// Offset of the exception handler for the try block
+ /// </summary>
+ public uint HandlerOffset;
+
+ /// <summary>
+ /// End offset of the exception handler
+ /// </summary>
+ public uint HandlerEnd;
+
+ /// <summary>
+ /// For type-based exception handlers, this is the type token.
+ /// For filter-based exception handlers, this is the filter offset.
+ /// </summary>
+ public uint ClassTokenOrFilterOffset;
+
+ /// <summary>
+ /// Textual representation of the class represented by the class token.
+ /// </summary>
+ public string ClassName;
+
+ /// <summary>
+ /// Read the EH clause from a given file offset in the PE image.
+ /// </summary>
+ /// <param name="reader">R2R image reader<param>
+ /// <param name="offset">Offset of the EH clause in the image</param>
+ public EHClause(R2RReader reader, int offset)
+ {
+ Flags = (CorExceptionFlag)BitConverter.ToUInt32(reader.Image, offset + 0 * sizeof(uint));
+ TryOffset = BitConverter.ToUInt32(reader.Image, offset + 1 * sizeof(uint));
+ TryEnd = BitConverter.ToUInt32(reader.Image, offset + 2 * sizeof(uint));
+ HandlerOffset = BitConverter.ToUInt32(reader.Image, offset + 3 * sizeof(uint));
+ HandlerEnd = BitConverter.ToUInt32(reader.Image, offset + 4 * sizeof(uint));
+ ClassTokenOrFilterOffset = BitConverter.ToUInt32(reader.Image, offset + 5 * sizeof(uint));
+
+ if ((Flags & CorExceptionFlag.COR_ILEXCEPTION_CLAUSE_KIND_MASK) == CorExceptionFlag.COR_ILEXCEPTION_CLAUSE_NONE)
+ {
+ ClassName = MetadataNameFormatter.FormatHandle(reader.MetadataReader, MetadataTokens.Handle((int)ClassTokenOrFilterOffset));
+ }
+ }
+
+ /// <summary>
+ /// Emit a textual representation of the EH info to a given string builder.
+ /// </summary>
+ /// <param name="stringBuilder">Output builder for the textual representation</param>
+ /// <param name="methodRva">Starting RVA of the runtime function is used to display the try / handler info as RVA intervals</param>
+ public void WriteTo(StringBuilder stringBuilder, int methodRva)
+ {
+ stringBuilder.Append($@"Flags {(uint)Flags:X2} ");
+ stringBuilder.Append($@"TryOff {TryOffset:X4} (RVA {(TryOffset + methodRva):X4}) ");
+ stringBuilder.Append($@"TryEnd {TryEnd:X4} (RVA {(TryEnd + methodRva):X4}) ");
+ stringBuilder.Append($@"HndOff {HandlerOffset:X4} (RVA {(HandlerOffset + methodRva):X4}) ");
+ stringBuilder.Append($@"HndEnd {HandlerEnd:X4} (RVA {(HandlerEnd + methodRva):X4}) ");
+ stringBuilder.Append($@"ClsFlt {ClassTokenOrFilterOffset:X4}");
+
+ switch (Flags & CorExceptionFlag.COR_ILEXCEPTION_CLAUSE_KIND_MASK)
+ {
+ case CorExceptionFlag.COR_ILEXCEPTION_CLAUSE_NONE:
+ stringBuilder.AppendFormat(" CATCH: {0}", ClassName ?? "null");
+ break;
+
+ case CorExceptionFlag.COR_ILEXCEPTION_CLAUSE_FILTER:
+ stringBuilder.AppendFormat(" FILTER (RVA {0:X4})", ClassTokenOrFilterOffset + methodRva);
+ break;
+
+ case CorExceptionFlag.COR_ILEXCEPTION_CLAUSE_FINALLY:
+ stringBuilder.AppendFormat(" FINALLY");
+ break;
+
+ case CorExceptionFlag.COR_ILEXCEPTION_CLAUSE_FAULT:
+ stringBuilder.AppendFormat(" FAULT");
+ break;
+
+ default:
+ throw new NotImplementedException(Flags.ToString());
+ }
+
+ if ((Flags & CorExceptionFlag.COR_ILEXCEPTION_CLAUSE_DUPLICATED) != (CorExceptionFlag)0)
+ {
+ stringBuilder.Append(" DUPLICATED");
+ }
+ }
+ }
+
+ /// <summary>
+ /// This class represents EH info for a single runtime function. EH info
+ /// is located using the map from runtime functions to EH clause lists in
+ /// the READYTORUN_SECTION_EXCEPTION_INFO header table.
+ /// </summary>
+ public class EHInfo
+ {
+ /// <summary>
+ /// RVA of the EH info in the PE image.
+ /// </summary>
+ public readonly int EHInfoRVA;
+
+ /// <summary>
+ /// Starting RVA of the corresponding runtime function.
+ /// </summary>
+ public readonly int MethodRVA;
+
+ /// <summary>
+ /// List of EH clauses for the runtime function.
+ /// </summary>
+ public readonly EHClause[] EHClauses;
+
+ /// <summary>
+ /// Construct the EH info for a given runtime method by reading it from a given offset
+ /// in the R2R PE executable. The offset is located by looking up the starting
+ /// IP address of the runtime function in the READYTORUN_SECTION_EXCEPTION_INFO table.
+ /// The length of the
+ /// </summary>
+ /// <param name="reader">R2R PE image reader</param>
+ /// <param name="ehInfoRva">RVA of the EH info</param>
+ /// <param name="methodRva">Starting RVA of the runtime function</param>
+ /// <param name="offset">File offset of the EH info</param>
+ /// <param name="clauseCount">Number of EH info clauses</param>
+ public EHInfo(R2RReader reader, int ehInfoRva, int methodRva, int offset, int clauseCount)
+ {
+ EHInfoRVA = ehInfoRva;
+ MethodRVA = methodRva;
+ EHClauses = new EHClause[clauseCount];
+ for (int clauseIndex = 0; clauseIndex < clauseCount; clauseIndex++)
+ {
+ EHClauses[clauseIndex] = new EHClause(reader, offset + clauseIndex * EHClause.Length);
+ }
+ }
+
+ /// <summary>
+ /// Emit the textual representation of the EH info into a given writer.
+ /// </summary>
+ public void WriteTo(StringBuilder stringBuilder)
+ {
+ foreach (EHClause ehClause in EHClauses)
+ {
+ ehClause.WriteTo(stringBuilder, MethodRVA);
+ stringBuilder.AppendLine();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Location of the EH clauses for a single runtime function
+ /// </summary>
+ public class EHInfoLocation
+ {
+ /// <summary>
+ /// RVA of the EH clause list in the R2R PE image
+ /// </summary>
+ public readonly int EHInfoRVA;
+
+ /// <summary>
+ /// Number of EH clauses
+ /// </summary>
+ public readonly int ClauseCount;
+
+ public EHInfoLocation(int ehInfoRva, int clauseCount)
+ {
+ EHInfoRVA = ehInfoRva;
+ ClauseCount = clauseCount;
+ }
+ }
+
+ /// <summary>
+ /// Lookup table maps IP RVAs to EH info.
+ /// </summary>
+ public class EHLookupTable
+ {
+ /// <summary>
+ /// Map from runtime function RVA's to EH info location in the R2R PE file.
+ /// </summary>
+ public readonly Dictionary<int, EHInfoLocation> RuntimeFunctionToEHInfoMap;
+
+ /// <summary>
+ /// Reads the EH info lookup table from the PE image.
+ /// </summary>
+ /// <param name="image">R2R PE image</param>
+ /// <param name="offset">Starting offset of the EH info in the PE image</param>
+ /// <param name="length">Byte length of the EH info</param>
+ public EHLookupTable(byte[] image, int offset, int length)
+ {
+ RuntimeFunctionToEHInfoMap = new Dictionary<int, EHInfoLocation>();
+
+ int rva1 = BitConverter.ToInt32(image, offset);
+ int eh1 = BitConverter.ToInt32(image, offset + sizeof(uint));
+
+ while ((length -= 2 * sizeof(uint)) >= 8)
+ {
+ offset += 2 * sizeof(uint);
+ int rva2 = BitConverter.ToInt32(image, offset);
+ int eh2 = BitConverter.ToInt32(image, offset + sizeof(uint));
+
+ RuntimeFunctionToEHInfoMap.Add(rva1, new EHInfoLocation(eh1, (eh2 - eh1) / EHClause.Length));
+
+ rva1 = rva2;
+ eh1 = eh2;
+ }
+ }
+ }
+}
diff --git a/src/tools/r2rdump/R2RMethod.cs b/src/tools/r2rdump/R2RMethod.cs
index a087dc4c5c..1ea77ea04b 100644
--- a/src/tools/r2rdump/R2RMethod.cs
+++ b/src/tools/r2rdump/R2RMethod.cs
@@ -82,11 +82,23 @@ namespace R2RDump
public BaseUnwindInfo UnwindInfo { get; }
+ public EHInfo EHInfo { 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, DebugInfo debugInfo)
+ public RuntimeFunction(
+ int id,
+ int startRva,
+ int endRva,
+ int unwindRva,
+ int codeOffset,
+ R2RMethod method,
+ BaseUnwindInfo unwindInfo,
+ BaseGcInfo gcInfo,
+ EHInfo ehInfo,
+ DebugInfo debugInfo)
{
Id = id;
StartAddress = startRva;
@@ -121,6 +133,7 @@ namespace R2RDump
}
CodeOffset = codeOffset;
method.GcInfo = gcInfo;
+ EHInfo = ehInfo;
}
public override string ToString()
@@ -178,6 +191,13 @@ namespace R2RDump
}
sb.AppendLine();
+ if (EHInfo != null)
+ {
+ sb.AppendLine($@"EH info @ {EHInfo.EHInfoRVA:X4}, #clauses = {EHInfo.EHClauses.Length}");
+ EHInfo.WriteTo(sb);
+ sb.AppendLine();
+ }
+
if (DebugInfo != null)
{
sb.AppendLine(DebugInfo.ToString());
diff --git a/src/tools/r2rdump/R2RReader.cs b/src/tools/r2rdump/R2RReader.cs
index 0da6e98f8d..c7da57857a 100644
--- a/src/tools/r2rdump/R2RReader.cs
+++ b/src/tools/r2rdump/R2RReader.cs
@@ -118,6 +118,11 @@ namespace R2RDump
public string CompilerIdentifier { get; }
/// <summary>
+ /// Exception lookup table is used to map runtime function addresses to EH clauses.
+ /// </summary>
+ public EHLookupTable EHLookupTable { get; }
+
+ /// <summary>
/// List of import sections present in the R2R executable.
/// </summary>
public IList<R2RImportSection> ImportSections { get; }
@@ -184,6 +189,9 @@ namespace R2RDump
ParseDebugInfo();
+ R2RSection exceptionInfoSection = R2RHeader.Sections[R2RSection.SectionType.READYTORUN_SECTION_EXCEPTION_INFO];
+ EHLookupTable = new EHLookupTable(Image, GetOffset(exceptionInfoSection.RelativeVirtualAddress), exceptionInfoSection.Size);
+
R2RMethods = new List<R2RMethod>();
if (R2RHeader.Sections.ContainsKey(R2RSection.SectionType.READYTORUN_SECTION_RUNTIME_FUNCTIONS))
{
@@ -380,7 +388,26 @@ namespace R2RDump
}
}
- RuntimeFunction rtf = new RuntimeFunction(runtimeFunctionId, startRva, endRva, unwindRva, codeOffset, method, unwindInfo, gcInfo, _runtimeFunctionToDebugInfo.GetValueOrDefault(runtimeFunctionId));
+ EHInfo ehInfo = null;
+
+ EHInfoLocation ehInfoLocation;
+ if (EHLookupTable.RuntimeFunctionToEHInfoMap.TryGetValue(startRva, out ehInfoLocation))
+ {
+ ehInfo = new EHInfo(this, ehInfoLocation.EHInfoRVA, startRva, GetOffset(ehInfoLocation.EHInfoRVA), ehInfoLocation.ClauseCount);
+ }
+
+ RuntimeFunction rtf = new RuntimeFunction(
+ runtimeFunctionId,
+ startRva,
+ endRva,
+ unwindRva,
+ codeOffset,
+ method,
+ unwindInfo,
+ gcInfo,
+ ehInfo,
+ _runtimeFunctionToDebugInfo.GetValueOrDefault(runtimeFunctionId));
+
method.RuntimeFunctions.Add(rtf);
runtimeFunctionId++;
codeOffset += rtf.Size;