Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
// NOTE this version is now out of date.// UPDATED VERSION IS AVAILABLE in the MDBG DISTRIBUTION.// See https://blogs.msdn.com/jmstall/archive/2005/11/08/mdbg_linkfest.aspx
// Sample to load PDB and dump as XML.
// Author: Mike Stall (https://blogs.msdn.com/jmstall)
// UPDATED on 1/26/05, uses PDB wrappers from MDBG sample.
// must include reference to Mdbg (such as MdbgCore.dll)
using System;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.IO;
using System.Diagnostics;
using System.Runtime.InteropServices;
// For symbol store
using System.Diagnostics.SymbolStore;
using Microsoft.Samples.Debugging.CorSymbolStore;
namespace XmlPdbReader
{
// Random utility methods.
static class Util
{
// Format a token to a string. Tokens are in hex.
public static string AsToken(int i)
{
return String.Format(System.Globalization.CultureInfo.InvariantCulture, "0x{0:x}", i);
}
// Since we're spewing this to XML, spew as a decimal number.
public static string AsIlOffset(int i)
{
return i.ToString();
}
}
/// <summary>
/// Class to write out XML for a PDB.
/// </summary>
class Pdb2XmlConverter
{
/// <summary>
/// Initialize the Pdb to Xml converter. Actual conversion happens in ReadPdbAndWriteToXml.
/// Passing a filename also makes it easy for us to use reflection to get some information
/// (such as enumeration)
/// </summary>
/// <param name="writer">XmlWriter to spew to.</param>
/// <param name="fileName">Filename for exe/dll. This class will find the pdb to match.</param>
public Pdb2XmlConverter(XmlWriter writer, string fileName)
{
m_writer = writer;
m_fileName = fileName;
}
// The filename that the pdb is for.
string m_fileName;
XmlWriter m_writer;
// Keep assembly so we can query metadata on it.
System.Reflection.Assembly m_assembly;
// Maps files to ids.
Dictionary<string, int> m_fileMapping = new Dictionary<string, int>();
/// <summary>
/// Load the PDB given the parameters at the ctor and spew it out to the XmlWriter specified
/// at the ctor.
/// </summary>
public void ReadPdbAndWriteToXml()
{
// Actually load the files
ISymbolReader reader = SymUtil.GetSymbolReaderForFile(m_fileName, null);
m_assembly = System.Reflection.Assembly.ReflectionOnlyLoadFrom(m_fileName);
// Begin writing XML.
m_writer.WriteStartDocument();
m_writer.WriteComment("This is an XML file representing the PDB for '" + m_fileName + "'");
m_writer.WriteStartElement("symbols");
// Record what input file these symbols are for.
m_writer.WriteAttributeString("file", m_fileName);
WriteDocList(reader);
WriteEntryPoint(reader);
WriteAllMethods(reader);
m_writer.WriteEndElement(); // "Symbols";
}
// Dump all of the methods in the given ISymbolReader to the XmlWriter provided in the ctor.
void WriteAllMethods(ISymbolReader reader)
{
m_writer.WriteComment("This is a list of all methods in the assembly that matches this PDB.");
m_writer.WriteComment("For each method, we provide the sequence tables that map from IL offsets back to source.");
m_writer.WriteStartElement("methods");
// Use reflection to enumerate all methods
foreach (Type t in m_assembly.GetTypes())
{
foreach (MethodInfo methodReflection in t.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly))
{
int token = methodReflection.MetadataToken;
ISymbolMethod methodSymbol = null;
m_writer.WriteStartElement("method");
{
m_writer.WriteAttributeString("name", t.FullName + "." + methodReflection.Name);
m_writer.WriteAttributeString("token", Util.AsToken(token));
try
{
methodSymbol = reader.GetMethod(new SymbolToken(token));
WriteSequencePoints(methodSymbol);
WriteLocals(methodSymbol);
}
catch (COMException e)
{
m_writer.WriteComment("No symbol info");
}
}
m_writer.WriteEndElement(); // method
}
}
m_writer.WriteEndElement();
}
// Write all the locals in the given method out to an XML file.
// Since the symbol store represents the locals in a recursive scope structure, we need to walk a tree.
// Although the locals are technically a hierarchy (based off nested scopes), it's easiest for clients
// if we present them as a linear list. We will provide the range for each local's scope so that somebody
// could reconstruct an approximation of the scope tree. The reconstruction may not be exact.
// (Note this would still break down if you had an empty scope nested in another scope.
void WriteLocals(ISymbolMethod method)
{
m_writer.WriteStartElement("locals");
{
// If there are no locals, then this element will just be empty.
WriteLocalsHelper(method.RootScope);
}
m_writer.WriteEndElement();
}
// Helper method to write the local variables in the given scope.
// Scopes match an IL range, and also have child scopes.
void WriteLocalsHelper(ISymbolScope scope)
{
foreach (ISymbolVariable l in scope.GetLocals())
{
m_writer.WriteStartElement("local");
{
m_writer.WriteAttributeString("name", l.Name);
// Each local maps to a unique "IL Index" or "slot" number.
// This index is what you pass to ICorDebugILFrame::GetLocalVariable() to get
// a specific local variable.
Debug.Assert(l.AddressKind == SymAddressKind.ILOffset);
int slot = l.AddressField1;
m_writer.WriteAttributeString("il_index", slot.ToString());
// Provide scope range
m_writer.WriteAttributeString("il_start", Util.AsIlOffset(scope.StartOffset));
m_writer.WriteAttributeString("il_end", Util.AsIlOffset(scope.EndOffset));
}
m_writer.WriteEndElement(); // local
}
foreach (ISymbolScope childScope in scope.GetChildren())
{
WriteLocalsHelper(childScope);
}
}
// Write the sequence points for the given method
// Sequence points are the map between IL offsets and source lines.
// A single method could span multiple files (use C#'s #line directive to see for yourself).
void WriteSequencePoints(ISymbolMethod method)
{
m_writer.WriteStartElement("sequencepoints");
int count = method.SequencePointCount;
m_writer.WriteAttributeString("total", count.ToString());
// Get the sequence points from the symbol store.
// We could cache these arrays and reuse them.
int[] offsets = new int[count];
ISymbolDocument[] docs = new ISymbolDocument[count];
int[] startColumn = new int[count];
int[] endColumn = new int[count];
int[] startRow = new int[count];
int[] endRow = new int[count];
method.GetSequencePoints(offsets, docs, startRow, startColumn, endRow, endColumn);
// Write out sequence points
for (int i = 0; i < count; i++)
{
m_writer.WriteStartElement("entry");
m_writer.WriteAttributeString("il_offset", Util.AsIlOffset(offsets[i]));
// If it's a special 0xFeeFee sequence point (eg, "hidden"),
// place an attribute on it to make it very easy for tools to recognize.
// See https://blogs.msdn.com/jmstall/archive/2005/06/19/FeeFee_SequencePoints.aspx
if (startRow[i] == 0xFeeFee)
{
m_writer.WriteAttributeString("hidden", XmlConvert.ToString(true));
}
else
{
m_writer.WriteAttributeString("start_row", startRow[i].ToString());
m_writer.WriteAttributeString("start_column", startColumn[i].ToString());
m_writer.WriteAttributeString("end_row", endRow[i].ToString());
m_writer.WriteAttributeString("end_column", endColumn[i].ToString());
m_writer.WriteAttributeString("file_ref", this.m_fileMapping[docs[i].URL].ToString());
}
m_writer.WriteEndElement();
}
m_writer.WriteEndElement(); // sequencepoints
}
// Write all docs, and add to the m_fileMapping list.
// Other references to docs will then just refer to this list.
void WriteDocList(ISymbolReader reader)
{
m_writer.WriteComment("This is a list of all source files referred by the PDB.");
int id = 0;
// Write doc list
m_writer.WriteStartElement("files");
{
ISymbolDocument[] docs = reader.GetDocuments();
foreach (ISymbolDocument doc in docs)
{
string url = doc.URL;
// Symbol store may give out duplicate documents. We'll fold them here
if (m_fileMapping.ContainsKey(url))
{
m_writer.WriteComment("There is a duplicate entry for: " + url);
continue;
}
id++;
m_fileMapping.Add(doc.URL, id);
m_writer.WriteStartElement("file");
{
m_writer.WriteAttributeString("id", id.ToString());
m_writer.WriteAttributeString("name", doc.URL);
}
m_writer.WriteEndElement(); // file
}
}
m_writer.WriteEndElement(); // files
}
// Write out a reference to the entry point method (if one exists)
void WriteEntryPoint(ISymbolReader reader)
{
try
{
// If there is no entry point token (such as in a dll), this will throw.
SymbolToken token = reader.UserEntryPoint;
ISymbolMethod m = reader.GetMethod(token);
Debug.Assert(m != null); // would have thrown by now.
// Should not throw past this point
m_writer.WriteComment(
"This is the token for the 'entry point' method, which is the method that will be called when the assembly is loaded." +
" This usually corresponds to 'Main'");
m_writer.WriteStartElement("EntryPoint");
WriteMethod(m);
m_writer.WriteEndElement();
}
catch (System.Runtime.InteropServices.COMException)
{
// If the Symbol APIs fail when looking for an entry point token, there is no entry point.
m_writer.WriteComment(
"There is no entry point token such as a 'Main' method. This module is probably a '.dll'");
}
}
// Write out XML snippet to refer to the given method.
void WriteMethod(ISymbolMethod method)
{
m_writer.WriteElementString("methodref", Util.AsToken(method.Token.GetToken()));
}
}
// Harness to drive PDB to XML rea
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Test harness for PDB2XML.");
if (args.Length < 2)
{
Console.WriteLine("Usage: Pdb2Xml <input managed exe> <output xml>");
Console.WriteLine("This will load the pdb for the managed exe, and then spew the pdb contents to an XML file.");
return;
}
string stInputExe = args[0];
string stOutputXml = args[1];
// Get a Text Writer to spew the PDB to.
XmlDocument doc = new XmlDocument();
XmlWriter xw = doc.CreateNavigator().AppendChild();
// Do the pdb for the exe into an xml stream.
Pdb2XmlConverter p = new Pdb2XmlConverter(xw, stInputExe);
p.ReadPdbAndWriteToXml();
xw.Close();
// Print the XML we just generated and save it to a file for more convenient viewing.
Console.WriteLine(doc.OuterXml);
doc.Save(stOutputXml);
{
// Proove that it's valid XML by reading it back in...
XmlDocument d2 = new XmlDocument();
d2.Load(stOutputXml);
}
// Now demonstrate some different queries.
XmlNode root = doc.DocumentElement;
DoQuery(QueryForStartRow("Foo.Main", 3), root);
DoQuery(QueryForEntryName(), root);
DoQuery(QueryForAllLocalsInMethod("Foo.Main"), root);
} // end Main
#region Sample Queries
// Some sample queries
static string QueryForEntryName()
{
return @"/symbols/methods/method[@token=/symbols/EntryPoint/methodref]/@name";
}
static string QueryForAllLocalsInMethod(string stMethod)
{
return "/symbols/methods/method[@name=\"" + stMethod + "\"]/locals/local/@name";
}
static string QueryForStartRow(string stMethod, int exactILOffset)
{
return "/symbols/methods/method[@name=\"" + stMethod + "\"]/sequencepoints/entry[@il_offset=\"" + exactILOffset + "\"]/@start_row";
}
#if false
// Here are more sample queries:
// Get all locals that are active at a given line?
@"/symbols/methods/method/locals/local[@il_start<=""2"" and @il_end>=""2""]/@name";
@"/symbols/files/file/@name"; // get all filenames referenced from PDB.
// ** All methods that have code in a given filename.
@"/symbols/methods/method[sequencepoints/entry/@file_ref=/symbols/files/file[@name=""c:\temp\t.cs""]/@id]/@name";
@"/symbols/files/file[@name=""c:\temp\t.cs""]/@id"; // File ID for a given filename:
@"/symbols/EntryPoint/methodref"; // entry point token.
@"/symbols/methods/method/@name"; // ** select all names of all methods
@"/symbols/methods/method[@token=""0x6000001""]"; // entire XML snippet for method with specified token value
@"/symbols/methods/method[@token=""0x6000001""]/@name"; // ** just the name of method with the given token value
@"/symbols/methods/method[@token=/symbols/EntryPoint/methodref]/@name"; // *** method name of the entry point token!!
@"/symbols/methods/method[@name=""Foo.Main""]/locals/local/@name"; // name of all locals in method Foo.Main
@"/symbols/methods/method/sequencepoints/entry[@il_offset=""0x12""]"; // sp entry for IL offset 12 (in all methods)
@"/symbols/methods/method[@name=""Foo.Main""]/sequencepoints/entry/@start_row"; // get all source rows for method Foo.Main
@"/symbols/methods/method[@name=""Foo.Main""]/sequencepoints/entry[@il_offset=""0x4""]/@start_row"; // get start row for for IL offset 4 in method Foo.Main
// Lookup method name + IL offset given source file + line
// Queries only return 1 result, so there's not a good way to get the (name, IL offset) pair back with a single query.
@"/symbols/methods/method/sequencepoints/entry[@start_row<=""26"" and @end_row>=""26"" and @file_ref=/symbols/files/file[@name=""c:\temp\t.cs""]/@id]/@il_offset";
@"/symbols/methods/method[sequencepoints/entry[@start_row<=""26"" and @end_row>=""26"" and @file_ref=/symbols/files/file[@name=""c:\temp\t.cs""]/@id]]/@name";
#endif
#endregion
// Helper to execute a query and print out to console.
static void DoQuery(string stQuery, XmlNode root)
{
Console.WriteLine("Query:{0}", stQuery);
XmlNodeList nodeList = root.SelectNodes(stQuery);
Console.WriteLine("Found {0} item(s) in query.", nodeList.Count);
Console.WriteLine("(outer)-----------");
foreach (XmlNode x in nodeList)
{
Console.WriteLine(x.OuterXml);
}
Console.WriteLine("(inner)-----------");
foreach (XmlNode x in nodeList)
{
Console.WriteLine(x.InnerText);
}
Console.WriteLine("------------------");
}
}
#region Get a symbol reader for the given module
// Encapsulate a set of helper classes to get a symbol reader from a file.
// The symbol interfaces require an unmanaged metadata interface.
static class SymUtil
{
static class NativeMethods
{
[DllImport("ole32.dll")]
public static extern int CoCreateInstance([In] ref Guid rclsid,
[In, MarshalAs(UnmanagedType.IUnknown)] Object pUnkOuter,
[In] uint dwClsContext,
[In] ref Guid riid,
[Out, MarshalAs(UnmanagedType.Interface)] out Object ppv);
}
// Wrapper.
public static ISymbolReader GetSymbolReaderForFile(string pathModule, string searchPath)
{
return SymUtil.GetSymbolReaderForFile(new SymbolBinder(), pathModule, searchPath);
}
// We demand Unmanaged code permissions because we're reading from the file system and calling out to the Symbol Reader
// @TODO - make this more specific.
[System.Security.Permissions.SecurityPermission(
System.Security.Permissions.SecurityAction.Demand,
Flags = System.Security.Permissions.SecurityPermissionFlag.UnmanagedCode)]
public static ISymbolReader GetSymbolReaderForFile(SymbolBinder binder, string pathModule, string searchPath)
{
// Guids for imported metadata interfaces.
Guid dispenserClassID = new Guid(0xe5cb7a31, 0x7512, 0x11d2, 0x89, 0xce, 0x00, 0x80, 0xc7, 0x92, 0xe5, 0xd8); // CLSID_CorMetaDataDispenser
Guid dispenserIID = new Guid(0x809c652e, 0x7396, 0x11d2, 0x97, 0x71, 0x00, 0xa0, 0xc9, 0xb4, 0xd5, 0x0c); // IID_IMetaDataDispenser
Guid importerIID = new Guid(0x7dac8207, 0xd3ae, 0x4c75, 0x9b, 0x67, 0x92, 0x80, 0x1a, 0x49, 0x7d, 0x44); // IID_IMetaDataImport
// First create the Metadata dispenser.
object objDispenser;
NativeMethods.CoCreateInstance(ref dispenserClassID, null, 1, ref dispenserIID, out objDispenser);
// Now open an Importer on the given filename. We'll end up passing this importer straight
// through to the Binder.
object objImporter;
IMetaDataDispenser dispenser = (IMetaDataDispenser)objDispenser;
dispenser.OpenScope(pathModule, 0, ref importerIID, out objImporter);
IntPtr importerPtr = IntPtr.Zero;
ISymbolReader reader;
try
{
// This will manually AddRef the underlying object, so we need to be very careful to Release it.
importerPtr = Marshal.GetComInterfaceForObject(objImporter, typeof(IMetadataImport));
reader = binder.GetReader(importerPtr, pathModule, searchPath);
}
finally
{
if (importerPtr != IntPtr.Zero)
{
Marshal.Release(importerPtr);
}
}
return reader;
}
}
#region Metadata Imports
// We can use reflection-only load context to use reflection to query for metadata information rather
// than painfully import the com-classic metadata interfaces.
[Guid("809c652e-7396-11d2-9771-00a0c9b4d50c"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[ComVisible(true)]
interface IMetaDataDispenser
{
// We need to be able to call OpenScope, which is the 2nd vtable slot.
// Thus we need this one placeholder here to occupy the first slot..
void DefineScope_Placeholder();
//STDMETHOD(OpenScope)( // Return code.
//LPCWSTR szScope, // [in] The scope to open.
// DWORD dwOpenFlags, // [in] Open mode flags.
// REFIID riid, // [in] The interface desired.
// IUnknown **ppIUnk) PURE; // [out] Return interface on success.
void OpenScope([In, MarshalAs(UnmanagedType.LPWStr)] String szScope, [In] Int32 dwOpenFlags, [In] ref Guid riid, [Out, MarshalAs(UnmanagedType.IUnknown)] out Object punk);
// Don't need any other methods.
}
// Since we're just blindly passing this interface through managed code to the Symbinder, we don't care about actually
// importing the specific methods.
// This needs to be public so that we can call Marshal.GetComInterfaceForObject() on it to get the
// underlying metadata pointer.
[Guid("7DAC8207-D3AE-4c75-9B67-92801A497D44"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[ComVisible(true)]
[CLSCompliant(true)]
public interface IMetadataImport
{
// Just need a single placeholder method so that it doesn't complain about an empty interface.
void Placeholder();
}
#endregion
#endregion Get a symbol reader for the given module
} // XmlPdbReader
Comments
Anonymous
August 24, 2005
I wrote some C# sample code to get an ISymbolReader from a managed PDB (Program Database) file and then...Anonymous
November 21, 2006
It's natural for a tool to use Reflection-Only loading to load an assembly and view the types in it.Anonymous
November 22, 2006
We've just updated the MDbg sample! This is a full source sample for building a managed debugger in C#.