Improving Boost Docs Project


Boost Debugger Visualizers

 http://svn.boost.org/trac/boost/wiki/DebuggerVisualizers


About Debugger Visualizers

Debugger visualizers enable developers to view data during debugging sessions in a human-friendly way.

Though each debugger UI usually tries to display the data as nicely as possible, with complex types such as those of Boost, the general approach usually does not allow the user to quickly get the information she needs. That's where debugger visualizers come to help: they are user- or library vendor-defined formatting rules that help the debugger UI to deliver data in a comprehensible form.


Visual Studio 2012

Visual Studio 2012 adds a new way of writing visualizers. There is an article about it here:

 http://code.msdn.microsoft.com/Writing-type-visualizers-2eae77a2

Visual Studio 2005 / 2008 / 2010

 Microsoft Visual Studio 2005 is the first of the Visual Studio versions to support debugger visualizers usable for template classes. For CLR languages, such as C#, the possibilities are even more powerful, and documented. Native C++ debugger visualizers are not documented, though it was claimed by some people from Microsoft that some docs are coming - so if you know of some, please let us know!

Native C++ Debugger Visualizers for MSVS 2005 / 2008 / 2010

The formatting rules are stored in the autoexp.dat text file (the [Visualizer] section) which can be found at

%VSINSTALLDIR%\Common7\Packages\Debugger\autoexp.dat

New visualizers can be installed by appending visualizer code to the [Visualizer] section. In the future, we would like to have an installer for this.

Existing visualizers

All visualizers are available in the svn at  https://svn.boost.org/svn/boost/sandbox/boost_docs/subprojects/DebuggerVisualizers. Currently, we support the following Boost types:

  • boost::array, ptr_array, ptr_deque, ptr_list, ptr_map, ptr_multimap, ptr_set, ptr_multiset, ptr_vector
  • boost::bimap
  • boost::interprocess::offset_ptr
  • boost::intrusive::list
  • boost::optional
  • boost::multi_index_container
  • boost::multiprecision
  • boost::shared_ptr
  • boost::posix_time::ptime, boost::posix_time::time_duration (two variants are available)
  • boost::regex
  • boost::variant

There's also an unfinished visualizer for unordered containers attached to #4209. I'm not sure It isn't clear if it's possible to get it to work.

If you're lacking a visualizer for your favorite type, let us know or write one yourself - it's easy!

Note that some older files also exist in the Boost Vault at  Boost Vault/Debug/Visualizers_MSVC_8, which you can use to upload new visualizers if you create some (though most people just post a message to the Boost Users mailing list with an attachment).

HOWTO

Our main source of knowledge is the  blog at virtualdub.org and the experience that we gained doing our own visualizers for Boost.

Some valuable information can also be found in the MSDN articles Unsupported Operators and Additional Operators and Restrictions on Native C++ Expressions.

I'll try to summarize our current knowledge here.

Visualizers (visualizer rules) have this general format:

; Comments start with semicolon
pattern [| pattern ...] {
  preview (
    preview_expression
  )
  stringview (
    stringview_expression
  )
  children (
    children_expression
  )
}

The stringview section is used for formatting the data to text, HTML or XML (not covered by this guide yet). The preview section is used to format the data for one-line view. This is used in tooltips (when you hover over variables in code editor) and for the watch window. The children section enumerates child nodes (for structured types). All sections are optional.

Rule pattern matching

When visualizing a variable, the IDE looks at its (most derived) type. It then looks at all patterns in autoexp.dat and tries to find match. My guess is that it chooses the first rule that matches, but there should not be more than one matching rule anyway.

Type names are processed as text. You can see the exact string that the visualizer sees in the watch window (the "Type" column). The bad thing about this is that extremely long type names are cut off at a certain string length. Such cropped strings then fail to match the visualizer patterns.

Patterns can contain wildcards. (My guess is that the patterns are internally transformed to regular expressions by the IDE.) This is great for templates. A wildcard can match one or more template arguments:

std::list<*>

