From d380f900e8adb2d62fc98d3a7e8ff0516e8492d0 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Tom=C3=A1=C5=A1=20Rylek?= Date: Tue, 13 Nov 2018 22:28:32 +0100 Subject: [PATCH] Initial support for normalized 'naked' R2RDump output (dotnet/coreclr#20875) * Initial support for normalized 'naked' R2RDump output This change introduces a new option "--naked" that takes output normalization even further - it intentionally leaves out any position information to make the output easier to diff between CPAOT and Crossgen. One other new option is "--entrypoints" which dumps a plain list of JITted methods in the R2R executable. This can be used for comparisons between CPAOT and Crossgen and / or for static analysis of what methods were actually emitted by the compiler. * Addressed Zach's PR feedback 1) Added argument consistency check for the invalid combination "--naked" + "--raw". 2) Added dump of multi-dimensional array lower bounds and sizes when available. Thanks Tomas Commit migrated from https://github.com/dotnet/coreclr/commit/e586793e0fbf0fa3d42e4519663ed2d977198a23 --- src/coreclr/src/tools/r2rdump/CoreDisTools.cs | 82 ++++++++++++++++-- src/coreclr/src/tools/r2rdump/R2RDump.cs | 28 ++++-- src/coreclr/src/tools/r2rdump/R2RMethod.cs | 31 +++---- src/coreclr/src/tools/r2rdump/R2RReader.cs | 20 +++-- src/coreclr/src/tools/r2rdump/R2RSignature.cs | 52 +++++++++++- src/coreclr/src/tools/r2rdump/TextDumper.cs | 117 ++++++++++++++++++-------- src/coreclr/src/tools/r2rdump/XmlDumper.cs | 11 +++ 7 files changed, 271 insertions(+), 70 deletions(-) diff --git a/src/coreclr/src/tools/r2rdump/CoreDisTools.cs b/src/coreclr/src/tools/r2rdump/CoreDisTools.cs index 66ed1c3..19117f7 100644 --- a/src/coreclr/src/tools/r2rdump/CoreDisTools.cs +++ b/src/coreclr/src/tools/r2rdump/CoreDisTools.cs @@ -91,6 +91,11 @@ namespace R2RDump private readonly R2RReader _reader; /// + /// Dump options + /// + private readonly DumpOptions _options; + + /// /// COM interface to the native disassembler in the CoreDisTools.dll library. /// private readonly IntPtr _disasm; @@ -99,9 +104,10 @@ namespace R2RDump /// Store the R2R reader and construct the disassembler for the appropriate architecture. /// /// - public Disassembler(R2RReader reader) + public Disassembler(R2RReader reader, DumpOptions options) { _reader = reader; + _options = options; _disasm = CoreDisTools.GetDisasm(_reader.Machine); } @@ -135,6 +141,32 @@ namespace R2RDump int instrSize = CoreDisTools.GetInstruction(_disasm, rtf, imageOffset, rtfOffset, _reader.Image, out instruction); instruction = instruction.Replace('\t', ' '); + if (_options.Naked) + { + StringBuilder nakedInstruction = new StringBuilder(); + foreach (string line in instruction.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries)) + { + int colon = line.IndexOf(':'); + if (colon >= 0) + { + colon += 2; + while (colon + 3 <= line.Length && + IsXDigit(line[colon]) && + IsXDigit(line[colon + 1]) && + line[colon + 2] == ' ') + { + colon += 3; + } + nakedInstruction.AppendLine(new string(' ', 32) + line.Substring(colon).TrimStart()); + } + else + { + nakedInstruction.AppendLine(line); + } + } + instruction = nakedInstruction.ToString(); + } + switch (_reader.Machine) { case Machine.Amd64: @@ -161,6 +193,11 @@ namespace R2RDump return instrSize; } + private static bool IsXDigit(char c) + { + return Char.IsDigit(c) || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); + } + const string RelIPTag = "[rip "; /// @@ -182,9 +219,24 @@ namespace R2RDump int newline = instruction.LastIndexOf('\n'); StringBuilder translated = new StringBuilder(); translated.Append(instruction, 0, leftBracket); - translated.AppendFormat("[0x{0:x4}]", target); + if (_options.Naked) + { + String targetName; + if (_reader.ImportCellNames.TryGetValue(target, out targetName)) + { + translated.AppendFormat("[{0}]", targetName); + } + else + { + translated.AppendFormat("[0x{0:x4}]", target); + } + } + else + { + translated.AppendFormat("[0x{0:x4}]", target); - AppendImportCellName(translated, target); + AppendImportCellName(translated, target); + } translated.Append(instruction, rightBracketPlusOne, newline - rightBracketPlusOne); @@ -216,9 +268,24 @@ namespace R2RDump StringBuilder translated = new StringBuilder(); translated.Append(instruction, 0, leftBracket); - translated.AppendFormat("[0x{0:x4}]", target); + if (_options.Naked) + { + String targetName; + if (_reader.ImportCellNames.TryGetValue(target, out targetName)) + { + translated.AppendFormat("[{0}]", targetName); + } + else + { + translated.AppendFormat("[0x{0:x4}]", target); + } + } + else + { + translated.AppendFormat("[0x{0:x4}]", target); - AppendImportCellName(translated, target); + AppendImportCellName(translated, target); + } translated.Append(instruction, rightBracketPlusOne, instruction.Length - rightBracketPlusOne); instruction = translated.ToString(); @@ -239,6 +306,11 @@ namespace R2RDump /// Textual representation of the instruction private void ProbeCommonIntelQuirks(RuntimeFunction rtf, int imageOffset, int rtfOffset, int instrSize, ref string instruction) { + if (_options.Naked) + { + // Don't relocate relative offsets in naked mode + return; + } if (instrSize == 2 && IsIntelJumpInstructionWithByteOffset(imageOffset + rtfOffset)) { sbyte offset = (sbyte)_reader.Image[imageOffset + rtfOffset + 1]; diff --git a/src/coreclr/src/tools/r2rdump/R2RDump.cs b/src/coreclr/src/tools/r2rdump/R2RDump.cs index e07e4f2..1ad800b 100644 --- a/src/coreclr/src/tools/r2rdump/R2RDump.cs +++ b/src/coreclr/src/tools/r2rdump/R2RDump.cs @@ -19,11 +19,13 @@ namespace R2RDump { public bool Raw; public bool Normalize; + public bool Naked; public bool Header; public bool Disasm; public bool Unwind; public bool GC; public bool SectionContents; + public bool EntryPoints; } public abstract class Dumper @@ -75,6 +77,7 @@ namespace R2RDump abstract internal void SkipLine(); abstract internal void DumpHeader(bool dumpSections); abstract internal void DumpSection(R2RSection section, XmlNode parentNode = null); + abstract internal void DumpEntryPoints(); abstract internal void DumpAllMethods(); abstract internal void DumpMethod(R2RMethod method, XmlNode parentNode = null); abstract internal void DumpRuntimeFunction(RuntimeFunction rtf, XmlNode parentNode = null); @@ -125,6 +128,7 @@ namespace R2RDump syntax.DefineOption("raw", ref _options.Raw, "Dump the raw bytes of each section or runtime function"); syntax.DefineOption("header", ref _options.Header, "Dump R2R header"); syntax.DefineOption("d|disasm", ref _options.Disasm, "Show disassembly of methods or runtime functions"); + syntax.DefineOption("naked", ref _options.Naked, "Naked dump suppresses most compilation details like placement addresses"); syntax.DefineOptionList("q|query", ref _queries, "Query method by exact name, signature, row id or token"); syntax.DefineOptionList("k|keyword", ref _keywords, "Search method by keyword"); syntax.DefineOptionList("r|runtimefunction", ref _runtimeFunctions, ArgStringToInt, "Get one runtime function by id or relative virtual address"); @@ -132,6 +136,7 @@ namespace R2RDump syntax.DefineOption("unwind", ref _options.Unwind, "Dump unwindInfo"); syntax.DefineOption("gc", ref _options.GC, "Dump gcInfo and slot table"); syntax.DefineOption("sc", ref _options.SectionContents, "Dump section contents"); + syntax.DefineOption("e|entrypoints", ref _options.EntryPoints, "Dump list of method / instance entrypoints in the R2R file"); syntax.DefineOption("n|normalize", ref _options.Normalize, "Normalize dump by sorting the various tables and methods (default = unsorted i.e. file order)"); syntax.DefineOption("v|verbose", ref verbose, "Dump disassembly, unwindInfo, gcInfo and section contents"); syntax.DefineOption("diff", ref _diff, "Compare two R2R images"); @@ -144,6 +149,7 @@ namespace R2RDump _options.Unwind = true; _options.GC = true; _options.SectionContents = true; + _options.EntryPoints = true; } return argSyntax; @@ -261,15 +267,22 @@ namespace R2RDump /// The structure containing the info of the ReadyToRun image public void Dump(R2RReader r2r) { - _dumper.Begin(); if (_queries.Count == 0 && _keywords.Count == 0 && _runtimeFunctions.Count == 0 && _sections.Count == 0) //dump all sections and methods if no queries specified { - _dumper.WriteDivider("R2R Header"); - _dumper.DumpHeader(true); + if (_options.Header || !_options.EntryPoints) + { + _dumper.WriteDivider("R2R Header"); + _dumper.DumpHeader(true); + } - if (!_options.Header) + if (_options.EntryPoints) + { + _dumper.DumpEntryPoints(); + } + + if (!_options.Header && !_options.EntryPoints) { _dumper.DumpAllMethods(); } @@ -426,6 +439,11 @@ namespace R2RDump if (_diff && _inputFilenames.Count < 2) throw new ArgumentException("Need at least 2 input files in diff mode"); + if (_options.Naked && _options.Raw) + { + throw new ArgumentException("The option '--naked' is incompatible with '--raw'"); + } + R2RReader previousReader = null; foreach (string filename in _inputFilenames) @@ -437,7 +455,7 @@ namespace R2RDump { if (r2r.InputArchitectureSupported() && r2r.DisassemblerArchitectureSupported()) { - disassembler = new Disassembler(r2r); + disassembler = new Disassembler(r2r, _options); } else { diff --git a/src/coreclr/src/tools/r2rdump/R2RMethod.cs b/src/coreclr/src/tools/r2rdump/R2RMethod.cs index 7155f75..592f123 100644 --- a/src/coreclr/src/tools/r2rdump/R2RMethod.cs +++ b/src/coreclr/src/tools/r2rdump/R2RMethod.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.IO; using System.Linq; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; @@ -372,28 +373,28 @@ namespace R2RDump SignatureString = sb.ToString(); } - public override string ToString() + public void WriteTo(TextWriter writer, DumpOptions options) { - StringBuilder sb = new StringBuilder(); + writer.WriteLine(SignatureString); - sb.AppendLine(SignatureString); - - sb.AppendLine($"Handle: 0x{MetadataTokens.GetToken(R2RReader.MetadataReader, MethodHandle):X8}"); - sb.AppendLine($"Rid: {MetadataTokens.GetRowNumber(R2RReader.MetadataReader, MethodHandle)}"); - sb.AppendLine($"EntryPointRuntimeFunctionId: {EntryPointRuntimeFunctionId}"); - sb.AppendLine($"Number of RuntimeFunctions: {RuntimeFunctions.Count}"); + writer.WriteLine($"Handle: 0x{MetadataTokens.GetToken(R2RReader.MetadataReader, MethodHandle):X8}"); + writer.WriteLine($"Rid: {MetadataTokens.GetRowNumber(R2RReader.MetadataReader, MethodHandle)}"); + writer.WriteLine($"EntryPointRuntimeFunctionId: {EntryPointRuntimeFunctionId}"); + writer.WriteLine($"Number of RuntimeFunctions: {RuntimeFunctions.Count}"); if (Fixups != null) { - sb.AppendLine($"Number of fixups: {Fixups.Count()}"); - foreach (FixupCell cell in Fixups) + writer.WriteLine($"Number of fixups: {Fixups.Count()}"); + IEnumerable fixups = Fixups; + if (options.Normalize) { - R2RImportSection importSection = R2RReader.ImportSections[(int)cell.TableIndex]; - R2RImportSection.ImportSectionEntry entry = importSection.Entries[(int)cell.CellOffset]; - sb.AppendLine($" TableIndex {cell.TableIndex}, Offset {cell.CellOffset:X4}: {entry.Signature}"); + fixups = fixups.OrderBy((fc) => fc.Signature); } - } - return sb.ToString(); + foreach (FixupCell cell in fixups) + { + writer.WriteLine($" TableIndex {cell.TableIndex}, Offset {cell.CellOffset:X4}: {cell.Signature}"); + } + } } } } diff --git a/src/coreclr/src/tools/r2rdump/R2RReader.cs b/src/coreclr/src/tools/r2rdump/R2RReader.cs index 63aa679..9d20d61 100644 --- a/src/coreclr/src/tools/r2rdump/R2RReader.cs +++ b/src/coreclr/src/tools/r2rdump/R2RReader.cs @@ -35,11 +35,17 @@ namespace R2RDump /// public uint CellOffset; - public FixupCell(int index, uint tableIndex, uint cellOffset) + /// + /// Fixup cell signature (textual representation of the typesystem object). + /// + public string Signature; + + public FixupCell(int index, uint tableIndex, uint cellOffset, string signature) { Index = index; TableIndex = tableIndex; CellOffset = cellOffset; + Signature = signature; } } @@ -212,6 +218,10 @@ namespace R2RDump EHLookupTable = new EHLookupTable(Image, GetOffset(exceptionInfoSection.RelativeVirtualAddress), exceptionInfoSection.Size); } + ImportSections = new List(); + ImportCellNames = new Dictionary(); + ParseImportSections(); + R2RMethods = new List(); InstanceMethods = new List(); @@ -234,10 +244,6 @@ namespace R2RDump ParseAvailableTypes(); CompilerIdentifier = ParseCompilerIdentifier(); - - ImportSections = new List(); - ImportCellNames = new Dictionary(); - ParseImportSections(); } } } @@ -698,7 +704,9 @@ namespace R2RDump while (true) { - cells.Add(new FixupCell(cells.Count, curTableIndex, fixupIndex)); + R2RImportSection importSection = ImportSections[(int)curTableIndex]; + R2RImportSection.ImportSectionEntry entry = importSection.Entries[(int)fixupIndex]; + cells.Add(new FixupCell(cells.Count, curTableIndex, fixupIndex, entry.Signature)); uint delta = reader.ReadUInt(); diff --git a/src/coreclr/src/tools/r2rdump/R2RSignature.cs b/src/coreclr/src/tools/r2rdump/R2RSignature.cs index 488b55c..852521a 100644 --- a/src/coreclr/src/tools/r2rdump/R2RSignature.cs +++ b/src/coreclr/src/tools/r2rdump/R2RSignature.cs @@ -699,7 +699,8 @@ namespace R2RDump break; case CorElementType.ELEMENT_TYPE_PTR: - builder.Append("ptr"); + ParseType(builder); + builder.Append('*'); break; case CorElementType.ELEMENT_TYPE_BYREF: @@ -717,7 +718,51 @@ namespace R2RDump break; case CorElementType.ELEMENT_TYPE_ARRAY: - builder.Append("array"); + ParseType(builder); + { + builder.Append('['); + uint rank = ReadUInt(); + if (rank != 0) + { + uint sizeCount = ReadUInt(); // number of sizes + uint[] sizes = new uint[sizeCount]; + for (uint sizeIndex = 0; sizeIndex < sizeCount; sizeIndex++) + { + sizes[sizeIndex] = ReadUInt(); + } + uint lowerBoundCount = ReadUInt(); // number of lower bounds + int[] lowerBounds = new int[sizeCount]; + for (uint lowerBoundIndex = 0; lowerBoundIndex < lowerBoundCount; lowerBoundIndex++) + { + lowerBounds[lowerBoundIndex] = ReadInt(); + } + for (int index = 0; index < rank; index++) + { + if (index > 0) + { + builder.Append(','); + } + if (lowerBoundCount > index && lowerBounds[index] != 0) + { + builder.Append(lowerBounds[index]); + builder.Append(".."); + if (sizeCount > index) + { + builder.Append(lowerBounds[index] + sizes[index] - 1); + } + } + else if (sizeCount > index) + { + builder.Append(sizes[index]); + } + else if (rank == 1) + { + builder.Append('*'); + } + } + } + builder.Append(']'); + } break; case CorElementType.ELEMENT_TYPE_GENERICINST: @@ -745,7 +790,8 @@ namespace R2RDump break; case CorElementType.ELEMENT_TYPE_SZARRAY: - builder.Append("szarray"); + ParseType(builder); + builder.Append("[]"); break; case CorElementType.ELEMENT_TYPE_MVAR: diff --git a/src/coreclr/src/tools/r2rdump/TextDumper.cs b/src/coreclr/src/tools/r2rdump/TextDumper.cs index 67a40a8..df1bef5 100644 --- a/src/coreclr/src/tools/r2rdump/TextDumper.cs +++ b/src/coreclr/src/tools/r2rdump/TextDumper.cs @@ -17,11 +17,14 @@ namespace R2RDump internal override void Begin() { - _writer.WriteLine($"Filename: {_r2r.Filename}"); - _writer.WriteLine($"OS: {_r2r.OS}"); - _writer.WriteLine($"Machine: {_r2r.Machine}"); - _writer.WriteLine($"ImageBase: 0x{_r2r.ImageBase:X8}"); - SkipLine(); + if (!_options.Normalize) + { + _writer.WriteLine($"Filename: {_r2r.Filename}"); + _writer.WriteLine($"OS: {_r2r.OS}"); + _writer.WriteLine($"Machine: {_r2r.Machine}"); + _writer.WriteLine($"ImageBase: 0x{_r2r.ImageBase:X8}"); + SkipLine(); + } } internal override void End() @@ -94,6 +97,15 @@ namespace R2RDump } } + internal override void DumpEntryPoints() + { + WriteDivider($@"R2R Entry Points"); + foreach (R2RMethod method in NormalizedMethods()) + { + _writer.WriteLine(method.SignatureString); + } + } + internal override void DumpAllMethods() { WriteDivider("R2R Methods"); @@ -111,7 +123,7 @@ namespace R2RDump internal override void DumpMethod(R2RMethod method, XmlNode parentNode = null) { WriteSubDivider(); - _writer.WriteLine(method.ToString()); + method.WriteTo(_writer, _options); if (_options.GC && method.GcInfo != null) { @@ -247,10 +259,13 @@ namespace R2RDump switch (section.Type) { case R2RSection.SectionType.READYTORUN_SECTION_AVAILABLE_TYPES: - uint availableTypesSectionOffset = (uint)_r2r.GetOffset(section.RelativeVirtualAddress); - NativeParser availableTypesParser = new NativeParser(_r2r.Image, availableTypesSectionOffset); - NativeHashtable availableTypes = new NativeHashtable(_r2r.Image, availableTypesParser, (uint)(availableTypesSectionOffset + section.Size)); - _writer.WriteLine(availableTypes.ToString()); + if (!_options.Naked) + { + uint availableTypesSectionOffset = (uint)_r2r.GetOffset(section.RelativeVirtualAddress); + NativeParser availableTypesParser = new NativeParser(_r2r.Image, availableTypesSectionOffset); + NativeHashtable availableTypes = new NativeHashtable(_r2r.Image, availableTypesParser, (uint)(availableTypesSectionOffset + section.Size)); + _writer.WriteLine(availableTypes.ToString()); + } foreach (string name in _r2r.AvailableTypes) { @@ -258,15 +273,21 @@ namespace R2RDump } break; case R2RSection.SectionType.READYTORUN_SECTION_METHODDEF_ENTRYPOINTS: - NativeArray methodEntryPoints = new NativeArray(_r2r.Image, (uint)_r2r.GetOffset(section.RelativeVirtualAddress)); - _writer.Write(methodEntryPoints.ToString()); + if (!_options.Naked) + { + NativeArray methodEntryPoints = new NativeArray(_r2r.Image, (uint)_r2r.GetOffset(section.RelativeVirtualAddress)); + _writer.Write(methodEntryPoints.ToString()); + } break; case R2RSection.SectionType.READYTORUN_SECTION_INSTANCE_METHOD_ENTRYPOINTS: - uint instanceSectionOffset = (uint)_r2r.GetOffset(section.RelativeVirtualAddress); - NativeParser instanceParser = new NativeParser(_r2r.Image, instanceSectionOffset); - NativeHashtable instMethodEntryPoints = new NativeHashtable(_r2r.Image, instanceParser, (uint)(instanceSectionOffset + section.Size)); - _writer.Write(instMethodEntryPoints.ToString()); - _writer.WriteLine(); + if (!_options.Naked) + { + uint instanceSectionOffset = (uint)_r2r.GetOffset(section.RelativeVirtualAddress); + NativeParser instanceParser = new NativeParser(_r2r.Image, instanceSectionOffset); + NativeHashtable instMethodEntryPoints = new NativeHashtable(_r2r.Image, instanceParser, (uint)(instanceSectionOffset + section.Size)); + _writer.Write(instMethodEntryPoints.ToString()); + _writer.WriteLine(); + } foreach (InstanceMethod instanceMethod in _r2r.InstanceMethods) { _writer.WriteLine($@"0x{instanceMethod.Bucket:X2} -> {instanceMethod.Method.SignatureString}"); @@ -297,37 +318,61 @@ namespace R2RDump _writer.WriteLine(_r2r.CompilerIdentifier); break; case R2RSection.SectionType.READYTORUN_SECTION_IMPORT_SECTIONS: - foreach (R2RImportSection importSection in _r2r.ImportSections) + if (_options.Naked) { - _writer.Write(importSection.ToString()); - if (_options.Raw && importSection.Entries.Count != 0) + DumpNakedImportSections(); + } + else + { + foreach (R2RImportSection importSection in _r2r.ImportSections) { - if (importSection.SectionRVA != 0) - { - _writer.WriteLine("Section Bytes:"); - DumpBytes(importSection.SectionRVA, (uint)importSection.SectionSize); - } - if (importSection.SignatureRVA != 0) + _writer.Write(importSection.ToString()); + if (_options.Raw && importSection.Entries.Count != 0) { - _writer.WriteLine("Signature Bytes:"); - DumpBytes(importSection.SignatureRVA, (uint)importSection.Entries.Count * sizeof(int)); + if (importSection.SectionRVA != 0) + { + _writer.WriteLine("Section Bytes:"); + DumpBytes(importSection.SectionRVA, (uint)importSection.SectionSize); + } + if (importSection.SignatureRVA != 0) + { + _writer.WriteLine("Signature Bytes:"); + DumpBytes(importSection.SignatureRVA, (uint)importSection.Entries.Count * sizeof(int)); + } + if (importSection.AuxiliaryDataRVA != 0 && importSection.AuxiliaryData != null) + { + _writer.WriteLine("AuxiliaryData Bytes:"); + DumpBytes(importSection.AuxiliaryDataRVA, (uint)importSection.AuxiliaryData.Size); + } } - if (importSection.AuxiliaryDataRVA != 0 && importSection.AuxiliaryData != null) + foreach (R2RImportSection.ImportSectionEntry entry in importSection.Entries) { - _writer.WriteLine("AuxiliaryData Bytes:"); - DumpBytes(importSection.AuxiliaryDataRVA, (uint)importSection.AuxiliaryData.Size); + _writer.WriteLine(entry.ToString()); } + _writer.WriteLine(); } - foreach (R2RImportSection.ImportSectionEntry entry in importSection.Entries) - { - _writer.WriteLine(entry.ToString()); - } - _writer.WriteLine(); } break; } } + private void DumpNakedImportSections() + { + List importSignatures = new List(); + foreach (R2RImportSection importSection in _r2r.ImportSections) + { + foreach (R2RImportSection.ImportSectionEntry entry in importSection.Entries) + { + importSignatures.Add(entry.Signature); + } + } + importSignatures.Sort(); + foreach (string sig in importSignatures) + { + _writer.WriteLine(sig); + } + } + internal override XmlNode DumpQueryCount(string q, string title, int count) { _writer.WriteLine(count + " result(s) for \"" + q + "\""); diff --git a/src/coreclr/src/tools/r2rdump/XmlDumper.cs b/src/coreclr/src/tools/r2rdump/XmlDumper.cs index 97a90e7..3712b89 100644 --- a/src/coreclr/src/tools/r2rdump/XmlDumper.cs +++ b/src/coreclr/src/tools/r2rdump/XmlDumper.cs @@ -114,6 +114,17 @@ namespace R2RDump } } + internal override void DumpEntryPoints() + { + XmlNode entryPointsNode = XmlDocument.CreateNode("element", "EntryPoints", ""); + _rootNode.AppendChild(entryPointsNode); + AddXMLAttribute(entryPointsNode, "Count", _r2r.R2RMethods.Count.ToString()); + foreach (R2RMethod method in NormalizedMethods()) + { + DumpMethod(method, entryPointsNode); + } + } + internal override void DumpAllMethods() { XmlNode methodsNode = XmlDocument.CreateNode("element", "Methods", ""); -- 2.7.4