How to Not Step Into Functions using the Visual C++ Debugger

Disclaimer: This is not a documented or supported feature. That means it was not tested, and might not work, and if it doesnt please dont call Microsoft Support and complain. If it does work, then hoorah.

 

Introduction

Ever stepped into a function you didn’t want to? Like an ATL or MFC CString constructor for example? Or a conversion operator you are really not interested in? Well there is a way to tell the Visual Studio debugger to never, ever step into a particular function. This is not a documented option, but the information is available in newgroups, but the changes from 6.0 to 7.0 have caused a great deal of confusion. Here is the real story for 7.0 (and 7.1).

Visual C++ 6

Visual C++ 6 users [by the way, stop using VC6 please, its TWELVE years old now] should run over to https://www.codeguru.com/debug/autoexp.shtml to get the scoop. Life was simple in VC6, but due to the fact that the ReadPrivateProfile API was used to read the autoexp.dat file, there were some limitations on what could and could not go in the file, especially equals signs. For this and other reasons (eg non-Power users cannot write to autoexp.dat on an NTFS drive) the information was moved to the registry for 7.0.

 

Visual Studio 7.0/7.1

The new list lives in the registry underneath HKCU\Software\Microsoft\VisualStudio\7.0\NativeDE\StepOver (use 7.1 if you are using Visual Studio 2003). The two last keys will not exist by default. Underneath this you need to place a list of string values of decimal numbers, paired with regular expressions. Figure 1 shows an example of this from my VS 2003 machine. Using a screenshot is clearer than showing the contents of a .reg file as .reg files need extra escape characters which renders them much more meaningless. (I will show the matching .reg file later). Due to an “oversight” (translation: bug) the items are evaluated in descending numeric order e.g. the “20” rule will match before the “10” rule. If you look at the example you can see that I have one rule, and that is any function name that starts with “ATL::” will not be stepped into.

 

The registry really contains “ATL\:\:.*=nostepinto” : lets dig into that a bit more. Function names are specified using regular expression syntax, the same as the rest of Visual Studio. In regex you need to escape colon characters, hence the two “\:” sequences. The “.*” is regex for “zero or more of any character” ie a general wildcard. The equals sign marks the end of the function name, and “nostepinto” means functions matching this patter will not be stepped into. The only other option for the right-hand-side is “stepinto” which performs the exact opposite function: it tells the debugger you want to step into these functions no matter what other entries (that follow this one) say.

Having decoded the screenshot, here is the .reg file that was made when I exported it. Note it is more complicated due to the escaping rules for .reg files.

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\7.1\NativeDE\StepOver]
"20"="ATL\\:\\:.*=nostepinto"

 

Visual Studio 2008

Moved to HKLM\Software\Microsoft\VisualStudio\9.0\NativeDE\StepOver. Yes, that means you need to be an Admin to set it up, and it is set for all users of your box. That's what happens when features aren't "official". Note that there is a default list of items to not step into.

 

Visual Studio 2010

Moved to HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\10.0\NativeDE\StepOver. Features the same default list as VS 2008.

 

Visual Studio 2012 (dev11)

Everything has changed! Until the VC++ team put something on their blog (feel free to bug them to do this), take a peek at this file:

C:\Program Files[ (x86)]\Microsoft Visual Studio 11.0\Common7\Packages\Debugger\Visualizers\default.natstepfilter 

 

64-bit Note

Remember to insert "\Wow6432Node" after "\Software" in all these reg paths if you are running 64-bit Windows as VS is still (regrettably) 32-bit.

 

Advanced Features

The debugger supports additional escapes over and above the normal Visual Studio regular expression syntax, but note that even I have never tried these so YMMV. I got this list from comments in the source. Well I'm sure you know that such things cannot always be trusted 100%, but here they are anyway:

\cid      A C/C++ identifier

\funct   A C/C++ function name

\scope  A set of class/namespace specifiers for a function (e.g. ATL::CFoo::CBar::)

\anything         any string

\oper    A C/C++ operator

 

Additional Examples

These examples are shown exactly how you would enter them via RegEdit, not in the more cumbersome .reg format. The numbers are arbitrary, but in the multi-line examples their relative values are important to get the right ordering.

Don’t step into members of CString[AWT] etc:

1 \scope:CString.*\:\:.*=NoStepInto

Don’t step into overloaded operators:

10 \scope:operator\oper:=NoStepInto

Don’t step into ATL:: except for CComBSTRs non-operator members:

20 ATL\:\:CComBSTR::\funct:=StepInto

10 ATL\:\:.*=NoStepInto

Don’t step into templated things, unless they are merely templated functions in a non-templated class:

20 \scope:\funct:=StepInto

10 .*[\<\>].NoStepInto

 

Notes

  • This is not a documented feature. Well obviously you are reading this “documentation” right here, but what I mean is that is not guaranteed to work as it was never officially tested, not is it supported by Microsoft. Its existence in future versions or update to current versions is not guaranteed.
  • The registry is scanned when you Launch a debuggee via F5. It is not scanned when you Attach to a process (a classic example of a feature that was never properly tested).
  • It works only for the native C++ debugger. Managed languages can use the DebuggerHiddenAttribute to achieve similar control but at the source level.
  • When choosing your regular expressions, you should check (via the disassembly window) what the real names of the functions are. The real names are often more complex then you might imagine, due to the use of #define, namespaces, and templates. For example a simple CString constructor with a char * argument in an MFC application is in fact called ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char> > >::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char> > > : phew!

MSFT disclaimer: This posting is provided "AS IS" with no warranties, and confers no rights.