and (preferred syntax)

std::list<*,*>

will both match std::list<int> - because it is in fact std::list<int,std::allocator<int> >.

As with marked subexpressions of regular expressions, we can access the actual text that matched the wildcards in the rule body's expressions. This text is referred to as $T1, $T2, $T3 etc. As with shell variable expansion, the $Ts are expanded in-place in the expression in which they appear. In case of std::list<*>, we have only one wildcard, and $T1 will be expanded to int,std::allocator<int>. In case of std::list<*,*>, $T1 expands to int and $T2 expands to std::allocator<int>. This is more usable, because we can access the item type (int) in the expressions.

preview section

The preview expression produces a string that is used for one-line representation of the data in the tooltips and in the watch window. Usually, one wants to pick 2 or 3 important members out of the hundreds members of the class.

The following code formats std::list preview:

std::list<*,*>{
  preview
    (
      #("[list size=", $e._Mysize, "]")
    )
}

Note the use of the contatenation operator #(s1, s2, ..., sn ). $e refers to the value of the variable being visualized (we are accessing the _Mysize member of the std::list implementation).

children section

When the children section is present, a [+] will appear to the left of the visualized value and can be used to expand it (display the children). Multiple children can be added using the #(c1, c2, ..., cn) operator, and child values can have custom names (and will appear sorted by name):

std::list<*,*>{
  children
  (
    #(
      first item: $e._Myhead->_Next->_Myval,
      second item: $e._Myhead->_Next->_Next->_Myval,
      [third item]: $e._Myhead->_Next->_Next->_Next->_Myval
    )
  )
}

This is nice, but we need more to display real structures like lists, arrays and trees. There are special processors for these structures:

#list

#list(head: head_expr size: size_expr next: next_expr) : deref_expr is used to visualize a linked list of values. This can be used to visualize std::list:

std::list<*,*>{
  children
  (
    #list(size: $c._Mysize,
          head: $c._Myhead->_Next,
          next: _Next
    ) : $e._Myval
  )
}

As you can see, we need to know the size of the list, where is the head node of the list, and how to get to a node's successor (this is done by accessing the next member of the current node). $c refers to the current expression, and in deref_expr, $e refers to the node being displayed.

Alternative approach is #list(head: head_expr skip: skip_expr next: next_expr) : deref_expr. Here skip_expr defines node value (probably compared by memory address) which terminates the list traversal. Also, if a node is reached twice (cycle detection), it is not displayed and the traversal is stopped.

#array

#array(expr: array_access_expr, size : size_expr) : deref_expr is similar to #list, but the values are accessed by indexing the array_head_expr by indices 0, 1, ..., (size_expr-1). In array_access_expr, $i denotes the current index.

There are optional parameters rank and base. base offsets the displayed indices of the array items (there must not be any deref_expr for this to work). rank can be used to display multidimensional arrays. In this case, size and base are evaluated repeatedly and $r in rank_expr and base_expr refers to the current rank being processed.

#tree

#tree(head: head_expr size: size_expr left: left_expr right:right_expr skip: skip_expr) : deref_expr is used to walk a binary tree. It is very similar to #list, the only difference being that the traversal is not linear but recursive depth-first. skip_expr is used to tell the shape of the tree (usually skip: 0).

Conditionals

There is a #if (expr) ( then_expr ) #elif ( elif_expr ) ... #else ( else_expr ) construct (with the #else parts optional) and a #switch(switch_expr) #case case0_value ( case0_expr ) ... #default ( default_expr ) #except ( except_expr ) ) construct that can be used in the expressions. My guess (haven't tried) is that values matching except_expr are not passed to #default.

Problems

One problem is when you have a pointer to a base class and you need to cast that to a derived class. It seems that such type casts are unsupported.

TODO

FAQ, examples, common pitfalls, ...


gdb, ddd, others

If you are interested, please send an email to the  boost-docs list telling us that you want to help.

Help wanted


Active developers



Filip Konvička
filip dot konvicka at logis dot cz