Writing a .net debugger (part 4) – breakpoints

After the last part the mindbg debugger stops at the application entry point, has module symbols loaded and displays source code that is being executed. Today we will gain some more control over the debugging process by using breakpoints. By the end of this post we will be able to stop the debugger on either a function execution or at any source line.

Setting a breakpoint on a function is quite straightforward – you only need to call CreateBreakpoint method on a ICorDebugFunction instance (that we want to have a stop on) and then activate the newly created breakpoint (with ICorDebugBreakpoint.Activate(1) function). The tricky part is how to find the ICorDebugFunction instance based on a string provided by the user. For this purpose we will write few helper methods that will use ICorMetadataImport interface. Let’s assume that we would like to set a breakpoint on a Test method of TestCmd class in testcmd.exe assembly. We will then use command “set-break mcmdtest.exe!TestCmd.Test”. After splitting the command string we will receive module path, class name and method name. We could easily find a module with a given path (we will iterate through the modules collection – for now it won’t be possible to create a breakpoint on a module that has not been loaded). Having found a module we may try to identify the type which “owns” the method. I really like the way how it is done in mdbg source code so we will copy their idea :). We will add a new method to the CorModule class:

// Brilliantly written taken from mdbg source code.
// returns a type token from name
// when the function fails, we return token TokenNotFound value.
public int GetTypeTokenFromName(string name)
{
    IMetadataImport importer = GetMetadataInterface<IMetadataImport>();

    int token = TokenNotFound;
    if (name.Length == 0)
        // this is special global type (we'll return token 0)
        token = TokenGlobalNamespace;
    else
    {
        try
        {
            importer.FindTypeDefByName(name, 0, out token);
        }
        catch (COMException e)
        {
            token = TokenNotFound;
            if ((HResult)e.ErrorCode == HResult.CLDB_E_RECORD_NOTFOUND)
            {
                int i = name.LastIndexOf('.');
                if (i > 0)
                {
                    int parentToken = GetTypeTokenFromName(name.Substring(0, i));
                    if (parentToken != TokenNotFound)
                    {
                        try
                        {
                            importer.FindTypeDefByName(name.Substring(i + 1), parentToken, out token);
                        }
                        catch (COMException e2)
                        {
                            token = TokenNotFound;
                            if ((HResult)e2.ErrorCode != HResult.CLDB_E_RECORD_NOTFOUND)
                                throw;
                        }
                    }
                }
            }
            else
                throw;
        }
    }
    return token;
}

Then, we will implement the MetadataType class that will inherit from Type. For clarity I will show you only the impelmented methods (others throw NotImplementedException):

internal sealed class MetadataType : Type
{
    private readonly IMetadataImport p_importer;
    private readonly Int32 p_typeToken;

    internal MetadataType(IMetadataImport importer, Int32 typeToken)
    {
        this.p_importer = importer;
        this.p_typeToken = typeToken;
    }

    ...
    
    public override System.Reflection.MethodInfo[] GetMethods(System.Reflection.BindingFlags bindingAttr)
    {
        IntPtr hEnum = new IntPtr();
        ArrayList methods = new ArrayList();

        Int32 methodToken;
        try
        {
            while (true)
            {
                Int32 size;
                p_importer.EnumMethods(ref hEnum, p_typeToken, out methodToken, 1, out size);
                if (size == 0)
                    break;
                methods.Add(new MetadataMethodInfo(p_importer, methodToken));
            }
        }
        finally
        {
            p_importer.CloseEnum(hEnum);
        }
        return (MethodInfo[])methods.ToArray(typeof(MethodInfo));
    }
    
    ...
}

As you could see in the listing we also used MetadataMethodInfo. The listing below presents body of this class:

internal sealed class MetadataMethodInfo : MethodInfo
{
    private readonly Int32 p_methodToken;
    private readonly Int32 p_classToken;
    private readonly IMetadataImport p_importer;
    private readonly String p_name;


