Debugging STL Containers with WinDbg: Prolog 2

In the original prolog of the Debugging STL Containers with WinDbg series, I started off with a simple main.cpp source, compiled it with the Visual Studio compiler, and debugged the executable with WinDbg.

A few folks mentioned that there ought to be some info that covers the steps from having an executable to seeing the debugger output. I had the assumption that if readers were interested in those blog posts they’d probably have some experiences with WinDbg. But I do agree that no matter how trivial some things might be, documenting the exact steps could be helpful one way or the other.

So here are my steps.

1. Prepare the Operating System, Compiler, and WinDbg:

a. Install Windows 8 Version 6.2 Build 9200.

b. Install Visual Studio Ultimate 2012 Version 11.0.50727.1 RTMREL.

c. Install Windows Software Development Kit (SDK) for Windows 8.

2. Open a Visual Studio command prompt.

In Windows 8, press Windows logo key Windows logo key+Q, and type "Developer Command Prompt for VS2012". Select the resulting app.

Here’s what my command prompt looks like where I create main.cpp:

 C:\Program Files (x86)\Microsoft Visual Studio 11.0>cd\
C:\>md Projects
C:\>cd Projects
C:\Projects>md STL
C:\Projects>cd STL
C:\Projects\STL>notepad main.cpp

3. Write some code in main.cpp. Here’s an example:

 #include <iostream>
#include <string>
#include <vector>

using namespace std;

int main() {
    vector<string> ss;
    for (int i = 0; i < (int)ss.size(); i++)
        cout << ss[i] << endl;

4. Save the file, and run the following command to compile it:

 C:\Projects\STL>cl /EHsc /nologo /W4 /MTd /Zi main.cpp

5. Start WinDbg. In Windows 8, press Windows logo key Windows logo key+Q, and type "windbg". Select "WinDbg (X86)".

6. In WinDbg, press Ctrl+E (or go to File..Open Executable), type in C:\Projects\STL\main.exe for File name, and then click the Open button. If you receive a Workspace prompt, click Yes to save information for workspace.

7. Type in the following commands, one after another, in WinDbg:

!sym noisy
!symfix c:\symbols
.reload /f ntdll.dll
.reload /f kernel32.dll
bp main!main

At this point here’s what your WinDbg Command window might look like (I’ve highlighted the commands you need to type in):

Microsoft (R) Windows Debugger Version 6.2.9200.16384 X86
Copyright (c) Microsoft Corporation. All rights reserved.

CommandLine: C:\Projects\STL\main.exe
Symbol search path is: *** Invalid ***
* Symbol loading may be unreliable without a symbol search path.           *
* Use .symfix to have the debugger choose a symbol path.                   *
* After setting your symbol path, use .reload to refresh symbol locations. *
Executable search path is: 
ModLoad: 00a90000 00b81000   main.exe
ModLoad: 772e0000 77437000   ntdll.dll
ModLoad: 770f0000 77220000   C:\Windows\SysWOW64\KERNEL32.DLL
ModLoad: 76830000 768d6000   C:\Windows\SysWOW64\KERNELBASE.dll
(fac.d10): Break instruction exception - code 80000003 (first chance)
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for ntdll.dll - 
eax=00000000 ebx=00000003 ecx=a0e10000 edx=00000000 esi=00000000 edi=00a900e8
eip=7738054d esp=0059f4e4 ebp=0059f510 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
7738054d cc              int     3
0:000> !sym noisy
noisy mode - symbol prompts on
0:000> !symfix c:\symbols
DBGHELP: Symbol Search Path: 
DBGHELP: Symbol Search Path: SRV*c:\symbols*
DBGHELP: Symbol Search Path: SRV*c:\symbols*
0:000> .reload /f ntdll.dll
DBGHELP: ntdll - public symbols  
0:000> .reload /f kernel32.dll
DBGHELP: KERNEL32 - public symbols  
0:000> bp main!main
SYMSRV:  c:\symbols\main.pdb\2AA57E80024F4D219D902A73C78FEA2D2\main.pdb not found
SYMSRV: not found
*** WARNING: Unable to verify checksum for main.exe
DBGHELP: main - private symbols & lines 
0:000> g
Breakpoint 0 hit
eax=00d42d98 ebx=7efeb000 ecx=00000001 edx=00d45700 esi=00000000 edi=00000000
eip=00a91820 esp=0059f958 ebp=0059f9a0 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
00a91820 55              push    ebp

Here’s a sample picture of it:


Here’s what happened:

!sym noisy - Activates noisy symbol loading so that when the debugger loads symbols you get more details like exactly where the symbol was loaded.

!symfix c:\symbols (or .symfix) - Sets the symbol path to point to Microsoft symbol store. I specified c:\symbols to store downloaded symbols there.

.reload /f ntdll.dll and .reload /f kernel32.dll - Since when this debug session starts I didn’t have proper symbol path set, symbols for these two modules were not loaded. After I set the symbol path with !symfix, I’m manually reloading the symbols for these two modules. This is not necessary in this specific case but in general it’s a good practice to have symbols for these two modules.

bp main!main - Sets a breakpoint at main().

g - Go (F5), just like what you’re accustomed to in Visual Studio.

8. At this point you can just press F10 or the Step Over button on the toolbar to step over the source code until you hit the for line. This is where the code added three elements into the vector.

9. Now in the debugger’s Command window you can type commands like dt ss to look at the vector:

 0:000> dt ss
Local var @ 0x59f8dc Type 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         : 0x00b750f8 std::_Container_proxy
   +0x004 _Myfirst         : 0x0059f8ec std::basic_string<char,std::char_traits<char>,std::allocator<char> >
   +0x008 _Mylast          : 0x00ae3b1a std::basic_string<char,std::char_traits<char>,std::allocator<char> >
   +0x00c _Myend           : 0x00000008 std::basic_string<char,std::char_traits<char>,std::allocator<char> >

There you go. The steps that accompany the rest of the series.

You know what, actually I’m glad I wrote these down. I might need to reference these sometime later myself.

P.S. I was tempted to go back to the prolog and add a section with an "Update" label. Sounded logical, but I didn’t want to bloat that blog post into a book. Hence this post, Prolog 2. I’ve tagged this post with the same tags as the other posts in the series, so at least the tag cloud works as it’s supposed to. There are so many other questions that could spawn from this little post, like "why not use Visual Studio to create main.cpp", "why X86 and not X64", "why c:\symbols and not d:\symbols", or even "why Windows 8 and not Windows XP or Windows 7". I’ll re-evaluate the need to expand on these points at a later time :)

P.P.S. Just a gentle reminder, if you’re still using Windows XP. Its support ends on April 8, 2014!