Search code examples
visual-studiobreakpointstrace

What expressions are allowed in tracepoints?


When creating a tracepoint in Visual Studio (right-click the breakpoint and choose "When Hit..."), the dialog has this text, emphasis mine:

You can include the value of a variable or other expression in the message by placing it in curly braces...

What expressions are allowed?


Solution

  • Microsoft's documentation is rather sparse on the exact details of what is and is not allowed. Most of the below was found by trial and error in the Immediate window. Note that this list is for C++, as that's what I code in. I believe in C#, some of the prohibited items below are actually allowed.

    Most basic expressions can be evaluated, including casting, setting variables, and calling functions.

    General Restrictions
    • Only C-style casts supported; no static_cast, dynamic_cast, reinterpret_cast, const_cast
    • Can't declare new variables or create objects
    • Can't use overloaded operators
    • Ternary operator doesn't work
    • Can't use the comma operator because Visual Studio uses it to format the result of the expression; use multiple sets of braces for multiple expressions
    Function Calls
    • Prohibited calls:
      • Lambdas (can't define or call them)
      • Functions in an anonymous namespace
      • Functions that take objects by value (because you can't create objects)
    • Permitted calls:
      • Member functions, both regular and virtual
      • Functions taking references or pointers, to either fundamental or class types
      • Passing in-scope variables
      • Using "&" to pass pointers to in-scope variables
      • Passing the literals "true", "false", numbers
      • Passing string literals, as long you don't run afoul of the "can't create objects" rule
      • Calling multiple functions with one tracepoint by using multiple sets of braces
    Variable Assignment
    • Prohibited:
      • Objects
      • String literals
    • Permitted:
      • Variables with fundamental types, value either from literals or other variables
      • Memory addresses, after casting: { *(bool*)(0x1234) = true }
      • Registers: { @eip = 0x1234 }

    Use Cases

    Calling functions from tracepoints can be quite powerful. You can get around most of the restrictions listed above with a carefully set up function and the right call. Here are some more specific ideas.

    Force an if

    Pretty straightforward: set up a tracepoint to set a variable and force an if-condition to true or false, depending on what you need to test. All without adding code or leaving the debug session.

    Breakpoint "toggling"

    I've seen the question a few times, "I need to break in a spot that gets hit a lot. I'd like to simply enable that breakpoint from another breakpoint, so the one I care about only gets breaks from a certain code path. How can I do that?" With our knowledge above, it's easy, although you do need a helper variable.

    1. Create a global boolean, set to false.
    2. Create a breakpoint at your final destination, with a condition to break only when the global flag is true.
    3. Set tracepoints in the critical spots that assign the global flag to true.

    The nice thing is that you can move the tracepoints around without leaving the debugging session. Use the Immediate window or the Watch window to reset your global flag, if you need to make another run at it. When you're done, all you need to clean up is that global boolean. No other code to remove.

    Automatically skip code

    The EIP register (at least on x86) is the instruction pointer. If you assign to it, you can change your program flow.

    1. Find the address of the line you want to skip to by breaking on it once and looking at the value of EIP, either in the Registers window or the Watch window with "@eip,x". (Note that the value in the Registers window is hex, but without the leading "0x".)
    2. Add a tracepoint on the line you want to skip from, with an expression like {@eip = address}, using the address from step 1.
    3. EIP assignment will happen before anything on the line is executed.

    Although this can be handy, be careful because skipping code like this can cause weird behavior.