    internal MetadataMethodInfo(IMetadataImport importer, Int32 methodToken)
    {
        p_importer = importer;
        p_methodToken = methodToken;

        int size;
        uint pdwAttr;
        IntPtr ppvSigBlob;
        uint pulCodeRVA, pdwImplFlags;
        uint pcbSigBlob;

        p_importer.GetMethodProps((uint)methodToken,
                                  out p_classToken,
                                  null,
                                  0,
                                  out size,
                                  out pdwAttr,
                                  out ppvSigBlob,
                                  out pcbSigBlob,
                                  out pulCodeRVA,
                                  out pdwImplFlags);

        StringBuilder szMethodName = new StringBuilder(size);
        p_importer.GetMethodProps((uint)methodToken,
                                out p_classToken,
                                szMethodName,
                                szMethodName.Capacity,
                                out size,
                                out pdwAttr,
                                out ppvSigBlob,
                                out pcbSigBlob,
                                out pulCodeRVA,
                                out pdwImplFlags);

        p_name = szMethodName.ToString();
        //m_methodAttributes = (MethodAttributes)pdwAttr;
    }

    ...
    
    public override string Name
    {
        get { return p_name; }
    }
    
    public override int MetadataToken
    {
        get { return this.p_methodToken; }
    }
}

Finally we are ready to implement the method that will return CorFunction instance:

public CorFunction ResolveFunctionName(CorModule module, String className, String functionName)
{
    Int32 typeToken = module.GetTypeTokenFromName(className);
    if (typeToken == CorModule.TokenNotFound)
        return null;

    Type t = new MetadataType(module.GetMetadataInterface<IMetadataImport>(), typeToken);
    CorFunction func = null;
    foreach (MethodInfo mi in t.GetMethods())
    {
        if (String.Equals(mi.Name, functionName, StringComparison.Ordinal))
        {
            func = module.GetFunctionFromToken(mi.MetadataToken);
            break;
        }
    }
    return func;
}

We will now concentrate on the second type of breakpoints: the code breakpoints which are set at a specific line of the source code file. Example of usage whould be “set-break mcmdtest.cs:23”. So how to set this type of breakpoint? First we need to find a module that was built from the given source file. We will iterate through all loaded modules and if a given module has symbols loaded (SymReader property != null) then we will check its documents URLS and compare them with the requested file name (snippet based on mdbg source code):

if(managedModule.SymReader==null)
  // no symbols for current module, skip it.
  return false;

foreach(ISymbolDocument doc in managedModule.SymReader.GetDocuments())
{
  if(String.Compare(doc.URL,m_file,true,CultureInfo.InvariantCulture)==0 ||
    String.Compare(System.IO.Path.GetFileName(doc.URL),m_file,true,CultureInfo.InvariantCulture)==0)
  {
     // we will fill for body later
  }
}

Having found the module we need to find a class method that the given source line belongs to. So first let’s locate the line that is the nearest sequence point to the given line by calling ISymbolDocument.FindClosestLine method. Next, with the help of the module’s ISymbolReader we will find the ISymbolMethod instance that represents our wanted function. The last step is to get the CorFunction instance based on the method’s token:

// the upper "for" body
Int32 line = 0;
try
{
  line = symdoc.FindClosestLine(lineNumber);
}
catch (System.Runtime.InteropServices.COMException ex)
{
  if (ex.ErrorCode == (Int32)HResult.E_FAIL)
      continue; // it's not this document
}
ISymbolMethod symmethod = symreader.GetMethodFromDocumentPosition(symdoc, line, 0);
CorFunction func = module.GetFunctionFromToken(symmethod.Token.GetToken());

Code breakpoints are created using ICorDebugCode.CreateBreakpoint method which takes as its parameter a code offset at which the breakpoint should be set. We will get an instance of the ICorDebugCode from the CorFunction instance (found in the last paragraph):

// from CorFunction.cs
public CorCode GetILCode()
{
    ICorDebugCode cocode = null;
    p_cofunc.GetILCode(out cocode);
    return new CorCode(cocode);
}

