Debugging STL Containers with WinDbg Part 4: Pair
For previous posts of this series, check out the following links:
Debugging STL Containers with WinDbg: Prolog
Debugging STL Containers with WinDbg: Prolog 2
Debugging STL Containers with WinDbg Part 1: Vector
Debugging STL Containers with WinDbg Part 2: List
Debugging STL Containers with WinDbg Part 3: Map
I should’ve blogged this post before part 3 as maps use pair to store each tree node’s value, but then I originally intended to fire and forget, so bare with me for now.
A pair is a template class that wraps a pair of values. Simply, pair::first stores the first value, and pair::second stores the second value.
Take the following example:
C:\Projects\STL>type pair.cpp
#include <iostream>
#include <string>
using namespace std;
int main() {
pair<string, string> mypair("pair1", "pair2");
cout << "mypair: " << mypair.first << " " << mypair.second << endl;
}
C:\Projects\STL>cl /EHsc /nologo /W4 /MTd /Zi pair.cpp
pair.cpp
C:\Projects\STL>pair
mypair: pair1 pair2
First you need to look at the type of the values. Do a dt on the pair variable and the debugger should tell you:
0:000> dt mypair
Local var @ 0x32fae0 Type std::pair<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::basic_string<char,std::char_traits<char>,std::allocator<char> > >
+0x000 first : std::basic_string<char,std::char_traits<char>,std::allocator<char> >
+0x01c second : std::basic_string<char,std::char_traits<char>,std::allocator<char> >
In this case since both of my pair values are strings, if you just do a dt -r on the pair variable, you can already see the values:
0:000> dt -r mypair
Local var @ 0xe7fe6c Type std::pair<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::basic_string<char,std::char_traits<char>,std::allocator<char> > >
+0x000 first : std::basic_string<char,std::char_traits<char>,std::allocator<char> >
+0x000 _Myproxy : 0x00ff2e68 std::_Container_proxy
+0x000 _Mycont : 0x00e7fe6c std::_Container_base12
+0x004 _Myfirstiter : (null)
+0x004 _Bx : std::_String_val<std::_Simple_types<char> >::_Bxty
+0x000 _Buf : [16] "pair1"
+0x000 _Ptr : 0x72696170 "--- memory read error at address 0x72696170 ---"
+0x000 _Alias : [16] "pair1"
+0x014 _Mysize : 5
+0x018 _Myres : 0xf
=00ce1d0c npos : 0xffffffff
+0x01c second : std::basic_string<char,std::char_traits<char>,std::allocator<char> >
+0x000 _Myproxy : 0x00ff2a68 std::_Container_proxy
+0x000 _Mycont : 0x00e7fe88 std::_Container_base12
+0x004 _Myfirstiter : (null)
+0x004 _Bx : std::_String_val<std::_Simple_types<char> >::_Bxty
+0x000 _Buf : [16] "pair2"
+0x000 _Ptr : 0x72696170 "--- memory read error at address 0x72696170 ---"
+0x000 _Alias : [16] "pair2"
+0x014 _Mysize : 5
+0x018 _Myres : 0xf
=00ce1d0c npos : 0xffffffff
You can expand each value individually as well:
0:000> dt -r mypair first..
Local var @ 0xe7fe6c Type std::pair<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::basic_string<char,std::char_traits<char>,std::allocator<char> > >
+0x000 first :
+0x000 _Myproxy :
+0x004 _Bx :
+0x000 _Buf : [16] "pair1"
+0x000 _Ptr : 0x72696170 "--- memory read error at address 0x72696170 ---"
+0x000 _Alias : [16] "pair1"
+0x014 _Mysize : 5
+0x018 _Myres : 0xf
=00ce1d0c npos : 0xffffffff
0:000> dt -r mypair second..
Local var @ 0xe7fe6c Type std::pair<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::basic_string<char,std::char_traits<char>,std::allocator<char> > >
+0x01c second :
+0x000 _Myproxy :
+0x004 _Bx :
+0x000 _Buf : [16] "pair2"
+0x000 _Ptr : 0x72696170 "--- memory read error at address 0x72696170 ---"
+0x000 _Alias : [16] "pair2"
+0x014 _Mysize : 5
+0x018 _Myres : 0xf
=00ce1d0c npos : 0xffffffff
Or you can use the C++ expression evaluator to access _Buf directly:
0:000> ?? mypair.first._Bx
union std::_String_val<std::_Simple_types<char> >::_Bxty
+0x000 _Buf : [16] "pair1"
+0x000 _Ptr : 0x72696170 "--- memory read error at address 0x72696170 ---"
+0x000 _Alias : [16] "pair1"
0:000> ?? mypair.second._Bx
union std::_String_val<std::_Simple_types<char> >::_Bxty
+0x000 _Buf : [16] "pair2"
+0x000 _Ptr : 0x72696170 "--- memory read error at address 0x72696170 ---"
+0x000 _Alias : [16] "pair2"
Let’s look at a pair of containers with the following example:
C:\Projects\STL>type pair.cpp
#include <iostream>
#include <string>
#include <vector>
#include <list>
using namespace std;
int main() {
vector<string> myvector;
myvector.push_back("1");
myvector.push_back("2");
myvector.push_back("3");
list<string> mylist;
mylist.insert(mylist.end(), "one");
mylist.insert(mylist.end(), "two");
mylist.insert(mylist.end(), "three");
pair<vector<string>, list<string>> mypair(myvector, mylist);
cout << mypair.first[0] << " " << mypair.second.front() << endl;
}
C:\Projects\STL>cl /EHsc /nologo /W4 /MTd /Zi pair.cpp
pair.cpp
C:\Projects\STL>pair
1 one
Again, you need to look at the type of the values. Do a dt on the pair variable and the debugger should tell you:
0:000> dt mypair
Local var @ 0xf3f938 Type std::pair<std::vector<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::allocator<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >,std::list<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::allocator<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > > >
+0x000 first : std::vector<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::allocator<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >
+0x010 second : std::list<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::allocator<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >
In this case my first value is of type std::vector<std::basic_string>, and my second value is of type std::list<std::basic_string>.
From here, I can move forward in two ways:
1. Use the C++ expression evaluator for both first and second:
0:000> ?? mypair.first
class std::vector<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::allocator<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >
+0x000 _Myproxy : 0x01586760 std::_Container_proxy
+0x004 _Myfirst : 0x01586aa8 std::basic_string<char,std::char_traits<char>,std::allocator<char> >
+0x008 _Mylast : 0x01586afc std::basic_string<char,std::char_traits<char>,std::allocator<char> >
+0x00c _Myend : 0x01586afc std::basic_string<char,std::char_traits<char>,std::allocator<char> >
0:000> ?? mypair.second
class std::list<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::allocator<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >
+0x000 _Myproxy : 0x01586c70 std::_Container_proxy
+0x004 _Myhead : 0x01586c10 std::_List_node<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,void *>
+0x008 _Mysize : 3
2. Use the offsets to get the addresses and use dt to display the type instances:
From the output of dt mypair above, the pair is at 0xf3f938. The first value is at offset 0x000, and the second value is at offset 0x010. With this info, I can ask the debugger to display the specific types at the specific addresses
0:000> dt mypair
Local var @ 0xfbf730 Type std::pair<std::vector<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::allocator<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >,std::list<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::allocator<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > > >
+0x000 first : std::vector<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::allocator<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >
+0x010 second : std::list<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::allocator<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >
0:000> dt 0xfbf730 std::vector<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::allocator<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >
pair!std::vector<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::allocator<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >
+0x000 _Myproxy : 0x01586760 std::_Container_proxy
+0x004 _Myfirst : 0x01586aa8 std::basic_string<char,std::char_traits<char>,std::allocator<char> >
+0x008 _Mylast : 0x01586afc std::basic_string<char,std::char_traits<char>,std::allocator<char> >
+0x00c _Myend : 0x01586afc std::basic_string<char,std::char_traits<char>,std::allocator<char> >
0:000> dt 0xfbf730+0x10 std::list<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::allocator<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >
pair!std::list<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::allocator<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >
+0x000 _Myproxy : 0x01586c70 std::_Container_proxy
+0x004 _Myhead : 0x01586c10 std::_List_node<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,void *>
+0x008 _Mysize : 3
From here I can conveniently reference part 1 and part 2 of this series to traverse the containers.