What does a debugger author need to do to support func-eval?

I've mentioned func-eval (aka property eval) is evil for end-users; but it's also evil if you want to write a debugger that uses func-eval.

For example, let's say you're writing your own managed debugger and you have a watch window, and you want to eval property-getters and ToString() calls on items.  Since VS does this, you can rest assured that it's indeed possible, but it does have some challenges:

  1. Func-eval is asynchronous. See here for how to do a func-eval. Quick summary is that you must setup the eval (ICorDebugEval::CallFunction), Continue the process, and then wait for an EvalComplete callback with your result.  In whidbey, ICDEval's result generally gives you back an ICorDebugHandleValue, which is like a strong-reference version of ICorDebugReferenceValue. This means you can hold onto that value and dereference it across multiple Continues. MDbg maps that value to the pseudo-value $result. Pre-whidbey, you had to play some very very evil games to make this work.
  2. You need to make some policy decisions.
    a) Other debug events may come in the meantime and you need to decide how to handle those.  For eg, if you hit a breakpoint, do you want to silently ignore it finish the eval, or do you want to enter a nested break state? It's your debugger and you get to decide what policy you want.
    b) Also, do you suspend other threads? If no, they will move on you when you eval and thus your eval becomes even more invasive. If yes, you may get deadlocks.
  3. You need to beware of neutering and refresh ICDValues. Many ICorDebug*Value objects become invalid once you continue the process. This means that you need to refresh all the outstanding values by getting a new ICDValue instance.
    Your debugger knows how it found everything it's displaying. So for each value, you need to reget its corresponding ICDValue. This is ugly, but there are some decent OOP techniques to do this somewhat cleanly. For example, you could wrap ICDValues in your own class, which remembers how the val was obtained and has some virtual Refresh() method to reget a new underlying ICDValue. This lets you do other things too, such as track when the value has changed and then display it differently (such as in red).
  4. You need to decide how you want to deal with side-effects. For example, suppose you have:
        class Foo {
            int m_x = 4; 
            int Prop1 { get { return m_x++; } }
            int Prop2 { get { return m_x++; } }
        }
    If you eval all the members, you change the members. Evaling the properties first won't help either.

I also think that all of these problems are innate challenges of func-eval. Regardless of how we carved the APIs, we would have these sort of problems. In some cases, we could shift the APIs around to solve a problem, but it would create another problem.

The bottom line is automatically doing func-evals for getters / ToString() as an integrated part of inspection is actually a very complicated thing. The VS debugger folks deserver a lot of credit for making this work so seamlessly. If you are thinking of doing this, play around with VS's debugger to get a feel for what it could look like and what sort of issues you may run into.

Comments

  • Anonymous
    March 05, 2006
    I created a blog category for Func-eval (aka Property Evaluation), and I updated some of my old...
  • Anonymous
    March 05, 2006
    Lol, you post this the DAY AFTER I sorely needed it :)

    I'd like to point out (especially for search engines) that when you're doing func-eval and your ICD*Value objects get neutered, in C# this will manifest as a COM exception that states "This object is in a zombie state".

    Maybe if the search engines pick up that exact phrase it'll save some poor sap 4 days of trying to find good info.

    I did essientially what you point out in #3 above and although it causes some cascading neuter effects, with some good synchronization and smart updates it works beautifully.

    Thanks again for having an active blog, this information is invaluable!!!
  • Anonymous
    March 05, 2006
    J - sorry :) I actually just saw your comment this morning, started writing a comment to reply, realized that it was a big comment and so decided to promote it to a post.

    Here's a tip that may help in the future: when you get COM exceptions, check the underlying HR and search that.

    Why are you writing a debugger anyways? :)

  • Anonymous
    March 05, 2006
    I did search for the HR, and found lots of unrelated stuff, but when I saw your post and searched the Mdbg source itself for that HR I found it.  I guess I just got kinda wrapped around the axle and missed the obvious ;)

    As for why I'm building a debugger, at this point it's research and personal interest.  I am such a geek :)

    By looking at the Mdgb source, this blog, DebugRef and other docs, I have managed to put together a quite capable though relatively simplistic (when compared to Mdbg) debugger that supports everything I need quite nicely.

    I don't need to debug native code (in fact my debugger is full-on JMC), and have full control over the type and scope of the debuggee (and by association the debugger), so I simply couldn't be happier with my simple debugger!

    You can't imagine how happy I am that there is an ICorDebug lib for me to use, and that this blog and related sample code exists.

    Thanks again!
  • Anonymous
    March 06, 2006
    J - glad this can be useful! Feel free to send any feedback my way.
  • Anonymous
    March 13, 2007
    Func-eval is evil . Func-eval abort is even worse. For those coming in late, Func-eval is when the debugger