Then we will find the IL offset in the function IL code that corresponds to the given source file line number:

// from CorFunction.cs
internal int GetIPFromPosition(ISymbolDocument document, int lineNumber)
{
    SetupSymbolInformation();
    if (!p_hasSymbols)
        return -1;

    for (int i = 0; i < p_SPcount; i++)
    {
        if (document.URL.Equals(p_SPdocuments[i].URL) && lineNumber == p_SPstartLines[i])
            return p_SPoffsets[i];
    }
    return -1;
}

Finally we are ready to parse user’s input and set breakpoints accordingly. I used two simple regex expressions to check the breakpoint type and call process.ResolveFunctionName for function breakpoints and process.ResolveCodeLocation for code breakpoints:

static Regex methodBreakpointRegex = new Regex(@"^((?<module>[\.\w\d]*)!)?(?<class>[\w\d\.]+)\.(?<method>[\w\d]+)$");
static Regex codeBreakpointRegex = new Regex(@"^(?<filepath>[\\\.\S]+)\:(?<linenum>\d+)$");

...

// try module!type.method location (simple regex used)
Match match = methodBreakpointRegex.Match(command);
if (match.Groups["method"].Length > 0)
{
    Console.Write("Setting method breakpoint... ");

    CorFunction func = process.ResolveFunctionName(match.Groups["module"].Value, match.Groups["class"].Value,
                                                    match.Groups["method"].Value);
    func.CreateBreakpoint().Activate(true);
    
    Console.WriteLine("done.");
    continue;
}
// try file code:line location
match = codeBreakpointRegex.Match(command);
if (match.Groups["filepath"].Length > 0)
{
    Console.Write("Setting code breakpoint...");

    int offset;
    CorCode code = process.ResolveCodeLocation(match.Groups["filepath"].Value, 
                                               Int32.Parse(match.Groups["linenum"].Value), 
                                               out offset);
    code.CreateBreakpoint(offset).Activate(true);

    Console.WriteLine("done.");
    continue;
}

I also corrected the main debugger loop so it is starting to look as a normal debugger command line :). As always the source code is available under http://mindbg.codeplex.com (revision 55832).

