diff options
author | Tomáš Rylek <trylek@microsoft.com> | 2018-09-18 14:41:05 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-09-18 14:41:05 +0200 |
commit | 9d3e96c3afe522f6f509628a856c41fe91d9da33 (patch) | |
tree | 8959d4c8eb9b86a65c0267f075de1e192d830d44 /src/tools | |
parent | d81f2635cc2f26370ea691b9d109cc8246d86472 (diff) | |
download | coreclr-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.cs | 260 | ||||
-rw-r--r-- | src/tools/r2rdump/R2RMethod.cs | 22 | ||||
-rw-r--r-- | src/tools/r2rdump/R2RReader.cs | 29 |
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; |