15 thoughts on “Writing a .net debugger (part 4) – breakpoints

  1. ellebaek January 25, 2012 / 16:52

    Hi

    This is all very interesting, yet I can’t really figure out how to apply it to my “problem”: I would like to write a very simple tool that traces the following events in the debuggee (like a very simple/limited IntelliTrace):

    1. Method entry with parameter values.
    2. Method exit with parameter values and return value.
    3. Exceptions with the stack trace, where exception was raised and where it’ll be handled.

    For this, is it necessary to set breakpoints at the beginning and end of all methods in the debuggee in order to get the method entry/exit events? Isn’t there a generic “give me all method entry/exit events” (there is in Java)?

    Also, I’m probably a bit lame, but I can’t seem to get part 4 working with set-break commands. Could you please show an example on how to use this?

    Example:

    echo set-break Class00001.exe!Class00001.method_01 | mindbgtest c:\\Class00001.exe

    throws

    Unhandled Exception: System.InvalidOperationException: Cannot read keys when either application does not have a console or when console input has been redirected from a file. Try Console.Read.
    at System.Console.ReadKey(Boolean intercept)
    at System.Console.ReadKey()
    at mindgbtest.Program.Main(String[] args) in C:\\mindbg_part4\mindbg\mindgbtest\Program.cs:line 52

    Any pointers are most welcome.

    Thanks in advance.

    Cheers

    Finn

    • Sebastian Solnica January 30, 2012 / 01:03

      Hi Finn,

      Unfortunately method tracing is not supported directly in the managed debugging API. Using breakpoints might be a workaround here, but it will really slow down the application (especially the application start). If you can modify the binaries you should consider using PostSharp or it’s open-source alternative AfterThought. If you can’t touch the binaries think about the profiling API – though it’s a little bit harder to use.

      Now, about the issue you have. In CorDebugger.CreateProcess method I’m adding a special flag (UInt32)CreateProcessFlags.CREATE_NEW_CONSOLE. This opens a new window for the debugee process so the debugger can still use its own console window. If you don’t want to spawn a new window you need to be careful with calling Console.ReadKey from the debugger if the debuggee is running. There is probably a bug in mindbg (I will try to fix it in the nearest future) that does stop the process from running when waiting for the user input. You may check mdbg source code for a correct approach.

      Hope it helps,
      Sebastian

  2. ellebaek January 31, 2012 / 09:01

    Hi Sebastian

    Thanks a lot for your prompt reply – very helpful and appreciated! I wasn’t aware that you could access parameter/return values through the profiling API so I’m checking that out.

    Thanks a lot.

    Cheers

    Finn

  3. Matthias February 23, 2012 / 12:43

    Hi Sebastian,

    great work, nicely explained.
    I have Visual C# 2010 Express and started mindbgtest with a very simple C# program (has nothing but some Console.WriteLine inside main and a Console.ReadKey to wait). The program didn’t stop at main as explained in part 3 (I tried both possibilities, creating the process and attaching to it) and didn’t load any symbols. Am I missing something ? Some options I have to set for the program I like to debug ?

    • Sebastian Solnica February 28, 2012 / 07:27

      Hi Matthias,

      Thank you for kind words. Sorry for the delay but it’s been a while since I worked on mindbg code and needed to refresh my memory:) It seems that something was changed in .NET internals and the debugger stopped working – I will need a moment to have a deeper look at it and will get back to you as soon as I figure out what’s going on there.

      Cheers,
      Sebastian

    • Sebastian Solnica March 1, 2012 / 08:05

      I think that the problem lies in DebuggingFacility.GetRuntime method. When starting the debugging process it sometimes returns an incorrect version of the runtime. I need to write some code that will check the version of the framework that the executable was created for and load it into the debugger. For now a simple workaround would be to fill the desiredVersion parameter (of the DebuggingFacility.CreateDebuggerForExeutable) to either “v4.0” if your assembly is .NET4 or “v2.0” for .NET2/3.5. Sorry for any inconvenience and I will try to fix it soon.

      Cheers,
      Sebastian

  4. Matthias February 23, 2012 / 12:44

    Thanks in advance.

    Cheers

    Matthias

  5. Alejandro Mosquera May 3, 2012 / 16:47

    Hey. You could write an example of use?

    thanks

  6. Arun Satyarth July 28, 2015 / 14:46

    Is there any way I can get a MethodInfo object if I have the token? I know that reverse is possible. ie to get token from MethodInfo. Please help.

    • Sebastian Solnica July 28, 2015 / 22:43

      Yes, have a look at CorMetadata.cs (project corapi in the Microsoft’s Mdbg sample) – there is a method GetMethodInfo there which maps token to a MethodInfo (under the hood it’s using IMetadataImport.GetMethodProps). Is this what you were looking for?

      • Arun Satyarth July 29, 2015 / 09:10

        Thanks for replying so promptly… It was almost what I needed until I realised that it is returning a fake MethodInfo object. There is a MetadataMethodInfo class which derives from MethodInfo, but overrides all its methods to throw NotImplementedException. So this is not the real MethodInfo object.
        I needed MethodInfo so that I could get the MethodHandle and forcefully JIt it using RuntimeHelpers.PrepareMethod.
        Just to give you a brief of what I am trying to achieve. I want to get the native(x86) code of a debugee function from the debugger. After getting an ICorDebugFunction, I can call GetNativeCode on it but it returns the native code only if it has been Jited. So I need to forcefully JIt it. The RuntimeHelpers.PrepareMethod can do that but it needs a methodhandle( not a token). So can we get the real MethodInfo object(which can get me the handle) of a debugee function from the debugger? In other words, is it possible to do reflection on debugee from debugger(at least to get the methodhandle)? Thanks again in advance….

      • Arun Satyarth August 7, 2015 / 13:17

        Thanks Sebastian…

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.